Merge mainline into pub_dependency_services (#3301)

diff --git a/analysis_options.yaml b/analysis_options.yaml
index 9cefb58..6177eba 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.yaml
+include: package:lints/recommended.yaml
 
 analyzer:
   errors:
@@ -12,53 +12,27 @@
 linter:
   rules:
     - avoid_catching_errors
-    - avoid_function_literals_in_foreach_calls
     - avoid_private_typedef_functions
     - avoid_redundant_argument_values
-    - avoid_renaming_method_parameters
     - avoid_returning_null_for_future
-    - avoid_returning_null_for_void
     - avoid_unused_constructor_parameters
     - avoid_void_async
-    - await_only_futures
-    - camel_case_types
     - cancel_subscriptions
-    - control_flow_in_finally
     - directives_ordering
-    - empty_statements
-    - file_names
-    - implementation_imports
-    - iterable_contains_unrelated_type
-    - list_remove_unrelated_type
-    - literal_only_boolean_expressions
     - missing_whitespace_between_adjacent_strings
     - no_adjacent_strings_in_list
     - no_runtimeType_toString
-    - non_constant_identifier_names
     - only_throw_errors
-    - overridden_fields
     - package_api_docs
-    - package_names
-    - package_prefixed_library_names
     - prefer_asserts_in_initializer_lists
     - prefer_const_declarations
-    - prefer_function_declarations_over_variables
-    - prefer_initializing_formals
-    - prefer_inlined_adds
-    - prefer_is_not_operator
-    - prefer_null_aware_operators
     - prefer_relative_imports
-    - prefer_typing_uninitialized_variables
-    - prefer_void_to_null
+    - prefer_single_quotes
     - sort_pub_dependencies
     - test_types_in_equals
     - throw_in_finally
-    - unnecessary_brace_in_string_interps
-    - unnecessary_getters_setters
+    - unawaited_futures
     - unnecessary_lambdas
     - unnecessary_null_aware_assignments
-    - unnecessary_overrides
     - unnecessary_parenthesis
     - unnecessary_statements
-    - unnecessary_string_interpolations
-    - void_checks
diff --git a/bin/dependency_services.dart b/bin/dependency_services.dart
new file mode 100644
index 0000000..1e7b4c1
--- /dev/null
+++ b/bin/dependency_services.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2021, 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.
+
+/// Support for automated upgrades.
+///
+/// For now this is not a finalized interface. Don't rely on this.
+import 'dart:async';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:pub/src/command.dart';
+import 'package:pub/src/command/dependency_services.dart';
+import 'package:pub/src/exit_codes.dart' as exit_codes;
+import 'package:pub/src/io.dart';
+import 'package:pub/src/log.dart' as log;
+
+class DependencyServicesCommandRunner extends CommandRunner<int>
+    implements PubTopLevel {
+  @override
+  String? get directory => argResults['directory'];
+
+  @override
+  bool get captureStackChains {
+    return argResults['trace'] ||
+        argResults['verbose'] ||
+        argResults['verbosity'] == 'all';
+  }
+
+  @override
+  bool get trace => argResults['trace'];
+
+  ArgResults? _argResults;
+
+  /// The top-level options parsed by the command runner.
+  @override
+  ArgResults get argResults {
+    final a = _argResults;
+    if (a == null) {
+      throw StateError(
+          'argResults cannot be used before Command.run is called.');
+    }
+    return a;
+  }
+
+  DependencyServicesCommandRunner()
+      : super('dependency_services', 'Support for automatic upgrades',
+            usageLineLength: lineLength) {
+    argParser.addFlag('version', negatable: false, help: 'Print pub version.');
+    argParser.addFlag('trace',
+        help: 'Print debugging information when an error occurs.');
+    argParser
+        .addOption('verbosity', help: 'Control output verbosity.', allowed: [
+      'error',
+      'warning',
+      'normal',
+      'io',
+      'solver',
+      'all'
+    ], allowedHelp: {
+      'error': 'Show only errors.',
+      'warning': 'Show only errors and warnings.',
+      'normal': 'Show errors, warnings, and user messages.',
+      'io': 'Also show IO operations.',
+      'solver': 'Show steps during version resolution.',
+      'all': 'Show all output including internal tracing messages.'
+    });
+    argParser.addFlag('verbose',
+        abbr: 'v', negatable: false, help: 'Shortcut for "--verbosity=all".');
+    argParser.addOption(
+      'directory',
+      abbr: 'C',
+      help: 'Run the subcommand in the directory<dir>.',
+      defaultsTo: '.',
+      valueHelp: 'dir',
+    );
+
+    addCommand(DependencyServicesListCommand());
+    addCommand(DependencyServicesReportCommand());
+    addCommand(DependencyServicesApplyCommand());
+  }
+
+  @override
+  Future<int> run(Iterable<String> args) async {
+    try {
+      _argResults = parse(args);
+      return await runCommand(argResults) ?? exit_codes.SUCCESS;
+    } on UsageException catch (error) {
+      log.exception(error);
+      return exit_codes.USAGE;
+    }
+  }
+
+  @override
+  void printUsage() {
+    log.message(usage);
+  }
+
+  @override
+  log.Verbosity get verbosity => log.Verbosity.normal;
+}
+
+Future<void> main(List<String> arguments) async {
+  await flushThenExit(await DependencyServicesCommandRunner().run(arguments));
+}
diff --git a/bin/pub.dart b/bin/pub.dart
index 926db85..f133a0f 100644
--- a/bin/pub.dart
+++ b/bin/pub.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/command_runner.dart';
 import 'package:pub/src/io.dart';
 
diff --git a/doc/repository-spec-v2.md b/doc/repository-spec-v2.md
index 6db7763..6ddfd0f 100644
--- a/doc/repository-spec-v2.md
+++ b/doc/repository-spec-v2.md
@@ -227,7 +227,7 @@
   "replacedBy": "<package>", /* optional field, if isDiscontinued == true */
   "latest": {
     "version": "<version>",
-    "isRetracted": true || false, /* optional field, false if omitted */
+    "retracted": true || false, /* optional field, false if omitted */
     "archive_url": "https://.../archive.tar.gz",
     "pubspec": {
       /* pubspec contents as JSON object */
@@ -236,7 +236,7 @@
   "versions": [
     {
       "version": "<package>",
-      "isRetracted": true || false, /* optional field, false if omitted */
+      "retracted": true || false, /* optional field, false if omitted */
       "archive_url": "https://.../archive.tar.gz",
       "pubspec": {
         /* pubspec contents as JSON object */
@@ -371,7 +371,9 @@
 archive size, or enforce any other repository specific constraints.
 
 This upload flow allows for archives to be uploaded directly to a signed POST
-URL for S3, GCS or similar blob storage service. Both the
+URL for [S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/HTTPPOSTExamples.html),
+[GCS](https://cloud.google.com/storage/docs/xml-api/post-object-forms) or
+similar blob storage service. Both the
 `<multipart-upload-url>` and `<finalize-upload-url>` is allowed to contain
 query-string parameters, and both of these URLs need only be temporary.
 
diff --git a/lib/pub.dart b/lib/pub.dart
index b78cbca..71de9d6 100644
--- a/lib/pub.dart
+++ b/lib/pub.dart
@@ -2,17 +2,28 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:args/command_runner.dart';
 import 'src/command_runner.dart';
 import 'src/pub_embeddable_command.dart';
 export 'src/executable.dart'
-    show getExecutableForCommand, CommandResolutionFailedException;
+    show
+        getExecutableForCommand,
+        CommandResolutionFailedException,
+        CommandResolutionIssue,
+        DartExecutableWithPackageConfig;
+export 'src/pub_embeddable_command.dart' show PubAnalytics;
 
 /// Returns a [Command] for pub functionality that can be used by an embedding
 /// CommandRunner.
-Command<int> pubCommand() => PubEmbeddableCommand();
+///
+/// If [analytics] is given, pub will use that analytics instance to send
+/// statistics about resolutions.
+///
+/// [isVerbose] should return `true` (after argument resolution) if the
+/// embedding top-level is in verbose mode.
+Command<int> pubCommand(
+        {PubAnalytics? analytics, required bool Function() isVerbose}) =>
+    PubEmbeddableCommand(analytics, isVerbose);
 
 /// Support for the `pub` toplevel command.
 @Deprecated('Use [pubCommand] instead.')
diff --git a/lib/src/authentication/client.dart b/lib/src/authentication/client.dart
index 3de5b2e..a6001ec 100644
--- a/lib/src/authentication/client.dart
+++ b/lib/src/authentication/client.dart
@@ -2,15 +2,12 @@
 // 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.
 
-// @dart=2.11
-
 import 'dart:io';
 
 import 'package:collection/collection.dart';
 import 'package:http/http.dart' as http;
 import 'package:http_parser/http_parser.dart';
 
-import '../exceptions.dart';
 import '../http.dart';
 import '../log.dart' as log;
 import '../system_cache.dart';
@@ -24,14 +21,17 @@
   /// Constructs Http client wrapper that injects `authorization` header to
   /// requests and handles authentication errors.
   ///
-  /// [credential] might be `null`. In that case `authorization` header will not
+  /// [_credential] might be `null`. In that case `authorization` header will not
   /// be injected to requests.
-  _AuthenticatedClient(this._inner, this.credential);
+  _AuthenticatedClient(this._inner, this._credential);
 
   final http.BaseClient _inner;
 
   /// Authentication scheme that could be used for authenticating requests.
-  final Credential credential;
+  final Credential? _credential;
+
+  /// Detected that [_credential] are invalid, happens when server responds 401.
+  bool _detectInvalidCredentials = false;
 
   @override
   Future<http.StreamedResponse> send(http.BaseRequest request) async {
@@ -42,20 +42,21 @@
     // to given serverBaseUrl. Otherwise credential leaks might ocurr when
     // archive_url hosted on 3rd party server that should not receive
     // credentials of the first party.
-    if (credential != null &&
-        credential.canAuthenticate(request.url.toString())) {
+    if (_credential != null &&
+        _credential!.canAuthenticate(request.url.toString())) {
       request.headers[HttpHeaders.authorizationHeader] =
-          await credential.getAuthorizationHeaderValue();
+          await _credential!.getAuthorizationHeaderValue();
     }
 
     try {
       final response = await _inner.send(request);
       if (response.statusCode == 401) {
+        _detectInvalidCredentials = true;
         _throwAuthException(response);
       }
       return response;
     } on PubHttpException catch (e) {
-      if (e.response?.statusCode == 403) {
+      if (e.response.statusCode == 403) {
         _throwAuthException(e.response);
       }
       rethrow;
@@ -68,16 +69,18 @@
   ///
   /// [RFC]: https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
   void _throwAuthException(http.BaseResponse response) {
-    String serverMessage;
+    String? serverMessage;
     if (response.headers.containsKey(HttpHeaders.wwwAuthenticateHeader)) {
       try {
-        final header = response.headers[HttpHeaders.wwwAuthenticateHeader];
+        final header = response.headers[HttpHeaders.wwwAuthenticateHeader]!;
         final challenge = AuthenticationChallenge.parseHeader(header)
             .firstWhereOrNull((challenge) =>
                 challenge.scheme == 'bearer' &&
                 challenge.parameters['realm'] == 'pub' &&
                 challenge.parameters['message'] != null);
-        serverMessage = challenge?.parameters['message'];
+        if (challenge != null) {
+          serverMessage = challenge.parameters['message'];
+        }
       } on FormatException {
         // Ignore errors might be caused when parsing invalid header values
       }
@@ -101,7 +104,7 @@
   const AuthenticationException(this.statusCode, this.serverMessage);
 
   final int statusCode;
-  final String serverMessage;
+  final String? serverMessage;
 
   @override
   String toString() {
@@ -124,31 +127,17 @@
   Future<T> Function(http.Client) fn,
 ) async {
   final credential = systemCache.tokenStore.findCredential(hostedUrl);
-  final http.Client client = _AuthenticatedClient(httpClient, credential);
+  final client = _AuthenticatedClient(httpClient, credential);
 
   try {
     return await fn(client);
-  } on AuthenticationException catch (error) {
-    String message;
-
-    if (error.statusCode == 401) {
-      if (systemCache.tokenStore.removeCredential(hostedUrl)) {
+  } finally {
+    if (client._detectInvalidCredentials) {
+      // try to remove the credential, if we detected that it is invalid!
+      final removed = systemCache.tokenStore.removeCredential(hostedUrl);
+      if (removed) {
         log.warning('Invalid token for $hostedUrl deleted.');
       }
-      message = '$hostedUrl package repository requested authentication! '
-          'You can provide credential using:\n'
-          '    pub token add $hostedUrl';
     }
-    if (error.statusCode == 403) {
-      message = 'Insufficient permissions to the resource in $hostedUrl '
-          'package repository. You can modify credential using:\n'
-          '    pub token add $hostedUrl';
-    }
-
-    if (error.serverMessage?.isNotEmpty == true) {
-      message += '\n${error.serverMessage}';
-    }
-
-    throw DataException(message);
   }
 }
diff --git a/lib/src/authentication/credential.dart b/lib/src/authentication/credential.dart
index a44cc91..e1dfc79 100644
--- a/lib/src/authentication/credential.dart
+++ b/lib/src/authentication/credential.dart
@@ -2,12 +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.
 
-// @dart=2.11
-
 import 'dart:io';
 
-import 'package:meta/meta.dart';
-
 import '../exceptions.dart';
 import '../source/hosted.dart';
 
@@ -27,10 +23,10 @@
 class Credential {
   /// Internal constructor that's only used by [fromJson].
   Credential._internal({
-    @required this.url,
-    @required this.unknownFields,
-    @required this.token,
-    @required this.env,
+    required this.url,
+    required this.unknownFields,
+    required this.token,
+    required this.env,
   });
 
   /// Create credential that stores clear text token.
@@ -62,7 +58,7 @@
     /// doesn't contains [key].
     ///
     /// Throws [FormatException] if value type is not [String].
-    String _string(String key) {
+    String? _string(String key) {
       if (json.containsKey(key)) {
         if (json[key] is! String) {
           throw FormatException('Provided $key value should be string');
@@ -84,10 +80,10 @@
   final Uri url;
 
   /// Authentication token value
-  final String token;
+  final String? token;
 
   /// Environment variable name that stores token value
-  final String env;
+  final String? env;
 
   /// Unknown fields found in pub-tokens.json. The fields might be created by the
   /// future version of pub tool. We don't want to override them when using the
@@ -114,13 +110,14 @@
   Future<String> getAuthorizationHeaderValue() {
     if (!isValid()) {
       throw DataException(
-        'Saved credential for $url pub repository is not supported by current '
-        'version of Dart SDK.',
+        'Saved credential for "$url" pub repository is not supported by '
+        'current version of Dart SDK.',
       );
     }
 
-    if (env != null) {
-      final value = Platform.environment[env];
+    final environment = env;
+    if (environment != null) {
+      final value = Platform.environment[environment];
       if (value == null) {
         throw DataException(
           'Saved credential for "$url" pub repository requires environment '
diff --git a/lib/src/authentication/token_store.dart b/lib/src/authentication/token_store.dart
index 24f70fc..3ef948d 100644
--- a/lib/src/authentication/token_store.dart
+++ b/lib/src/authentication/token_store.dart
@@ -2,12 +2,12 @@
 // 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.
 
-// @dart=2.11
-
 import 'dart:convert';
+import 'dart:io';
 
 import 'package:path/path.dart' as path;
 
+import '../exceptions.dart';
 import '../io.dart';
 import '../log.dart' as log;
 import 'credential.dart';
@@ -17,7 +17,7 @@
   TokenStore(this.configDir);
 
   /// Cache directory.
-  final String configDir;
+  final String? configDir;
 
   /// List of saved authentication tokens.
   ///
@@ -28,9 +28,9 @@
   /// Reads "pub-tokens.json" and parses / deserializes it into list of
   /// [Credential].
   List<Credential> _loadCredentials() {
-    final result = List<Credential>.empty(growable: true);
+    final result = <Credential>[];
     final path = _tokensFile;
-    if (!fileExists(path)) {
+    if (path == null || !fileExists(path)) {
       return result;
     }
 
@@ -90,11 +90,20 @@
     return result;
   }
 
+  Never missingConfigDir() {
+    final variable = Platform.isWindows ? '%APPDATA%' : r'$HOME';
+    throw DataException('No config dir found. Check that $variable is set');
+  }
+
   /// Writes [credentials] into "pub-tokens.json".
   void _saveCredentials(List<Credential> credentials) {
-    ensureDir(path.dirname(_tokensFile));
+    final tokensFile = _tokensFile;
+    if (tokensFile == null) {
+      missingConfigDir();
+    }
+    ensureDir(path.dirname(tokensFile));
     writeTextFile(
-        _tokensFile,
+        tokensFile,
         jsonEncode(<String, dynamic>{
           'version': 1,
           'hosted': credentials.map((it) => it.toJson()).toList(),
@@ -134,8 +143,8 @@
 
   /// Returns [Credential] for authenticating given [hostedUrl] or `null` if no
   /// matching credential is found.
-  Credential findCredential(Uri hostedUrl) {
-    Credential matchedCredential;
+  Credential? findCredential(Uri hostedUrl) {
+    Credential? matchedCredential;
     for (final credential in credentials) {
       if (credential.url == hostedUrl && credential.isValid()) {
         if (matchedCredential == null) {
@@ -161,10 +170,22 @@
 
   /// Deletes pub-tokens.json file from the disk.
   void deleteTokensFile() {
-    deleteEntry(_tokensFile);
-    log.message('pub-tokens.json is deleted.');
+    final tokensFile = _tokensFile;
+    if (tokensFile == null) {
+      missingConfigDir();
+    } else if (!fileExists(tokensFile)) {
+      log.message('No credentials file found at "$tokensFile"');
+    } else {
+      deleteEntry(tokensFile);
+      log.message('pub-tokens.json is deleted.');
+    }
   }
 
   /// Full path to the "pub-tokens.json" file.
-  String get _tokensFile => path.join(configDir, 'pub-tokens.json');
+  ///
+  /// `null` if no config directory could be found.
+  String? get _tokensFile {
+    var dir = configDir;
+    return dir == null ? null : path.join(dir, 'pub-tokens.json');
+  }
 }
diff --git a/lib/src/command.dart b/lib/src/command.dart
index cd4a524..4d02c8a 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -2,15 +2,15 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
 import 'package:args/args.dart';
 import 'package:args/command_runner.dart';
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:http/http.dart' as http;
 import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
 
 import 'authentication/token_store.dart';
 import 'command_runner.dart';
@@ -46,15 +46,27 @@
 /// of subcommands. Only leaf commands are ever actually invoked. If a command
 /// has subcommands, then one of those must always be chosen.
 abstract class PubCommand extends Command<int> {
-  String get directory => argResults['directory'] ?? _pubTopLevel.directory;
+  @override
+  ArgResults get argResults {
+    final a = super.argResults;
+    if (a == null) {
+      throw StateError(
+          'argResults cannot be used before Command.run is called.');
+    }
+    return a;
+  }
 
-  SystemCache get cache => _cache ??= SystemCache(isOffline: isOffline);
+  String get directory =>
+      (argResults.options.contains('directory')
+          ? argResults['directory']
+          : null) ??
+      _pubTopLevel.directory;
 
-  SystemCache _cache;
+  late final SystemCache cache = SystemCache(isOffline: isOffline);
 
   GlobalPackages get globals => _globals ??= GlobalPackages(cache);
 
-  GlobalPackages _globals;
+  GlobalPackages? _globals;
 
   TokenStore get tokenStore => cache.tokenStore;
 
@@ -62,12 +74,10 @@
   ///
   /// This will load the pubspec and fail with an error if the current directory
   /// is not a package.
-  Entrypoint get entrypoint => _entrypoint ??= Entrypoint(directory, cache);
-
-  Entrypoint _entrypoint;
+  late final Entrypoint entrypoint = Entrypoint(directory, cache);
 
   /// The URL for web documentation for this command.
-  String get docUrl => null;
+  String? get docUrl => null;
 
   /// Override this and return `false` to disallow trailing options from being
   /// parsed after a non-option argument is parsed.
@@ -76,11 +86,9 @@
   // Lazily initialize the parser because the superclass constructor requires
   // it but we want to initialize it based on [allowTrailingOptions].
   @override
-  ArgParser get argParser => _argParser ??= ArgParser(
+  late final ArgParser argParser = ArgParser(
       allowTrailingOptions: allowTrailingOptions, usageLineLength: lineLength);
 
-  ArgParser _argParser;
-
   /// Override this to use offline-only sources instead of hitting the network.
   ///
   /// This will only be called before the [SystemCache] is created. After that,
@@ -88,7 +96,7 @@
   bool get isOffline => false;
 
   @override
-  String get usageFooter {
+  String? get usageFooter {
     if (docUrl == null) return null;
     return 'See $docUrl for detailed documentation.';
   }
@@ -98,35 +106,41 @@
 
   /// The first command in the command chain.
   Command get _topCommand {
-    var command = this;
-    while (command.parent != null) {
+    Command current = this;
+    while (true) {
+      var parent = current.parent;
+      if (parent == null) return current;
+      current = parent;
+    }
+  }
+
+  PubEmbeddableCommand? get _pubEmbeddableCommand {
+    Command? command = this;
+    while (command is! PubEmbeddableCommand) {
+      if (command == null) {
+        return null;
+      }
       command = command.parent;
     }
+
     return command;
   }
 
-  PubEmbeddableCommand get _pubEmbeddableCommand {
-    var command = this;
-    while (command != null && command is! PubEmbeddableCommand) {
-      command = command.parent;
-    }
-    return command;
-  }
+  PubTopLevel get _pubTopLevel =>
+      _pubEmbeddableCommand ?? runner as PubTopLevel;
 
-  PubTopLevel get _pubTopLevel {
-    return _pubEmbeddableCommand ?? (runner as PubCommandRunner);
-  }
+  PubAnalytics? get analytics => _pubEmbeddableCommand?.analytics;
 
   @override
   String get invocation {
-    var command = this;
+    PubCommand? command = this;
     var names = [];
     do {
-      names.add(command.name);
-      command = command.parent;
+      names.add(command?.name);
+      command = command?.parent as PubCommand?;
     } while (command != null);
     return [
-      runner.executableName,
+      runner!.executableName,
       ...names.reversed,
       argumentsDescription,
     ].join(' ');
@@ -143,7 +157,7 @@
   /// when exiting successfully.
   ///
   /// This should only be modified by [overrideExitCode].
-  int _exitCodeOverride;
+  int? _exitCodeOverride;
 
   /// Override the exit code that would normally be used when exiting
   /// successfully. Intended to be used by subcommands like `run` that wishes
@@ -161,25 +175,40 @@
   @nonVirtual
   FutureOr<int> run() async {
     computeCommand(_pubTopLevel.argResults);
-    if (_pubTopLevel.trace) {
-      log.recordTranscript();
-    }
+
     log.verbosity = _pubTopLevel.verbosity;
     log.fine('Pub ${sdk.version}');
 
+    var crashed = false;
     try {
-      await captureErrors(runProtected,
+      await captureErrors<void>(() async => runProtected(),
           captureStackChains: _pubTopLevel.captureStackChains);
       if (_exitCodeOverride != null) {
-        return _exitCodeOverride;
+        return _exitCodeOverride!;
       }
       return exit_codes.SUCCESS;
     } catch (error, chain) {
       log.exception(error, chain);
 
       if (_pubTopLevel.trace) {
-        log.dumpTranscript();
+        log.dumpTranscriptToStdErr();
       } else if (!isUserFacingException(error)) {
+        log.error('''
+This is an unexpected error. The full log and other details are collected in:
+
+    $transcriptPath
+
+Consider creating an issue on https://github.com/dart-lang/pub/issues/new
+and attaching the relevant parts of that log file.
+''');
+        crashed = true;
+      }
+      return _chooseExitCode(error);
+    } finally {
+      final verbose = _pubTopLevel.verbosity == log.Verbosity.all;
+
+      // Write the whole log transcript to file.
+      if (verbose || crashed) {
         // Escape the argument for users to copy-paste in bash.
         // Wrap with single quotation, and use '\'' to insert single quote, as
         // long as we have no spaces this doesn't create a new argument.
@@ -187,29 +216,36 @@
             RegExp(r'^[a-zA-Z0-9-_]+$').stringMatch(x) == null
                 ? "'${x.replaceAll("'", r"'\''")}'"
                 : x;
-        log.error("""
-This is an unexpected error. Please run
 
-    dart pub --trace ${_topCommand.name} ${_topCommand.argResults.arguments.map(protectArgument).join(' ')}
+        late final Entrypoint? e;
+        try {
+          e = entrypoint;
+        } on ApplicationException {
+          e = null;
+        }
+        log.dumpTranscriptToFile(
+          transcriptPath,
+          'dart pub ${_topCommand.argResults!.arguments.map(protectArgument).join(' ')}',
+          e,
+        );
 
-and include the logs in an issue on https://github.com/dart-lang/pub/issues/new
-""");
+        if (!crashed) {
+          log.message('Logs written to $transcriptPath.');
+        }
       }
-      return _chooseExitCode(error);
-    } finally {
       httpClient.close();
     }
   }
 
   /// Returns the appropriate exit code for [exception], falling back on 1 if no
   /// appropriate exit code could be found.
-  int _chooseExitCode(exception) {
+  int _chooseExitCode(Object exception) {
     if (exception is SolveFailure) {
       var packageNotFound = exception.packageNotFound;
       if (packageNotFound != null) exception = packageNotFound;
     }
     while (exception is WrappedException && exception.innerError is Exception) {
-      exception = exception.innerError;
+      exception = exception.innerError!;
     }
 
     if (exception is HttpException ||
@@ -244,7 +280,7 @@
     log.message(usage);
   }
 
-  static String _command;
+  static String? _command;
 
   /// Returns the nested name of the command that's currently being run.
   /// Examples:
@@ -258,10 +294,10 @@
   ///
   /// For top-level commands, if an alias is used, the primary command name is
   /// returned. For instance `install` becomes `get`.
-  static String get command => _command;
+  static late final String command = _command ?? '';
 
   static void computeCommand(ArgResults argResults) {
-    var list = <String>[];
+    var list = <String?>[];
     for (var command = argResults.command;
         command != null;
         command = command.command) {
@@ -269,9 +305,8 @@
 
       if (list.isEmpty) {
         // this is a top-level command
-        final rootCommand = pubCommandAliases.entries.singleWhere(
-            (element) => element.value.contains(command.name),
-            orElse: () => null);
+        final rootCommand = pubCommandAliases.entries.singleWhereOrNull(
+            (element) => element.value.contains(command!.name));
         if (rootCommand != null) {
           commandName = rootCommand.key;
         }
@@ -280,13 +315,17 @@
     }
     _command = list.join(' ');
   }
+
+  String get transcriptPath {
+    return p.join(cache.rootDir, 'log', 'pub_log.txt');
+  }
 }
 
 abstract class PubTopLevel {
   bool get captureStackChains;
   log.Verbosity get verbosity;
   bool get trace;
-  String get directory;
+  String? get directory;
 
   /// The argResults from the level of parsing of the 'pub' command.
   ArgResults get argResults;
diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index 728f8ea..3557104 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
@@ -14,6 +13,7 @@
 import '../exceptions.dart';
 import '../git.dart';
 import '../io.dart';
+import '../language_version.dart';
 import '../log.dart' as log;
 import '../package.dart';
 import '../package_name.dart';
@@ -34,9 +34,10 @@
   @override
   String get name => 'add';
   @override
-  String get description => 'Add a dependency to pubspec.yaml.';
+  String get description => 'Add dependencies to pubspec.yaml.';
   @override
-  String get argumentsDescription => '<package>[:<constraint>] [options]';
+  String get argumentsDescription =>
+      '<package>[:<constraint>] [<package2>[:<constraint2>]...] [options]';
   @override
   String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-add';
   @override
@@ -44,29 +45,34 @@
 
   bool get isDev => argResults['dev'];
   bool get isDryRun => argResults['dry-run'];
-  String get gitUrl => argResults['git-url'];
-  String get gitPath => argResults['git-path'];
-  String get gitRef => argResults['git-ref'];
-  String get hostUrl => argResults['hosted-url'];
-  String get path => argResults['path'];
-  String get sdk => argResults['sdk'];
+  String? get gitUrl => argResults['git-url'];
+  String? get gitPath => argResults['git-path'];
+  String? get gitRef => argResults['git-ref'];
+  String? get hostUrl => argResults['hosted-url'];
+  String? get path => argResults['path'];
+  String? get sdk => argResults['sdk'];
 
   bool get hasGitOptions => gitUrl != null || gitRef != null || gitPath != null;
   bool get hasHostOptions => hostUrl != null;
 
+  bool get isHosted => !hasGitOptions && path == null && path == null;
+
   AddCommand() {
     argParser.addFlag('dev',
         abbr: 'd',
         negatable: false,
-        help: 'Adds package to the development dependencies instead.');
+        help: 'Adds to the development dependencies instead.');
 
     argParser.addOption('git-url', help: 'Git URL of the package');
     argParser.addOption('git-ref',
         help: 'Git branch or commit to be retrieved');
     argParser.addOption('git-path', help: 'Path of git package in repository');
     argParser.addOption('hosted-url', help: 'URL of package host server');
-    argParser.addOption('path', help: 'Local path');
-    argParser.addOption('sdk', help: 'SDK source for package');
+    argParser.addOption('path', help: 'Add package from local path');
+    argParser.addOption('sdk',
+        help: 'add package from SDK source',
+        allowed: ['flutter'],
+        valueHelp: '[flutter]');
     argParser.addFlag(
       'example',
       help:
@@ -85,25 +91,32 @@
     argParser.addFlag('precompile',
         help: 'Build executables in immediate dependencies.');
     argParser.addOption('directory',
-        abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
+        abbr: 'C', help: 'Run this in the directory <dir>.', valueHelp: 'dir');
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
   }
 
   @override
   Future<void> runProtected() async {
     if (argResults.rest.isEmpty) {
-      usageException('Must specify a package to be added.');
-    } else if (argResults.rest.length > 1) {
-      usageException('Takes only a single argument.');
+      usageException('Must specify at least one package to be added.');
+    } else if (argResults.rest.length > 1 && gitUrl != null) {
+      usageException('Can only add a single git package at a time.');
+    } else if (argResults.rest.length > 1 && path != null) {
+      usageException('Can only add a single local package at a time.');
+    }
+    final languageVersion = entrypoint.root.pubspec.languageVersion;
+    final updates =
+        argResults.rest.map((p) => _parsePackage(p, languageVersion)).toList();
+
+    var updatedPubSpec = entrypoint.root.pubspec;
+    for (final update in updates) {
+      /// Perform version resolution in-memory.
+      updatedPubSpec =
+          await _addPackageToPubspec(updatedPubSpec, update.packageRange);
     }
 
-    final packageInformation = _parsePackage(argResults.rest.first);
-    final package = packageInformation.first;
-
-    /// Perform version resolution in-memory.
-    final updatedPubSpec =
-        await _addPackageToPubspec(entrypoint.root.pubspec, package);
-
-    SolveResult solveResult;
+    late SolveResult solveResult;
 
     try {
       /// Use [SolveType.UPGRADE] to solve for the highest version of [package]
@@ -112,9 +125,11 @@
       /// that a resolution exists before we update pubspec.yaml.
       // TODO(sigurdm): We should really use a spinner here.
       solveResult = await resolveVersions(
-          SolveType.UPGRADE, cache, Package.inMemory(updatedPubSpec));
+          SolveType.upgrade, cache, Package.inMemory(updatedPubSpec));
     } on GitException {
-      dataError('Unable to resolve package "${package.name}" with the given '
+      final packageRange = updates.first.packageRange;
+      dataError(
+          'Unable to resolve package "${packageRange.name}" with the given '
           'git parameters.');
     } on SolveFailure catch (e) {
       dataError(e.message);
@@ -123,56 +138,63 @@
       dataError(e.message);
     }
 
-    final resultPackage = solveResult.packages
-        .firstWhere((packageId) => packageId.name == package.name);
+    /// Verify the results for each package.
+    for (final update in updates) {
+      final packageRange = update.packageRange;
+      final name = packageRange.name;
+      final resultPackage = solveResult.packages
+          .firstWhere((packageId) => packageId.name == name);
 
-    /// Assert that [resultPackage] is within the original user's expectations.
-    if (package.constraint != null &&
-        !package.constraint.allows(resultPackage.version)) {
-      if (updatedPubSpec.dependencyOverrides != null &&
-          updatedPubSpec.dependencyOverrides.isNotEmpty) {
-        dataError(
-            '"${package.name}" resolved to "${resultPackage.version}" which '
-            'does not satisfy constraint "${package.constraint}". This could be '
-            'caused by "dependency_overrides".');
+      /// Assert that [resultPackage] is within the original user's expectations.
+      var constraint = packageRange.constraint;
+      if (!constraint.allows(resultPackage.version)) {
+        var dependencyOverrides = updatedPubSpec.dependencyOverrides;
+        if (dependencyOverrides.isNotEmpty) {
+          dataError('"$name" resolved to "${resultPackage.version}" which '
+              'does not satisfy constraint "$constraint". This could be '
+              'caused by "dependency_overrides".');
+        }
+        dataError('"$name" resolved to "${resultPackage.version}" which '
+            'does not satisfy constraint "$constraint".');
       }
-      dataError(
-          '"${package.name}" resolved to "${resultPackage.version}" which '
-          'does not satisfy constraint "${package.constraint}".');
     }
-
     if (isDryRun) {
       /// Even if it is a dry run, run `acquireDependencies` so that the user
       /// gets a report on the other packages that might change version due
       /// to this new dependency.
       final newRoot = Package.inMemory(updatedPubSpec);
 
-      // TODO(jonasfj): Stop abusing Entrypoint.global for dry-run output
-      await Entrypoint.global(newRoot, entrypoint.lockFile, cache,
-              solveResult: solveResult)
-          .acquireDependencies(
-        SolveType.GET,
-        dryRun: true,
-        precompile: argResults['precompile'],
-      );
+      await Entrypoint.inMemory(newRoot, cache,
+              solveResult: solveResult, lockFile: entrypoint.lockFile)
+          .acquireDependencies(SolveType.get,
+              dryRun: true,
+              precompile: argResults['precompile'],
+              analytics: analytics,
+              generateDotPackages: false);
     } else {
       /// Update the `pubspec.yaml` before calling [acquireDependencies] to
       /// ensure that the modification timestamp on `pubspec.lock` and
       /// `.dart_tool/package_config.json` is newer than `pubspec.yaml`,
       /// ensuring that [entrypoint.assertUptoDate] will pass.
-      _updatePubspec(resultPackage, packageInformation, isDev);
+      _updatePubspec(solveResult.packages, updates, isDev);
 
       /// Create a new [Entrypoint] since we have to reprocess the updated
       /// pubspec file.
       final updatedEntrypoint = Entrypoint(directory, cache);
-      await updatedEntrypoint.acquireDependencies(SolveType.GET,
-          precompile: argResults['precompile']);
+      await updatedEntrypoint.acquireDependencies(
+        SolveType.get,
+        precompile: argResults['precompile'],
+        analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
+      );
 
       if (argResults['example'] && entrypoint.example != null) {
-        await entrypoint.example.acquireDependencies(
-          SolveType.GET,
+        await entrypoint.example!.acquireDependencies(
+          SolveType.get,
           precompile: argResults['precompile'],
           onlyReportSuccessOrFailure: true,
+          analytics: analytics,
+          generateDotPackages: argResults['legacy-packages-file'],
         );
       }
     }
@@ -272,9 +294,7 @@
   ///
   /// If any of the other git options are defined when `--git-url` is not
   /// defined, an error will be thrown.
-  Pair<PackageRange, dynamic> _parsePackage(String package) {
-    ArgumentError.checkNotNull(package, 'package');
-
+  _ParseResult _parsePackage(String package, LanguageVersion languageVersion) {
     final _conflictingFlagSets = [
       ['git-url', 'git-ref', 'git-path'],
       ['hosted-url'],
@@ -287,7 +307,7 @@
       final conflictingFlag = _conflictingFlagSets
           .where((s) => !s.contains(flag))
           .expand((s) => s)
-          .firstWhere(argResults.wasParsed, orElse: () => null);
+          .firstWhereOrNull(argResults.wasParsed);
       if (conflictingFlag != null) {
         usageException(
             'Packages can only have one source, "pub add" flags "--$flag" and '
@@ -314,7 +334,7 @@
 
     /// We want to allow for [constraint] to take on a `null` value here to
     /// preserve the fact that the user did not specify a constraint.
-    VersionConstraint constraint;
+    VersionConstraint? constraint;
 
     try {
       constraint = splitPackage.length == 2
@@ -326,6 +346,7 @@
 
     /// Determine the relevant [packageRange] and [pubspecInformation] depending
     /// on the type of package.
+    var path = this.path;
     if (hasGitOptions) {
       dynamic git;
 
@@ -334,7 +355,7 @@
       }
       Uri parsed;
       try {
-        parsed = Uri.parse(gitUrl);
+        parsed = Uri.parse(gitUrl!);
       } on FormatException catch (e) {
         usageException('The --git-url must be a valid url: ${e.message}.');
       }
@@ -357,7 +378,7 @@
         git.removeWhere((key, value) => value == null);
       }
 
-      packageRange = cache.sources['git']
+      packageRange = cache.sources.git
           .parseRef(packageName, git, containingPath: entrypoint.pubspecPath)
           .withConstraint(constraint ?? VersionConstraint.any);
       pubspecInformation = {'git': git};
@@ -366,28 +387,38 @@
           ? PathSource.relativePathWithPosixSeparators(
               p.relative(path, from: entrypoint.root.dir))
           : path;
-      packageRange = cache.sources['path']
+      packageRange = cache.sources.path
           .parseRef(packageName, relativeToEntryPoint,
               containingPath: entrypoint.pubspecPath)
           .withConstraint(constraint ?? VersionConstraint.any);
       pubspecInformation = {'path': relativeToEntryPoint};
     } else if (sdk != null) {
-      packageRange = cache.sources['sdk']
+      packageRange = cache.sources.sdk
           .parseRef(packageName, sdk)
           .withConstraint(constraint ?? VersionConstraint.any);
       pubspecInformation = {'sdk': sdk};
     } else {
-      final hostInfo =
-          hasHostOptions ? {'url': hostUrl, 'name': packageName} : null;
-
-      if (hostInfo == null) {
-        pubspecInformation = constraint?.toString();
+      // Hosted
+      final Object? hostInfo;
+      if (hasHostOptions) {
+        hostInfo = languageVersion.supportsShorterHostedSyntax
+            ? hostUrl
+            : {'url': hostUrl, 'name': packageName};
+        pubspecInformation = {
+          'hosted': hostInfo,
+        };
       } else {
-        pubspecInformation = {'hosted': hostInfo};
+        hostInfo = null;
+        pubspecInformation = constraint?.toString();
       }
 
-      packageRange = PackageRange(packageName, cache.sources['hosted'],
-          constraint ?? VersionConstraint.any, hostInfo ?? packageName);
+      packageRange = cache.hosted.source
+          .parseRef(
+            packageName,
+            hostInfo,
+            languageVersion: entrypoint.root.pubspec.languageVersion,
+          )
+          .withConstraint(constraint ?? VersionConstraint.any);
     }
 
     if (pubspecInformation is Map && constraint != null) {
@@ -400,65 +431,64 @@
       };
     }
 
-    return Pair(packageRange, pubspecInformation);
+    return _ParseResult(packageRange, pubspecInformation);
   }
 
   /// Writes the changes to the pubspec file.
-  void _updatePubspec(PackageId resultPackage,
-      Pair<PackageRange, dynamic> packageInformation, bool isDevelopment) {
-    ArgumentError.checkNotNull(resultPackage, 'resultPackage');
-    ArgumentError.checkNotNull(packageInformation, 'pubspecInformation');
-
-    final package = packageInformation.first;
-    var pubspecInformation = packageInformation.last;
-
-    if ((sdk != null || hasHostOptions) &&
-        pubspecInformation is Map &&
-        pubspecInformation['version'] == null) {
-      /// We cannot simply assign the value of version since it is likely that
-      /// [pubspecInformation] takes on the type
-      /// [Map<String, Map<String, String>>]
-      pubspecInformation = {
-        ...pubspecInformation,
-        'version': '^${resultPackage.version}'
-      };
-    }
-
-    final dependencyKey = isDevelopment ? 'dev_dependencies' : 'dependencies';
-    final packagePath = [dependencyKey, package.name];
-
+  void _updatePubspec(List<PackageId> resultPackages,
+      List<_ParseResult> updates, bool isDevelopment) {
     final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
     log.io('Reading ${entrypoint.pubspecPath}.');
     log.fine('Contents:\n$yamlEditor');
 
-    /// Handle situations where the user might not have the dependencies or
-    /// dev_dependencies map.
-    if (yamlEditor.parseAt([dependencyKey], orElse: () => null)?.value ==
-        null) {
-      yamlEditor.update([dependencyKey],
-          {package.name: pubspecInformation ?? '^${resultPackage.version}'});
-    } else {
-      yamlEditor.update(
-          packagePath, pubspecInformation ?? '^${resultPackage.version}');
-    }
+    for (final update in updates) {
+      final packageRange = update.packageRange;
+      final name = packageRange.name;
+      final resultId = resultPackages.firstWhere((id) => id.name == name);
+      var description = update.description;
 
-    log.fine('Added ${package.name} to "$dependencyKey".');
-
-    /// Remove the package from dev_dependencies if we are adding it to
-    /// dependencies. Refer to [_addPackageToPubspec] for additional discussion.
-    if (!isDevelopment) {
-      final devDependenciesNode =
-          yamlEditor.parseAt(['dev_dependencies'], orElse: () => null);
-
-      if (devDependenciesNode is YamlMap &&
-          devDependenciesNode.containsKey(package.name)) {
-        if (devDependenciesNode.length == 1) {
-          yamlEditor.remove(['dev_dependencies']);
-        } else {
-          yamlEditor.remove(['dev_dependencies', package.name]);
+      if (isHosted) {
+        final inferredConstraint =
+            VersionConstraint.compatibleWith(resultId.version).toString();
+        if (description == null) {
+          description = inferredConstraint;
+        } else if (description is Map && description['version'] == null) {
+          /// We cannot simply assign the value of version since it is likely that
+          /// [description] takes on the type
+          /// [Map<String, Map<String, String>>]
+          description = {...description, 'version': '^${resultId.version}'};
         }
+      }
 
-        log.fine('Removed ${package.name} from "dev_dependencies".');
+      final dependencyKey = isDevelopment ? 'dev_dependencies' : 'dependencies';
+      final packagePath = [dependencyKey, name];
+
+      /// Ensure we have a [dependencyKey] map in the `pubspec.yaml`.
+      if (yamlEditor.parseAt([dependencyKey],
+              orElse: () => YamlScalar.wrap(null)).value ==
+          null) {
+        yamlEditor.update([dependencyKey], {});
+      }
+      yamlEditor.update(packagePath, description);
+
+      log.fine('Added ${packageRange.name} to "$dependencyKey".');
+
+      /// Remove the package from dev_dependencies if we are adding it to
+      /// dependencies. Refer to [_addPackageToPubspec] for additional discussion.
+      if (!isDevelopment) {
+        final devDependenciesNode = yamlEditor
+            .parseAt(['dev_dependencies'], orElse: () => YamlScalar.wrap(null));
+
+        if (devDependenciesNode is YamlMap &&
+            devDependenciesNode.containsKey(name)) {
+          if (devDependenciesNode.length == 1) {
+            yamlEditor.remove(['dev_dependencies']);
+          } else {
+            yamlEditor.remove(['dev_dependencies', name]);
+          }
+
+          log.fine('Removed $name from "dev_dependencies".');
+        }
       }
     }
 
@@ -466,3 +496,9 @@
     writeTextFile(entrypoint.pubspecPath, yamlEditor.toString());
   }
 }
+
+class _ParseResult {
+  PackageRange packageRange;
+  Object? description;
+  _ParseResult(this.packageRange, this.description);
+}
diff --git a/lib/src/command/barback.dart b/lib/src/command/barback.dart
index f059827..d058015 100644
--- a/lib/src/command/barback.dart
+++ b/lib/src/command/barback.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../log.dart' as log;
 import '../utils.dart';
diff --git a/lib/src/command/build.dart b/lib/src/command/build.dart
index 6673722..8c199d6 100644
--- a/lib/src/command/build.dart
+++ b/lib/src/command/build.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'barback.dart';
 
 /// Handles the `build` pub command.
diff --git a/lib/src/command/cache.dart b/lib/src/command/cache.dart
index baa7403..12c4fbf 100644
--- a/lib/src/command/cache.dart
+++ b/lib/src/command/cache.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 import 'cache_add.dart';
 import 'cache_clean.dart';
diff --git a/lib/src/command/cache_add.dart b/lib/src/command/cache_add.dart
index 8dc76b7..e3e64e4 100644
--- a/lib/src/command/cache_add.dart
+++ b/lib/src/command/cache_add.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/command/cache_clean.dart b/lib/src/command/cache_clean.dart
index e5ae0df..42b0f1f 100644
--- a/lib/src/command/cache_clean.dart
+++ b/lib/src/command/cache_clean.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../command_runner.dart';
 import '../io.dart';
diff --git a/lib/src/command/cache_list.dart b/lib/src/command/cache_list.dart
index 9316172..f5ec62e 100644
--- a/lib/src/command/cache_list.dart
+++ b/lib/src/command/cache_list.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import '../command.dart';
diff --git a/lib/src/command/cache_repair.dart b/lib/src/command/cache_repair.dart
index 3658531..74968be 100644
--- a/lib/src/command/cache_repair.dart
+++ b/lib/src/command/cache_repair.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
@@ -25,6 +23,8 @@
 
   @override
   Future<void> runProtected() async {
+    // Delete any eventual temp-files left in the cache.
+    cache.deleteTempDir();
     // Repair every cached source.
     final repairResults = (await Future.wait(
             cache.sources.all.map(cache.source).map((source) async {
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index 9926731..8731fe3 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -2,14 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 /// This implements support for dependency-bot style automated upgrades.
 /// It is still work in progress - do not rely on the current output.
 import 'dart:convert';
 import 'dart:io';
 
 import 'package:async/async.dart' show collectBytes;
+import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 import 'package:yaml_edit/yaml_edit.dart';
@@ -24,6 +23,7 @@
 import '../pubspec_utils.dart';
 import '../solver.dart';
 import '../system_cache.dart';
+import '../utils.dart';
 
 class DependencyServicesCommand extends PubCommand {
   @override
@@ -64,16 +64,15 @@
 
     final breakingPubspec = stripVersionUpperBounds(compatiblePubspec);
 
-    final compatiblePackagesResult =
-        await _tryResolve(compatiblePubspec, cache);
+    final compatiblePackagesResult = await _resolve(compatiblePubspec, cache);
 
-    final breakingPackagesResult = await _tryResolve(breakingPubspec, cache);
+    final breakingPackagesResult = await _resolve(breakingPubspec, cache);
 
     // This list will be empty if there is no lock file.
     final currentPackages = fileExists(entrypoint.lockFilePath)
         ? Map<String, PackageId>.from(entrypoint.lockFile.packages)
         : Map<String, PackageId>.fromIterable(
-            await _tryResolve(entrypoint.root.pubspec, cache),
+            await _resolve(entrypoint.root.pubspec, cache),
             key: (e) => e.name);
     currentPackages.remove(entrypoint.root.name);
 
@@ -82,7 +81,7 @@
 
     Future<List<Object>> _computeUpgradeSet(
         Pubspec rootPubspec, PackageId package,
-        {UpgradeType upgradeType}) async {
+        {required UpgradeType upgradeType}) async {
       final lockFile = entrypoint.lockFile;
       final pubspec = upgradeType == UpgradeType.multiBreaking
           ? stripVersionUpperBounds(rootPubspec)
@@ -100,12 +99,14 @@
       }
 
       final resolution = await tryResolveVersions(
-        SolveType.GET,
+        SolveType.get,
         cache,
         Package.inMemory(pubspec),
         lockFile: lockFile,
       );
 
+      if (resolution == null) return [];
+
       return [
         ...resolution.packages.where((r) {
           if (r.name == rootPubspec.name) return false;
@@ -132,12 +133,10 @@
     }
 
     for (final package in currentPackages.values) {
-      final compatibleVersion = compatiblePackagesResult.firstWhere(
-          (element) => element.name == package.name,
-          orElse: () => null);
-      final multiBreakingVersion = breakingPackagesResult.firstWhere(
-          (element) => element.name == package.name,
-          orElse: () => null);
+      final compatibleVersion = compatiblePackagesResult
+          .firstWhereOrNull((element) => element.name == package.name);
+      final multiBreakingVersion = breakingPackagesResult
+          .firstWhereOrNull((element) => element.name == package.name);
       final singleBreakingPubspec = Pubspec(
         compatiblePubspec.name,
         version: compatiblePubspec.version,
@@ -149,24 +148,24 @@
           .toRange()
           .withConstraint(stripUpperBound(package.toRange().constraint));
       final singleBreakingPackagesResult =
-          await _tryResolve(singleBreakingPubspec, cache);
-      final singleBreakingVersion = singleBreakingPackagesResult.firstWhere(
-          (element) => element.name == package.name,
-          orElse: () => null);
+          await _resolve(singleBreakingPubspec, cache);
+      final singleBreakingVersion = singleBreakingPackagesResult
+          .firstWhereOrNull((element) => element.name == package.name);
 
       dependencies.add({
         'name': package.name,
         'version': package.version.toString(),
         'kind': _kindString(compatiblePubspec, package.name),
-        'latest': (await cache.getLatest(package)).version.toString(),
+        'latest': (await cache.getLatest(package))?.version.toString(),
         'constraint': _constraintOf(compatiblePubspec, package.name).toString(),
         if (compatibleVersion != null)
           'compatible': await _computeUpgradeSet(
               compatiblePubspec, compatibleVersion,
               upgradeType: UpgradeType.compatible),
-        'single-breaking': await _computeUpgradeSet(
-            singleBreakingPubspec, singleBreakingVersion,
-            upgradeType: UpgradeType.singleBreaking),
+        if (singleBreakingVersion != null)
+          'single-breaking': await _computeUpgradeSet(
+              singleBreakingPubspec, singleBreakingVersion,
+              upgradeType: UpgradeType.singleBreaking),
         if (multiBreakingVersion != null)
           'multi-breaking': await _computeUpgradeSet(
               breakingPubspec, multiBreakingVersion,
@@ -177,11 +176,9 @@
   }
 }
 
-VersionConstraint _constraintOf(Pubspec pubspec, String packageName) {
-  return (pubspec.dependencies[packageName] ??
-          pubspec.devDependencies[packageName])
-      ?.constraint;
-}
+VersionConstraint? _constraintOf(Pubspec pubspec, String packageName) =>
+    (pubspec.dependencies[packageName] ?? pubspec.devDependencies[packageName])
+        ?.constraint;
 
 String _kindString(Pubspec pubspec, String packageName) {
   return pubspec.dependencies.containsKey(packageName)
@@ -193,15 +190,13 @@
 
 /// Try to solve [pubspec] return [PackageId]s in the resolution or `null` if no
 /// resolution was found.
-Future<List<PackageId>> _tryResolve(Pubspec pubspec, SystemCache cache) async {
-  final solveResult = await tryResolveVersions(
-    SolveType.UPGRADE,
-    cache,
-    Package.inMemory(pubspec),
-  );
-
-  return solveResult?.packages;
-}
+Future<List<PackageId>> _resolve(Pubspec pubspec, SystemCache cache) async =>
+    (await resolveVersions(
+      SolveType.upgrade,
+      cache,
+      Package.inMemory(pubspec),
+    ))
+        .packages;
 
 class DependencyServicesListCommand extends PubCommand {
   @override
@@ -227,7 +222,7 @@
     final currentPackages = fileExists(entrypoint.lockFilePath)
         ? Map<String, PackageId>.from(entrypoint.lockFile.packages)
         : Map<String, PackageId>.fromIterable(
-            await _tryResolve(entrypoint.root.pubspec, cache),
+            await _resolve(entrypoint.root.pubspec, cache),
             key: (e) => e.name);
     currentPackages.remove(entrypoint.root.name);
 
@@ -273,11 +268,25 @@
     YamlEditor(readTextFile(entrypoint.pubspecPath));
     final toApply = <_PackageVersion>[];
     final input = json.decode(utf8.decode(await collectBytes(stdin)));
-    for (final change in input['dependencyChanges']) {
-      toApply.add(_PackageVersion(
-        change['name'],
-        change['version'] != null ? Version.parse(change['version']) : null,
-      ));
+    final changes = input['dependencyChanges'];
+    if (changes is! List) {
+      dataError('The dependencyChanges field must be a list');
+    }
+    for (final change in changes) {
+      final name = change['name'];
+      if (name is! String) {
+        dataError('The "name" field must be a string');
+      }
+      final version = change['version'];
+      if (version is! String?) {
+        dataError('The "version" field must be a string');
+      }
+      toApply.add(
+        _PackageVersion(
+          name,
+          version != null ? Version.parse(version) : null,
+        ),
+      );
     }
 
     final pubspec = entrypoint.root.pubspec;
@@ -301,7 +310,7 @@
               VersionConstraint.compatibleWith(targetVersion).toString());
         }
 
-        if (lockFile != null) {
+        if (lockFileEditor != null) {
           if (lockFileYaml['packages'].containsKey(targetPackage)) {
             lockFileEditor.update(['packages', targetPackage, 'version'],
                 targetVersion.toString());
@@ -312,14 +321,18 @@
     if (pubspecEditor.edits.isNotEmpty) {
       writeTextFile(entrypoint.pubspecPath, pubspecEditor.toString());
     }
-    if (lockFile != null && lockFileEditor.edits.isNotEmpty) {
+    if (lockFileEditor != null && lockFileEditor.edits.isNotEmpty) {
       writeTextFile(entrypoint.lockFilePath, lockFileEditor.toString());
     }
     await log.warningsOnlyUnlessTerminal(
       () => () async {
         // This will fail if the new configuration does not resolve.
-        await Entrypoint(directory, cache)
-            .acquireDependencies(SolveType.GET, dryRun: true);
+        await Entrypoint(directory, cache).acquireDependencies(
+          SolveType.get,
+          dryRun: true,
+          analytics: null,
+          generateDotPackages: false,
+        );
       },
     );
     // Dummy message.
@@ -329,6 +342,6 @@
 
 class _PackageVersion {
   String name;
-  Version version;
+  Version? version;
   _PackageVersion(this.name, this.version);
 }
diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 1742701..79463fc 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:collection';
 import 'dart:convert';
 
@@ -37,7 +35,8 @@
       AnalysisContextManager();
 
   /// The [StringBuffer] used to accumulate the output.
-  StringBuffer _buffer;
+  // TODO(sigurdm): use a local variable for this.
+  final _buffer = StringBuffer();
 
   /// Whether to include dev dependencies.
   bool get _includeDev => argResults['dev'];
@@ -67,8 +66,7 @@
   Future<void> runProtected() async {
     // Explicitly Run this in the directorycase we don't access `entrypoint.packageGraph`.
     entrypoint.assertUpToDate();
-
-    _buffer = StringBuffer();
+    _buffer.clear();
 
     if (argResults['json']) {
       if (argResults.wasParsed('dev')) {
@@ -89,7 +87,7 @@
         final current = toVisit.removeLast();
         if (visited.contains(current)) continue;
         visited.add(current);
-        final currentPackage = entrypoint.packageGraph.packages[current];
+        final currentPackage = entrypoint.packageGraph.packages[current]!;
         final next = (current == entrypoint.root.name
                 ? entrypoint.root.immediateDependencies
                 : currentPackage.dependencies)
@@ -121,7 +119,7 @@
           ...entrypoint.root.immediateDependencies.keys
               .map((name) => entrypoint.packageGraph.packages[name])
         ])
-          ...package.executableNames.map((name) => package == entrypoint.root
+          ...package!.executableNames.map((name) => package == entrypoint.root
               ? ':$name'
               : (package.name == name ? name : '${package.name}:$name'))
       ];
@@ -331,7 +329,6 @@
     if (package != null) return package;
     dataError('The pubspec.yaml file has changed since the pubspec.lock file '
         'was generated, please run "$topLevelProgram pub get" again.');
-    return null;
   }
 
   /// Outputs all executables reachable from [entrypoint].
@@ -342,7 +339,7 @@
               ? entrypoint.root.immediateDependencies
               : entrypoint.root.dependencies)
           .keys
-          .map((name) => entrypoint.packageGraph.packages[name])
+          .map((name) => entrypoint.packageGraph.packages[name]!)
     ];
 
     for (var package in packages) {
diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart
index 2d0840a..fce5ef3 100644
--- a/lib/src/command/downgrade.dart
+++ b/lib/src/command/downgrade.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
@@ -44,6 +42,8 @@
 
     argParser.addOption('directory',
         abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
   }
 
   @override
@@ -53,16 +53,24 @@
           'The --packages-dir flag is no longer used and does nothing.'));
     }
     var dryRun = argResults['dry-run'];
+
     await entrypoint.acquireDependencies(
-      SolveType.DOWNGRADE,
+      SolveType.downgrade,
       unlock: argResults.rest,
       dryRun: dryRun,
+      analytics: analytics,
+      generateDotPackages: argResults['legacy-packages-file'],
     );
-    if (argResults['example'] && entrypoint.example != null) {
-      await entrypoint.example.acquireDependencies(SolveType.GET,
-          unlock: argResults.rest,
-          dryRun: dryRun,
-          onlyReportSuccessOrFailure: true);
+    var example = entrypoint.example;
+    if (argResults['example'] && example != null) {
+      await example.acquireDependencies(
+        SolveType.get,
+        unlock: argResults.rest,
+        dryRun: dryRun,
+        onlyReportSuccessOrFailure: true,
+        analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
+      );
     }
 
     if (isOffline) {
diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart
index 2709238..7b8906c 100644
--- a/lib/src/command/get.dart
+++ b/lib/src/command/get.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
@@ -35,6 +33,9 @@
 
     argParser.addFlag('packages-dir', hide: true);
 
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
+
     argParser.addFlag(
       'example',
       help: 'Also run in `example/` (if it exists).',
@@ -51,14 +52,24 @@
       log.warning(log.yellow(
           'The --packages-dir flag is no longer used and does nothing.'));
     }
-    await entrypoint.acquireDependencies(SolveType.GET,
-        dryRun: argResults['dry-run'], precompile: argResults['precompile']);
+    await entrypoint.acquireDependencies(
+      SolveType.get,
+      dryRun: argResults['dry-run'],
+      precompile: argResults['precompile'],
+      generateDotPackages: argResults['legacy-packages-file'],
+      analytics: analytics,
+    );
 
-    if (argResults['example'] && entrypoint.example != null) {
-      await entrypoint.example.acquireDependencies(SolveType.GET,
-          dryRun: argResults['dry-run'],
-          precompile: argResults['precompile'],
-          onlyReportSuccessOrFailure: true);
+    var example = entrypoint.example;
+    if (argResults['example'] && example != null) {
+      await example.acquireDependencies(
+        SolveType.get,
+        dryRun: argResults['dry-run'],
+        precompile: argResults['precompile'],
+        generateDotPackages: argResults['legacy-packages-file'],
+        analytics: analytics,
+        onlyReportSuccessOrFailure: true,
+      );
     }
   }
 }
diff --git a/lib/src/command/global.dart b/lib/src/command/global.dart
index b7f7b01..a7e8898 100644
--- a/lib/src/command/global.dart
+++ b/lib/src/command/global.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 import 'global_activate.dart';
 import 'global_deactivate.dart';
diff --git a/lib/src/command/global_activate.dart b/lib/src/command/global_activate.dart
index 2959e6e..a21386a 100644
--- a/lib/src/command/global_activate.dart
+++ b/lib/src/command/global_activate.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -54,13 +52,13 @@
   @override
   Future<void> runProtected() async {
     // Default to `null`, which means all executables.
-    List<String> executables;
+    List<String>? executables;
     if (argResults.wasParsed('executable')) {
       if (argResults.wasParsed('no-executables')) {
         usageException('Cannot pass both --no-executables and --executable.');
       }
 
-      executables = argResults['executable'] as List<String>;
+      executables = argResults['executable'];
     } else if (argResults['no-executables']) {
       // An empty list means no executables.
       executables = [];
@@ -78,8 +76,8 @@
       features[feature] = FeatureDependency.unused;
     }
 
-    var overwrite = argResults['overwrite'];
-    Uri hostedUrl;
+    final overwrite = argResults['overwrite'] as bool;
+    Uri? hostedUrl;
     if (argResults.wasParsed('hosted-url')) {
       try {
         hostedUrl = validateAndNormalizeHostedUrl(argResults['hosted-url']);
@@ -90,7 +88,7 @@
 
     Iterable<String> args = argResults.rest;
 
-    dynamic readArg([String error]) {
+    String readArg([String error = '']) {
       if (args.isEmpty) usageException(error);
       var arg = args.first;
       args = args.skip(1);
@@ -139,8 +137,12 @@
 
         var path = readArg('No package to activate given.');
         validateNoExtraArgs();
-        return globals.activatePath(path, executables,
-            overwriteBinStubs: overwrite);
+        return globals.activatePath(
+          path,
+          executables,
+          overwriteBinStubs: overwrite,
+          analytics: analytics,
+        );
     }
 
     throw StateError('unreachable');
diff --git a/lib/src/command/global_deactivate.dart b/lib/src/command/global_deactivate.dart
index e06bb5e..7e99e19 100644
--- a/lib/src/command/global_deactivate.dart
+++ b/lib/src/command/global_deactivate.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../log.dart' as log;
 import '../utils.dart';
diff --git a/lib/src/command/global_list.dart b/lib/src/command/global_list.dart
index cd72791..5e22042 100644
--- a/lib/src/command/global_list.dart
+++ b/lib/src/command/global_list.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 
 /// Handles the `global list` pub command.
diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index 28d5ca6..71e0a9d 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
@@ -19,8 +17,7 @@
   String get name => 'run';
   @override
   String get description =>
-      'Run an executable from a globally activated package.\n'
-      "NOTE: We are currently optimizing this command's startup time.";
+      'Run an executable from a globally activated package.';
   @override
   String get argumentsDescription => '<package>:<executable> [args...]';
   @override
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index 151ee79..c927e07 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -36,15 +34,13 @@
   bool get takesArguments => false;
 
   /// The URL of the server to which to upload the package.
-  Uri get server {
-    if (_server != null) {
-      return _server;
-    }
+  late final Uri server = _createServer();
 
+  Uri _createServer() {
     // An explicit argument takes precedence.
     if (argResults.wasParsed('server')) {
       try {
-        return _server = validateAndNormalizeHostedUrl(argResults['server']);
+        return validateAndNormalizeHostedUrl(argResults['server']);
       } on FormatException catch (e) {
         usageException('Invalid server: $e');
       }
@@ -54,19 +50,16 @@
     final publishTo = entrypoint.root.pubspec.publishTo;
     if (publishTo != null) {
       try {
-        return _server = validateAndNormalizeHostedUrl(publishTo);
+        return validateAndNormalizeHostedUrl(publishTo);
       } on FormatException catch (e) {
         throw DataException('Invalid publish_to: $e');
       }
     }
 
     // Use the default server if nothing else is specified
-    return _server = cache.sources.hosted.defaultUrl;
+    return cache.sources.hosted.defaultUrl;
   }
 
-  /// Cache value for [server].
-  Uri _server;
-
   /// Whether the publish is just a preview.
   bool get dryRun => argResults['dry-run'];
 
@@ -92,13 +85,13 @@
 
   Future<void> _publishUsingClient(
     List<int> packageBytes,
-    http.BaseClient client,
+    http.Client client,
   ) async {
-    Uri cloudStorageUrl;
+    Uri? cloudStorageUrl;
 
     try {
       await log.progress('Uploading', () async {
-        var newUri = server.resolve('/api/packages/versions/new');
+        var newUri = server.resolve('api/packages/versions/new');
         var response = await client.get(newUri, headers: pubApiHeaders);
         var parameters = parseJsonResponse(response);
 
@@ -107,7 +100,7 @@
         cloudStorageUrl = Uri.parse(url);
         // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We
         // should report that error and exit.
-        var request = http.MultipartRequest('POST', cloudStorageUrl);
+        var request = http.MultipartRequest('POST', cloudStorageUrl!);
 
         var fields = _expectField(parameters, 'fields', response);
         if (fields is! Map) invalidServerResponse(response);
@@ -127,8 +120,24 @@
         handleJsonSuccess(
             await client.get(Uri.parse(location), headers: pubApiHeaders));
       });
+    } on AuthenticationException catch (error) {
+      var msg = '';
+      if (error.statusCode == 401) {
+        msg += '$server package repository requested authentication!\n'
+            'You can provide credentials using:\n'
+            '    pub token add $server\n';
+      }
+      if (error.statusCode == 403) {
+        msg += 'Insufficient permissions to the resource at the $server '
+            'package repository.\nYou can modify credentials using:\n'
+            '    pub token add $server\n';
+      }
+      if (error.serverMessage != null) {
+        msg += '\n' + error.serverMessage! + '\n';
+      }
+      dataError(msg + log.red('Authentication failed!'));
     } on PubHttpException catch (error) {
-      var url = error.response.request.url;
+      var url = error.response.request!.url;
       if (url == cloudStorageUrl) {
         // TODO(nweiz): the response may have XML-formatted information about
         // the error. Try to parse that out once we have an easily-accessible
@@ -156,7 +165,8 @@
         // explicitly have to define mock servers as official server to test
         // publish command with oauth2 credentials.
         if (runningFromTest &&
-            Platform.environment.containsKey('PUB_HOSTED_URL'))
+            Platform.environment.containsKey('PUB_HOSTED_URL') &&
+            Platform.environment['_PUB_TEST_AUTH_METHOD'] == 'oauth2')
           Platform.environment['PUB_HOSTED_URL'],
       };
 
@@ -172,7 +182,7 @@
         });
       }
     } on PubHttpException catch (error) {
-      var url = error.response.request.url;
+      var url = error.response.request!.url;
       if (Uri.parse(url.origin) == Uri.parse(server.origin)) {
         handleJsonError(error.response);
       } else {
diff --git a/lib/src/command/list_package_dirs.dart b/lib/src/command/list_package_dirs.dart
index 96c0735..09d248e 100644
--- a/lib/src/command/list_package_dirs.dart
+++ b/lib/src/command/list_package_dirs.dart
@@ -2,15 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 
 import '../command.dart';
 import '../command_runner.dart';
-import '../exit_codes.dart' as exit_codes;
 import '../io.dart';
 import '../log.dart' as log;
+import '../package_name.dart';
 import '../utils.dart';
 
 /// Handles the `list-package-dirs` pub command.
@@ -45,7 +43,8 @@
     var output = {};
 
     // Include the local paths to all locked packages.
-    var packages = mapMap(entrypoint.lockFile.packages, value: (name, package) {
+    var packages = mapMap(entrypoint.lockFile.packages,
+        value: (String name, PackageId package) {
       var source = entrypoint.cache.source(package.source);
       var packageDir = source.getDirectory(package);
       // Normalize paths and make them absolute for backwards compatibility
@@ -67,6 +66,5 @@
     ];
 
     log.json.message(output);
-    return exit_codes.SUCCESS;
   }
 }
diff --git a/lib/src/command/login.dart b/lib/src/command/login.dart
index 747bb55..8c31e69 100644
--- a/lib/src/command/login.dart
+++ b/lib/src/command/login.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -40,7 +38,7 @@
     }
   }
 
-  Future<_UserInfo> _retrieveUserInfo() async {
+  Future<_UserInfo?> _retrieveUserInfo() async {
     return await oauth2.withClient(cache, (client) async {
       final discovery = await httpClient.get(Uri.https(
           'accounts.google.com', '/.well-known/openid-configuration'));
@@ -62,5 +60,5 @@
   final String email;
   _UserInfo(this.name, this.email);
   @override
-  String toString() => ['<$email>', if (name != null) name].join(' ');
+  String toString() => ['<$email>', name].join(' ');
 }
diff --git a/lib/src/command/logout.dart b/lib/src/command/logout.dart
index 071e8c0..04ffe12 100644
--- a/lib/src/command/logout.dart
+++ b/lib/src/command/logout.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index 444bd03..2941952 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -2,14 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:math';
 
-import 'package:meta/meta.dart';
+import 'package:collection/collection.dart'
+    show IterableExtension, IterableNullableExtension;
 import 'package:path/path.dart' as path;
 
 import '../command.dart';
@@ -119,7 +118,7 @@
       'outdated': _OutdatedMode(),
       'null-safety': _NullSafetyMode(cache, entrypoint,
           shouldShowSpinner: _shouldShowSpinner),
-    }[argResults['mode']];
+    }[argResults['mode']]!;
 
     final includeDevDependencies = argResults['dev-dependencies'];
     final includeDependencyOverrides = argResults['dependency-overrides'];
@@ -138,10 +137,10 @@
 
     final resolvablePubspec = await mode.resolvablePubspec(upgradablePubspec);
 
-    List<PackageId> upgradablePackages;
-    List<PackageId> resolvablePackages;
-    bool hasUpgradableResolution;
-    bool hasResolvableResolution;
+    late List<PackageId> upgradablePackages;
+    late List<PackageId> resolvablePackages;
+    late bool hasUpgradableResolution;
+    late bool hasResolvableResolution;
 
     await log.spinner('Resolving', () async {
       final upgradablePackagesResult =
@@ -169,16 +168,16 @@
 
     Future<_PackageDetails> analyzeDependency(PackageRef packageRef) async {
       final name = packageRef.name;
-      final current = (entrypoint.lockFile?.packages ?? {})[name];
+      final current = entrypoint.lockFile.packages[name];
 
-      final upgradable = upgradablePackages.firstWhere((id) => id.name == name,
-          orElse: () => null);
-      final resolvable = resolvablePackages.firstWhere((id) => id.name == name,
-          orElse: () => null);
+      final upgradable =
+          upgradablePackages.firstWhereOrNull((id) => id.name == name);
+      final resolvable =
+          resolvablePackages.firstWhereOrNull((id) => id.name == name);
 
       // Find the latest version, and if it's overridden.
       var latestIsOverridden = false;
-      PackageId latest;
+      PackageId? latest;
       // If not overridden in current resolution we can use this
       if (!entrypoint.root.pubspec.dependencyOverrides.containsKey(name)) {
         latest ??=
@@ -296,27 +295,25 @@
     return argResults['mode'] != 'null-safety';
   }
 
-  bool _prereleases;
-
-  bool get prereleases => _prereleases ??= () {
-        // First check if 'prereleases' was passed as an argument.
-        // If that was not the case, check for use of the legacy spelling
-        // 'pre-releases'.
-        // Otherwise fall back to the default implied by the mode.
-        if (argResults.wasParsed('prereleases')) {
-          return argResults['prereleases'];
-        }
-        if (argResults.wasParsed('pre-releases')) {
-          return argResults['pre-releases'];
-        }
-        return argResults['mode'] == 'null-safety';
-      }();
+  late final bool prereleases = () {
+    // First check if 'prereleases' was passed as an argument.
+    // If that was not the case, check for use of the legacy spelling
+    // 'pre-releases'.
+    // Otherwise fall back to the default implied by the mode.
+    if (argResults.wasParsed('prereleases')) {
+      return argResults['prereleases'];
+    }
+    if (argResults.wasParsed('pre-releases')) {
+      return argResults['pre-releases'];
+    }
+    return argResults['mode'] == 'null-safety';
+  }();
 
   /// Retrieves the pubspec of package [name] in [version] from [source].
   ///
   /// Returns `null`, if given `null` as a convinience.
-  Future<_VersionDetails> _describeVersion(
-    PackageId id,
+  Future<_VersionDetails?> _describeVersion(
+    PackageId? id,
     bool isOverridden,
   ) async {
     if (id == null) {
@@ -367,9 +364,9 @@
 
 /// Try to solve [pubspec] return [PackageId]s in the resolution or `null` if no
 /// resolution was found.
-Future<List<PackageId>> _tryResolve(Pubspec pubspec, SystemCache cache) async {
+Future<List<PackageId>?> _tryResolve(Pubspec pubspec, SystemCache cache) async {
   final solveResult = await tryResolveVersions(
-    SolveType.UPGRADE,
+    SolveType.upgrade,
     cache,
     Package.inMemory(pubspec),
   );
@@ -380,13 +377,13 @@
 Future<void> _outputJson(
   List<_PackageDetails> rows,
   Mode mode, {
-  @required bool showAll,
-  @required bool includeDevDependencies,
+  required bool showAll,
+  required bool includeDevDependencies,
 }) async {
   final markedRows =
       Map.fromIterables(rows, await mode.markVersionDetails(rows));
   if (!showAll) {
-    rows.removeWhere((row) => markedRows[row][0].asDesired);
+    rows.removeWhere((row) => markedRows[row]![0].asDesired);
   }
   if (!includeDevDependencies) {
     rows.removeWhere(
@@ -402,10 +399,10 @@
           ...(rows..sort((a, b) => a.name.compareTo(b.name)))
               .map((packageDetails) => {
                     'package': packageDetails.name,
-                    'current': markedRows[packageDetails][0]?.toJson(),
-                    'upgradable': markedRows[packageDetails][1]?.toJson(),
-                    'resolvable': markedRows[packageDetails][2]?.toJson(),
-                    'latest': markedRows[packageDetails][3]?.toJson(),
+                    'current': markedRows[packageDetails]![0].toJson(),
+                    'upgradable': markedRows[packageDetails]![1].toJson(),
+                    'resolvable': markedRows[packageDetails]![2].toJson(),
+                    'latest': markedRows[packageDetails]![3].toJson(),
                   })
         ]
       },
@@ -416,16 +413,16 @@
 Future<void> _outputHuman(
   List<_PackageDetails> rows,
   Mode mode, {
-  @required bool showAll,
-  @required bool useColors,
-  @required bool includeDevDependencies,
-  @required bool lockFileExists,
-  @required bool hasDirectDependencies,
-  @required bool hasDevDependencies,
-  @required bool showTransitiveDependencies,
-  @required bool hasUpgradableResolution,
-  @required bool hasResolvableResolution,
-  @required String directory,
+  required bool showAll,
+  required bool useColors,
+  required bool includeDevDependencies,
+  required bool lockFileExists,
+  required bool hasDirectDependencies,
+  required bool hasDevDependencies,
+  required bool showTransitiveDependencies,
+  required bool hasUpgradableResolution,
+  required bool hasResolvableResolution,
+  required String directory,
 }) async {
   final directoryDesc = directory == '.' ? '' : ' in $directory';
   log.message(mode.explanation(directoryDesc) + '\n');
@@ -434,11 +431,11 @@
 
   List<_FormattedString> formatted(_PackageDetails package) => [
         _FormattedString(package.name),
-        ...markedRows[package].map((m) => m.toHuman()),
+        ...markedRows[package]!.map((m) => m.toHuman()),
       ];
 
   if (!showAll) {
-    rows.removeWhere((row) => markedRows[row][0].asDesired);
+    rows.removeWhere((row) => markedRows[row]![0].asDesired);
   }
   if (rows.isEmpty) {
     log.message(mode.foundNoBadText);
@@ -506,7 +503,8 @@
     for (var j = 0; j < row.length; j++) {
       b.write(row[j].formatted(useColors: useColors));
       b.write(' ' *
-          ((columnWidths[j] + 2) - row[j].computeLength(useColors: useColors)));
+          ((columnWidths[j]! + 2) -
+              row[j].computeLength(useColors: useColors)));
     }
     log.message(b.toString());
   }
@@ -624,15 +622,15 @@
     final rows = <List<_MarkedVersionDetails>>[];
     for (final packageDetails in packages) {
       final cols = <_MarkedVersionDetails>[];
-      _VersionDetails previous;
+      _VersionDetails? previous;
       for (final versionDetails in [
         packageDetails.current,
         packageDetails.upgradable,
         packageDetails.resolvable,
         packageDetails.latest
       ]) {
-        String Function(String) color;
-        String prefix;
+        String Function(String)? color;
+        String? prefix;
         var asDesired = false;
         if (versionDetails != null) {
           final isLatest = versionDetails == packageDetails.latest;
@@ -660,8 +658,8 @@
   }
 
   @override
-  Future<Pubspec> resolvablePubspec(Pubspec pubspec) async {
-    return stripVersionUpperBounds(pubspec);
+  Future<Pubspec> resolvablePubspec(Pubspec? pubspec) async {
+    return stripVersionUpperBounds(pubspec!);
   }
 }
 
@@ -674,7 +672,7 @@
   final _notCompliantEmoji = emoji('✗', 'x');
 
   _NullSafetyMode(this.cache, this.entrypoint,
-      {@required this.shouldShowSpinner});
+      {required this.shouldShowSpinner});
 
   @override
   String explanation(String directoryDescription) => '''
@@ -714,14 +712,14 @@
           packageDetails.resolvable?._id,
           packageDetails.latest?._id,
         ]
-      }.where((id) => id != null);
+      }.whereNotNull();
 
       return Map.fromEntries(
         await Future.wait(
           ids.map(
             (id) async => MapEntry(
                 id,
-                (await id.source.bind(cache).describe(id))
+                (await id.source!.bind(cache).describe(id))
                     .languageVersion
                     .supportsNullSafety),
           ),
@@ -737,20 +735,20 @@
           packageDetails.latest
         ].map(
           (versionDetails) {
-            String Function(String) color;
-            String prefix;
-            bool nullSafetyJson;
+            String Function(String)? color;
+            String? prefix;
+            MapEntry<String, Object>? jsonExplanation;
             var asDesired = false;
             if (versionDetails != null) {
-              if (nullSafetyMap[versionDetails._id]) {
+              if (nullSafetyMap[versionDetails._id]!) {
                 color = log.green;
                 prefix = _compliantEmoji;
-                nullSafetyJson = true;
+                jsonExplanation = MapEntry('nullSafety', true);
                 asDesired = true;
               } else {
                 color = log.red;
                 prefix = _notCompliantEmoji;
-                nullSafetyJson = false;
+                jsonExplanation = MapEntry('nullSafety', false);
               }
             }
             return _MarkedVersionDetails(
@@ -758,7 +756,7 @@
               asDesired: asDesired,
               format: color,
               prefix: prefix,
-              jsonExplanation: MapEntry('nullSafety', nullSafetyJson),
+              jsonExplanation: jsonExplanation,
             );
           },
         ).toList()
@@ -809,14 +807,17 @@
           _overridden == other._overridden &&
           _id.source == other._id.source &&
           _pubspec.version == other._pubspec.version;
+
+  @override
+  int get hashCode => Object.hash(_pubspec.version, _id.source, _overridden);
 }
 
 class _PackageDetails implements Comparable<_PackageDetails> {
   final String name;
-  final _VersionDetails current;
-  final _VersionDetails upgradable;
-  final _VersionDetails resolvable;
-  final _VersionDetails latest;
+  final _VersionDetails? current;
+  final _VersionDetails? upgradable;
+  final _VersionDetails? resolvable;
+  final _VersionDetails? latest;
   final _DependencyKind kind;
 
   _PackageDetails(this.name, this.current, this.upgradable, this.resolvable,
@@ -830,7 +831,7 @@
     return name.compareTo(other.name);
   }
 
-  Map<String, Object> toJson() {
+  Map<String, Object?> toJson() {
     return {
       'package': name,
       'current': current?.toJson(),
@@ -870,15 +871,16 @@
   devTransitive,
 }
 
-_FormattedString _format(String value, Function(String) format, {prefix = ''}) {
+_FormattedString _format(String value, String Function(String) format,
+    {prefix = ''}) {
   return _FormattedString(value, format: format, prefix: prefix);
 }
 
 class _MarkedVersionDetails {
-  final MapEntry<String, Object> _jsonExplanation;
-  final _VersionDetails _versionDetails;
-  final String Function(String) _format;
-  final String _prefix;
+  final MapEntry<String, Object>? _jsonExplanation;
+  final _VersionDetails? _versionDetails;
+  final String Function(String)? _format;
+  final String? _prefix;
 
   /// This should be true if the mode creating this consideres the version as
   /// "good".
@@ -889,7 +891,7 @@
 
   _MarkedVersionDetails(
     this._versionDetails, {
-    @required this.asDesired,
+    required this.asDesired,
     format,
     prefix = '',
     jsonExplanation,
@@ -903,12 +905,13 @@
         prefix: _prefix,
       );
 
-  Object toJson() {
+  Object? toJson() {
     if (_versionDetails == null) return null;
 
-    return _jsonExplanation == null
-        ? _versionDetails.toJson()
-        : (_versionDetails.toJson()..addEntries([_jsonExplanation]));
+    var jsonExplanation = _jsonExplanation;
+    return jsonExplanation == null
+        ? _versionDetails!.toJson()
+        : (_versionDetails!.toJson()..addEntries([jsonExplanation]));
   }
 }
 
@@ -921,15 +924,15 @@
   /// A prefix for marking this string if colors are not used.
   final String _prefix;
 
-  _FormattedString(this.value, {String Function(String) format, prefix})
+  _FormattedString(this.value, {String Function(String)? format, prefix})
       : _format = format ?? _noFormat,
         _prefix = prefix ?? '';
 
-  String formatted({@required bool useColors}) {
+  String formatted({required bool useColors}) {
     return useColors ? _format(_prefix + value) : _prefix + value;
   }
 
-  int computeLength({@required bool useColors}) {
+  int computeLength({required bool? useColors}) {
     return _prefix.length + value.length;
   }
 
diff --git a/lib/src/command/remove.dart b/lib/src/command/remove.dart
index f505785..82a9547 100644
--- a/lib/src/command/remove.dart
+++ b/lib/src/command/remove.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:yaml/yaml.dart';
 import 'package:yaml_edit/yaml_edit.dart';
 
@@ -52,6 +50,9 @@
 
     argParser.addOption('directory',
         abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
+
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
   }
 
   @override
@@ -67,12 +68,12 @@
       final newPubspec = _removePackagesFromPubspec(rootPubspec, packages);
       final newRoot = Package.inMemory(newPubspec);
 
-      await Entrypoint.global(newRoot, entrypoint.lockFile, cache)
-          .acquireDependencies(
-        SolveType.GET,
-        precompile: argResults['precompile'],
-        dryRun: true,
-      );
+      await Entrypoint.inMemory(newRoot, cache, lockFile: entrypoint.lockFile)
+          .acquireDependencies(SolveType.get,
+              precompile: argResults['precompile'],
+              dryRun: true,
+              analytics: null,
+              generateDotPackages: false);
     } else {
       /// Update the pubspec.
       _writeRemovalToPubspec(packages);
@@ -80,13 +81,22 @@
       /// Create a new [Entrypoint] since we have to reprocess the updated
       /// pubspec file.
       final updatedEntrypoint = Entrypoint(directory, cache);
-      await updatedEntrypoint.acquireDependencies(SolveType.GET,
-          precompile: argResults['precompile']);
+      await updatedEntrypoint.acquireDependencies(
+        SolveType.get,
+        precompile: argResults['precompile'],
+        analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
+      );
 
-      if (argResults['example'] && entrypoint.example != null) {
-        await entrypoint.example.acquireDependencies(SolveType.GET,
-            precompile: argResults['precompile'],
-            onlyReportSuccessOrFailure: true);
+      var example = entrypoint.example;
+      if (argResults['example'] && example != null) {
+        await example.acquireDependencies(
+          SolveType.get,
+          precompile: argResults['precompile'],
+          onlyReportSuccessOrFailure: true,
+          analytics: analytics,
+          generateDotPackages: argResults['legacy-packages-file'],
+        );
       }
     }
   }
@@ -122,8 +132,8 @@
       /// There may be packages where the dependency is declared both in
       /// dependencies and dev_dependencies.
       for (final dependencyKey in ['dependencies', 'dev_dependencies']) {
-        final dependenciesNode =
-            yamlEditor.parseAt([dependencyKey], orElse: () => null);
+        final dependenciesNode = yamlEditor
+            .parseAt([dependencyKey], orElse: () => YamlScalar.wrap(null));
 
         if (dependenciesNode is YamlMap &&
             dependenciesNode.containsKey(package)) {
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index c706c72..e223c5a 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
@@ -45,8 +43,6 @@
     argParser.addFlag('sound-null-safety',
         help: 'Override the default null safety execution mode.');
     argParser.addOption('mode', help: 'Deprecated option', hide: true);
-    // mode exposed for `dartdev run` to use as subprocess.
-    argParser.addFlag('dart-dev-run', hide: true);
     argParser.addOption('directory',
         abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
   }
@@ -58,9 +54,6 @@
         log.message('Deprecated. Use `dart run` instead.');
       });
     }
-    if (argResults['dart-dev-run']) {
-      return await _runFromDartDev();
-    }
     if (argResults.rest.isEmpty) {
       usageException('Must specify an executable to run.');
     }
@@ -104,54 +97,4 @@
     );
     overrideExitCode(exitCode);
   }
-
-  /// Implement a mode for use in `dartdev run`.
-  ///
-  /// Usage: `dartdev run [package[:command]]`
-  ///
-  /// If `package` is not given, defaults to current root package.
-  /// If `command` is not given, defaults to name of `package`.
-  ///
-  /// Runs `bin/<command>.dart` from package `<package>`. If `<package>` is not
-  /// mutable (local root package or path-dependency) a source snapshot will be
-  /// cached in
-  /// `.dart_tool/pub/bin/<package>/<command>.dart-<sdkVersion>.snapshot`.
-  Future<void> _runFromDartDev() async {
-    var package = entrypoint.root.name;
-    var command = package;
-    var args = <String>[];
-
-    if (argResults.rest.isNotEmpty) {
-      if (argResults.rest[0].contains(RegExp(r'[/\\]'))) {
-        usageException('[<package>[:command]] cannot contain "/" or "\\"');
-      }
-
-      package = argResults.rest[0];
-      if (package.contains(':')) {
-        final parts = package.split(':');
-        if (parts.length > 2) {
-          usageException('[<package>[:command]] cannot contain multiple ":"');
-        }
-        package = parts[0];
-        command = parts[1];
-      } else {
-        command = package;
-      }
-      args = argResults.rest.skip(1).toList();
-    }
-
-    final vmArgs = vmArgsFromArgResults(argResults);
-
-    overrideExitCode(
-      await runExecutable(
-        entrypoint,
-        Executable(package, 'bin/$command.dart'),
-        args,
-        vmArgs: vmArgs,
-        enableAsserts: argResults['enable-asserts'] || argResults['checked'],
-        recompile: entrypoint.precompileExecutable,
-        alwaysUseSubprocess: alwaysUseSubprocess,
-      ),
-    );
-  }
 }
diff --git a/lib/src/command/serve.dart b/lib/src/command/serve.dart
index 76c0002..4c560e7 100644
--- a/lib/src/command/serve.dart
+++ b/lib/src/command/serve.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'barback.dart';
 
 /// Handles the `serve` pub command.
diff --git a/lib/src/command/token.dart b/lib/src/command/token.dart
index dafa88f..9886dcf 100644
--- a/lib/src/command/token.dart
+++ b/lib/src/command/token.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.11
-
 import '../command.dart';
 import 'token_add.dart';
 import 'token_list.dart';
diff --git a/lib/src/command/token_add.dart b/lib/src/command/token_add.dart
index f7cd6b9..caf3b8b 100644
--- a/lib/src/command/token_add.dart
+++ b/lib/src/command/token_add.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.11
-
 import 'dart:async';
 import 'dart:io';
 
@@ -26,7 +24,7 @@
   @override
   String get argumentsDescription => '[hosted-url]';
 
-  String get envVar => argResults['env-var'];
+  String? get envVar => argResults['env-var'];
 
   TokenAddCommand() {
     argParser.addOption('env-var',
@@ -42,32 +40,28 @@
     } else if (argResults.rest.length > 1) {
       usageException('Takes only a single argument.');
     }
+    final rawHostedUrl = argResults.rest.first;
 
     try {
-      var hostedUrl = validateAndNormalizeHostedUrl(argResults.rest.first);
-      if (hostedUrl.isScheme('HTTP')) {
-        throw DataException('Insecure package repository could not be added.');
+      var hostedUrl = validateAndNormalizeHostedUrl(rawHostedUrl);
+      if (!hostedUrl.isScheme('HTTPS')) {
+        throw FormatException('url must be https://, '
+            'insecure repositories cannot use authentication.');
       }
 
       if (envVar == null) {
         await _addTokenFromStdin(hostedUrl);
       } else {
-        await _addEnvVarToken(hostedUrl);
+        await _addEnvVarToken(hostedUrl, envVar!);
       }
     } on FormatException catch (e) {
-      usageException('Invalid [hosted-url]: "${argResults.rest.first}"\n'
+      usageException('Invalid [hosted-url]: "$rawHostedUrl"\n'
           '${e.message}');
-    } on TimeoutException catch (_) {
-      // Timeout is added to readLine call to make sure automated jobs doesn't
-      // get stuck on noop state if user forget to pipe token to the 'token add'
-      // command. This behavior might be removed.
-      throw ApplicationException('Token is not provided within 15 minutes.');
     }
   }
 
   Future<void> _addTokenFromStdin(Uri hostedUrl) async {
-    final token = await stdinPrompt('Enter secret token:', echoMode: false)
-        .timeout(const Duration(minutes: 15));
+    final token = await stdinPrompt('Enter secret token:', echoMode: false);
     if (token.isEmpty) {
       usageException('Token is not provided.');
     }
@@ -79,22 +73,45 @@
     );
   }
 
-  Future<void> _addEnvVarToken(Uri hostedUrl) async {
+  Future<void> _addEnvVarToken(Uri hostedUrl, String envVar) async {
     if (envVar.isEmpty) {
-      throw DataException('Cannot use the empty string as --env-var');
+      usageException('Cannot use the empty string as --env-var');
+    }
+
+    // Environment variable names on Windows [1] and UNIX [2] cannot contain
+    // equal signs.
+    // [1] https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
+    // [2] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
+    if (envVar.contains('=')) {
+      throw DataException(
+        'Environment variable name --env-var="$envVar" cannot contain "=", the '
+        'equals sign is not allowed in environment variable names.',
+      );
+    }
+
+    // Help the user if they typed something that is unlikely to be correct.
+    // This could happen if you include $, whitespace, quotes or accidentally
+    // dereference the environment variable instead.
+    if (!RegExp(r'^[A-Z_][A-Z0-9_]*$').hasMatch(envVar)) {
+      log.warning(
+        'The environment variable name --env-var="$envVar" does not use '
+        'uppercase characters A-Z, 0-9 and underscore. This is unusual for '
+        'environment variable names.\n'
+        'Check that you meant to use the environment variable name: "$envVar".',
+      );
     }
 
     tokenStore.addCredential(Credential.env(hostedUrl, envVar));
     log.message(
       'Requests to "$hostedUrl" will now be authenticated using the secret '
-      'token stored in the environment variable `$envVar`.',
+      'token stored in the environment variable "$envVar".',
     );
 
     if (!Platform.environment.containsKey(envVar)) {
       // If environment variable doesn't exist when
       // pub token add <hosted-url> --env-var <ENV_VAR> is called, we should
       // print a warning.
-      log.warning('Environment variable `$envVar` is not defined.');
+      log.warning('Environment variable "$envVar" is not defined.');
     }
   }
 }
diff --git a/lib/src/command/token_list.dart b/lib/src/command/token_list.dart
index 828f004..3cf73ee 100644
--- a/lib/src/command/token_list.dart
+++ b/lib/src/command/token_list.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.11
-
 import '../command.dart';
 import '../log.dart' as log;
 
diff --git a/lib/src/command/token_remove.dart b/lib/src/command/token_remove.dart
index 7c49a0d..1b08127 100644
--- a/lib/src/command/token_remove.dart
+++ b/lib/src/command/token_remove.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.11
-
 import '../command.dart';
 import '../exceptions.dart';
 import '../log.dart' as log;
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 39a42c7..0dc5fb7 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -58,6 +56,9 @@
 
     argParser.addFlag('packages-dir', hide: true);
 
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
+
     argParser.addFlag(
       'major-versions',
       help: 'Upgrades packages to their latest resolvable versions, '
@@ -82,6 +83,8 @@
 
   bool get _precompile => argResults['precompile'];
 
+  bool get _packagesFile => argResults['legacy-packages-file'];
+
   bool get _upgradeNullSafety =>
       argResults['nullsafety'] || argResults['null-safety'];
 
@@ -116,18 +119,21 @@
     if (argResults['example'] && entrypoint.example != null) {
       // Reload the entrypoint to ensure we pick up potential changes that has
       // been made.
-      final exampleEntrypoint = Entrypoint(directory, cache).example;
+      final exampleEntrypoint = Entrypoint(directory, cache).example!;
       await _runUpgrade(exampleEntrypoint, onlySummary: true);
     }
   }
 
   Future<void> _runUpgrade(Entrypoint e, {bool onlySummary = false}) async {
-    await e.acquireDependencies(SolveType.UPGRADE,
-        unlock: argResults.rest,
-        dryRun: _dryRun,
-        precompile: _precompile,
-        onlyReportSuccessOrFailure: onlySummary);
-
+    await e.acquireDependencies(
+      SolveType.upgrade,
+      unlock: argResults.rest,
+      dryRun: _dryRun,
+      precompile: _precompile,
+      onlyReportSuccessOrFailure: onlySummary,
+      generateDotPackages: _packagesFile,
+      analytics: analytics,
+    );
     _showOfflineWarning();
   }
 
@@ -179,12 +185,12 @@
     final resolvedPackages = <String, PackageId>{};
     final solveResult = await log.spinner('Resolving dependencies', () async {
       return await resolveVersions(
-        SolveType.UPGRADE,
+        SolveType.upgrade,
         cache,
         Package.inMemory(resolvablePubspec),
       );
     }, condition: _shouldShowSpinner);
-    for (final resolvedPackage in solveResult?.packages ?? []) {
+    for (final resolvedPackage in solveResult.packages) {
       resolvedPackages[resolvedPackage.name] = resolvedPackage;
     }
 
@@ -196,9 +202,8 @@
       ...entrypoint.root.pubspec.devDependencies.values,
     ].where((dep) => dep.source is HostedSource);
     for (final dep in declaredHostedDependencies) {
-      final resolvedPackage = resolvedPackages[dep.name];
-      assert(resolvedPackage != null);
-      if (resolvedPackage == null || !toUpgrade.contains(dep.name)) {
+      final resolvedPackage = resolvedPackages[dep.name]!;
+      if (!toUpgrade.contains(dep.name)) {
         // If we're not to upgrade this package, or it wasn't in the
         // resolution somehow, then we ignore it.
         continue;
@@ -219,30 +224,37 @@
         resolvedPackage.version,
       ));
     }
+    final newPubspecText = _updatePubspec(changes);
 
     if (_dryRun) {
       // Even if it is a dry run, run `acquireDependencies` so that the user
       // gets a report on changes.
-      // TODO(jonasfj): Stop abusing Entrypoint.global for dry-run output
-      await Entrypoint.global(
-        Package.inMemory(resolvablePubspec),
-        entrypoint.lockFile,
+      await Entrypoint.inMemory(
+        Package.inMemory(
+          Pubspec.parse(newPubspecText, cache.sources),
+        ),
         cache,
+        lockFile: entrypoint.lockFile,
         solveResult: solveResult,
       ).acquireDependencies(
-        SolveType.UPGRADE,
+        SolveType.get,
         dryRun: true,
         precompile: _precompile,
+        analytics: null, // No analytics for dry-run
+        generateDotPackages: false,
       );
     } else {
-      await _updatePubspec(changes);
-
+      if (changes.isNotEmpty) {
+        writeTextFile(entrypoint.pubspecPath, newPubspecText);
+      }
       // TODO: Allow Entrypoint to be created with in-memory pubspec, so that
       //       we can show the changes when not in --dry-run mode. For now we only show
       //       the changes made to pubspec.yaml in dry-run mode.
       await Entrypoint(directory, cache).acquireDependencies(
-        SolveType.UPGRADE,
+        SolveType.get,
         precompile: _precompile,
+        analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
     }
 
@@ -275,12 +287,12 @@
     final resolvedPackages = <String, PackageId>{};
     final solveResult = await log.spinner('Resolving dependencies', () async {
       return await resolveVersions(
-        SolveType.UPGRADE,
+        SolveType.upgrade,
         cache,
         Package.inMemory(nullsafetyPubspec),
       );
     }, condition: _shouldShowSpinner);
-    for (final resolvedPackage in solveResult?.packages ?? []) {
+    for (final resolvedPackage in solveResult.packages) {
       resolvedPackages[resolvedPackage.name] = resolvedPackage;
     }
 
@@ -292,9 +304,8 @@
       ...entrypoint.root.pubspec.devDependencies.values,
     ].where((dep) => dep.source is HostedSource);
     for (final dep in declaredHostedDependencies) {
-      final resolvedPackage = resolvedPackages[dep.name];
-      assert(resolvedPackage != null);
-      if (resolvedPackage == null || !toUpgrade.contains(dep.name)) {
+      final resolvedPackage = resolvedPackages[dep.name]!;
+      if (!toUpgrade.contains(dep.name)) {
         // If we're not to upgrade this package, or it wasn't in the
         // resolution somehow, then we ignore it.
         continue;
@@ -313,29 +324,35 @@
       changes[dep] = dep.withConstraint(constraint);
     }
 
+    final newPubspecText = _updatePubspec(changes);
     if (_dryRun) {
       // Even if it is a dry run, run `acquireDependencies` so that the user
       // gets a report on changes.
       // TODO(jonasfj): Stop abusing Entrypoint.global for dry-run output
-      await Entrypoint.global(
-        Package.inMemory(nullsafetyPubspec),
-        entrypoint.lockFile,
+      await Entrypoint.inMemory(
+        Package.inMemory(Pubspec.parse(newPubspecText, cache.sources)),
         cache,
+        lockFile: entrypoint.lockFile,
         solveResult: solveResult,
       ).acquireDependencies(
-        SolveType.UPGRADE,
+        SolveType.upgrade,
         dryRun: true,
         precompile: _precompile,
+        analytics: null,
+        generateDotPackages: false,
       );
     } else {
-      await _updatePubspec(changes);
-
+      if (changes.isNotEmpty) {
+        writeTextFile(entrypoint.pubspecPath, newPubspecText);
+      }
       // TODO: Allow Entrypoint to be created with in-memory pubspec, so that
       //       we can show the changes in --dry-run mode. For now we only show
       //       the changes made to pubspec.yaml in dry-run mode.
       await Entrypoint(directory, cache).acquireDependencies(
-        SolveType.UPGRADE,
+        SolveType.upgrade,
         precompile: _precompile,
+        analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
     }
 
@@ -353,10 +370,9 @@
       ...entrypoint.root.pubspec.devDependencies.keys
     ];
     await Future.wait(directDeps.map((name) async {
-      final resolvedPackage = resolvedPackages[name];
-      assert(resolvedPackage != null);
+      final resolvedPackage = resolvedPackages[name]!;
 
-      final boundSource = resolvedPackage.source.bind(cache);
+      final boundSource = resolvedPackage.source!.bind(cache);
       final pubspec = await boundSource.describe(resolvedPackage);
       if (!pubspec.languageVersion.supportsNullSafety) {
         nonMigratedDirectDeps.add(name);
@@ -380,13 +396,11 @@
   }
 
   /// Updates `pubspec.yaml` with given [changes].
-  Future<void> _updatePubspec(
+  String _updatePubspec(
     Map<PackageRange, PackageRange> changes,
-  ) async {
+  ) {
     ArgumentError.checkNotNull(changes, 'changes');
 
-    if (changes.isEmpty) return;
-
     final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
     final deps = entrypoint.root.pubspec.dependencies.keys;
     final devDeps = entrypoint.root.pubspec.devDependencies.keys;
@@ -406,9 +420,7 @@
         );
       }
     }
-
-    /// Windows line endings are already handled by [yamlEditor]
-    writeTextFile(entrypoint.pubspecPath, yamlEditor.toString());
+    return yamlEditor.toString();
   }
 
   /// Outputs a summary of changes made to `pubspec.yaml`.
@@ -468,7 +480,7 @@
             return dep;
           }
 
-          final boundSource = dep.source.bind(cache);
+          final boundSource = dep.source!.bind(cache);
           final packages = await boundSource.getVersions(dep.toRef());
           packages.sort((a, b) => a.version.compareTo(b.version));
 
@@ -483,7 +495,9 @@
           }
 
           hasNoNullSafetyVersions.add(dep.name);
-          return null;
+          // This value is never used. We will throw an exception because
+          //`hasNonNullSafetyVersions` is not empty.
+          return dep.withConstraint(VersionConstraint.empty);
         }));
 
     final deps = _removeUpperConstraints(original.dependencies.values);
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index 2498eea..99a6a57 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
diff --git a/lib/src/command/version.dart b/lib/src/command/version.dart
index 51eeb8f..2ac96e7 100644
--- a/lib/src/command/version.dart
+++ b/lib/src/command/version.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../log.dart' as log;
 import '../sdk.dart';
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index 8bfa25b..7c8b041 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -2,20 +2,18 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
 import 'package:args/args.dart';
 import 'package:args/command_runner.dart';
 import 'package:path/path.dart' as p;
-import 'package:pub/src/command/dependency_services.dart';
 
 import 'command.dart' show PubTopLevel, lineLength;
 import 'command/add.dart';
 import 'command/build.dart';
 import 'command/cache.dart';
+import 'command/dependency_services.dart';
 import 'command/deps.dart';
 import 'command/downgrade.dart';
 import 'command/get.dart';
@@ -48,45 +46,53 @@
 
 class PubCommandRunner extends CommandRunner<int> implements PubTopLevel {
   @override
-  String get directory => _argResults['directory'];
+  String? get directory => argResults['directory'];
 
   @override
   bool get captureStackChains {
-    return _argResults['trace'] ||
-        _argResults['verbose'] ||
-        _argResults['verbosity'] == 'all';
+    return argResults['trace'] ||
+        argResults['verbose'] ||
+        argResults['verbosity'] == 'all';
   }
 
   @override
   Verbosity get verbosity {
-    switch (_argResults['verbosity']) {
+    switch (argResults['verbosity']) {
       case 'error':
-        return log.Verbosity.ERROR;
+        return log.Verbosity.error;
       case 'warning':
-        return log.Verbosity.WARNING;
+        return log.Verbosity.warning;
       case 'normal':
-        return log.Verbosity.NORMAL;
+        return log.Verbosity.normal;
       case 'io':
-        return log.Verbosity.IO;
+        return log.Verbosity.io;
       case 'solver':
-        return log.Verbosity.SOLVER;
+        return log.Verbosity.solver;
       case 'all':
-        return log.Verbosity.ALL;
+        return log.Verbosity.all;
       default:
         // No specific verbosity given, so check for the shortcut.
-        if (_argResults['verbose']) return log.Verbosity.ALL;
-        return log.Verbosity.NORMAL;
+        if (argResults['verbose']) return log.Verbosity.all;
+        if (runningFromTest) return log.Verbosity.testing;
+        return log.Verbosity.normal;
     }
   }
 
   @override
-  bool get trace => _argResults['trace'];
+  bool get trace => argResults['trace'];
 
-  ArgResults _argResults;
+  ArgResults? _argResults;
 
   /// The top-level options parsed by the command runner.
   @override
-  ArgResults get argResults => _argResults;
+  ArgResults get argResults {
+    final a = _argResults;
+    if (a == null) {
+      throw StateError(
+          'argResults cannot be used before Command.run is called.');
+    }
+    return a;
+  }
 
   @override
   String get usageFooter =>
@@ -152,7 +158,7 @@
   Future<int> run(Iterable<String> args) async {
     try {
       _argResults = parse(args);
-      return await runCommand(_argResults) ?? exit_codes.SUCCESS;
+      return await runCommand(argResults) ?? exit_codes.SUCCESS;
     } on UsageException catch (error) {
       log.exception(error);
       return exit_codes.USAGE;
@@ -160,7 +166,7 @@
   }
 
   @override
-  Future<int> runCommand(ArgResults topLevelResults) async {
+  Future<int?> runCommand(ArgResults topLevelResults) async {
     _checkDepsSynced();
 
     if (topLevelResults['version']) {
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index e498058..f4f8686 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -7,8 +7,7 @@
 import 'dart:io';
 
 import 'package:analyzer/dart/analysis/analysis_context.dart';
-import 'package:analyzer/dart/analysis/context_builder.dart';
-import 'package:analyzer/dart/analysis/context_locator.dart';
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.dart';
@@ -67,21 +66,22 @@
       modificationStamp: 0,
     );
 
+    var contextCollection = AnalysisContextCollection(
+      includedPaths: [path],
+      resourceProvider: resourceProvider,
+      sdkPath: getSdkPath(),
+    );
+
     // Add new contexts for the given path.
-    var contextLocator = ContextLocator(resourceProvider: resourceProvider);
-    var roots = contextLocator.locateRoots(includedPaths: [path]);
-    for (var root in roots) {
-      var contextRootPath = root.root.path;
+    for (var analysisContext in contextCollection.contexts) {
+      var contextRootPath = analysisContext.contextRoot.root.path;
 
       // If there is already a context for this context root path, keep it.
       if (_contexts.containsKey(contextRootPath)) {
         continue;
       }
 
-      var contextBuilder = ContextBuilder();
-      var context = contextBuilder.createContext(
-          contextRoot: root, sdkPath: getSdkPath());
-      _contexts[contextRootPath] = context;
+      _contexts[contextRootPath] = analysisContext;
     }
   }
 
@@ -94,7 +94,7 @@
   /// Throws [AnalyzerErrorGroup] is the file has parsing errors.
   CompilationUnit parse(String path) {
     path = p.normalize(p.absolute(path));
-    var parseResult = _getExistingSession(path).getParsedUnit2(path);
+    var parseResult = _getExistingSession(path).getParsedUnit(path);
     if (parseResult is ParsedUnitResult) {
       if (parseResult.errors.isNotEmpty) {
         throw AnalyzerErrorGroup(parseResult.errors);
@@ -146,11 +146,14 @@
   String toString() => errors.join('\n');
 }
 
-/// Precompiles the Dart executable at [executablePath] to a kernel file at
-/// [outputPath].
+/// Precompiles the Dart executable at [executablePath].
 ///
-/// This file is also cached at [incrementalDillOutputPath] which is used to
-/// initialize the compiler on future runs.
+/// If the compilation succeeds it is saved to a kernel file at [outputPath].
+///
+/// If compilation fails, the output is cached at [incrementalDillOutputPath].
+///
+/// Whichever of [incrementalDillOutputPath] and [outputPath] already exists is
+/// used to initialize the compiler run.
 ///
 /// The [packageConfigPath] should point at the package config file to be used
 /// for `package:` uri resolution.
@@ -158,39 +161,65 @@
 /// The [name] is used to describe the executable in logs and error messages.
 Future<void> precompile({
   required String executablePath,
-  required String incrementalDillOutputPath,
+  required String incrementalDillPath,
   required String name,
   required String outputPath,
   required String packageConfigPath,
 }) async {
   ensureDir(p.dirname(outputPath));
-  ensureDir(p.dirname(incrementalDillOutputPath));
+  ensureDir(p.dirname(incrementalDillPath));
+
   const platformDill = 'lib/_internal/vm_platform_strong.dill';
   final sdkRoot = p.relative(p.dirname(p.dirname(Platform.resolvedExecutable)));
-  var client = await FrontendServerClient.start(
-    executablePath,
-    incrementalDillOutputPath,
-    platformDill,
-    sdkRoot: sdkRoot,
-    packagesJson: packageConfigPath,
-    printIncrementalDependencies: false,
-  );
+  String? tempDir;
+  FrontendServerClient? client;
   try {
-    var result = await client.compile();
+    tempDir = createTempDir(p.dirname(incrementalDillPath), 'tmp');
+    // To avoid potential races we copy the incremental data to a temporary file
+    // for just this compilation.
+    final temporaryIncrementalDill =
+        p.join(tempDir, '${p.basename(incrementalDillPath)}.incremental.dill');
+    try {
+      if (fileExists(incrementalDillPath)) {
+        copyFile(incrementalDillPath, temporaryIncrementalDill);
+      } else if (fileExists(outputPath)) {
+        copyFile(outputPath, temporaryIncrementalDill);
+      }
+    } on FileSystemException {
+      // Not able to copy existing file, compilation will start from scratch.
+    }
+
+    client = await FrontendServerClient.start(
+      executablePath,
+      temporaryIncrementalDill,
+      platformDill,
+      sdkRoot: sdkRoot,
+      packagesJson: packageConfigPath,
+      printIncrementalDependencies: false,
+    );
+    final result = await client.compile();
 
     final highlightedName = log.bold(name);
     if (result?.errorCount == 0) {
       log.message('Built $highlightedName.');
-      await File(incrementalDillOutputPath).copy(outputPath);
+      // By using rename we ensure atomicity. An external observer will either
+      // see the old or the new snapshot.
+      renameFile(temporaryIncrementalDill, outputPath);
     } else {
-      // Don't leave partial results.
-      deleteEntry(outputPath);
+      // By using rename we ensure atomicity. An external observer will either
+      // see the old or the new snapshot.
+      renameFile(temporaryIncrementalDill, incrementalDillPath);
+      // If compilation failed we don't want to leave an incorrect snapshot.
+      tryDeleteEntry(outputPath);
 
       throw ApplicationException(
           log.yellow('Failed to build $highlightedName:\n') +
               (result?.compilerOutputLines.join('\n') ?? ''));
     }
   } finally {
-    client.kill();
+    client?.kill();
+    if (tempDir != null) {
+      tryDeleteEntry(tempDir);
+    }
   }
 }
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 8820c33..202580c 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -12,12 +10,12 @@
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
 
 import 'command_runner.dart';
 import 'dart.dart' as dart;
 import 'exceptions.dart';
 import 'executable.dart';
-import 'http.dart' as http;
 import 'io.dart';
 import 'language_version.dart';
 import 'lock_file.dart';
@@ -28,6 +26,7 @@
 import 'package_graph.dart';
 import 'package_name.dart';
 import 'packages_file.dart' as packages_file;
+import 'pub_embeddable_command.dart';
 import 'pubspec.dart';
 import 'sdk.dart';
 import 'solver.dart';
@@ -73,78 +72,81 @@
 /// but may be the entrypoint when you're running its tests.
 class Entrypoint {
   /// The root package this entrypoint is associated with.
+  ///
+  /// For a global package, this is the activated package.
   final Package root;
 
+  /// For a global package, this is the directory that the package is installed
+  /// in. Non-global packages have null.
+  final String? globalDir;
+
   /// The system-wide cache which caches packages that need to be fetched over
   /// the network.
   final SystemCache cache;
 
   /// Whether this entrypoint exists within the package cache.
-  bool get isCached => root.dir != null && p.isWithin(cache.rootDir, root.dir);
+  bool get isCached => !root.isInMemory && p.isWithin(cache.rootDir, root.dir);
 
   /// Whether this is an entrypoint for a globally-activated package.
-  final bool isGlobal;
+  // final bool isGlobal;
+  bool get isGlobal => globalDir != null;
 
   /// The lockfile for the entrypoint.
   ///
   /// If not provided to the entrypoint, it will be loaded lazily from disk.
-  LockFile get lockFile {
-    if (_lockFile != null) return _lockFile;
+  LockFile get lockFile => _lockFile ??= _loadLockFile();
 
+  LockFile _loadLockFile() {
     if (!fileExists(lockFilePath)) {
-      _lockFile = LockFile.empty();
+      return _lockFile = LockFile.empty();
     } else {
-      _lockFile = LockFile.load(lockFilePath, cache.sources);
+      return _lockFile = LockFile.load(lockFilePath, cache.sources);
     }
-
-    return _lockFile;
   }
 
-  LockFile _lockFile;
+  LockFile? _lockFile;
 
   /// The package graph for the application and all of its transitive
   /// dependencies.
   ///
   /// Throws a [DataError] if the `.dart_tool/package_config.json` file isn't
   /// up-to-date relative to the pubspec and the lockfile.
-  PackageGraph get packageGraph {
-    if (_packageGraph != null) return _packageGraph;
+  PackageGraph get packageGraph => _packageGraph ??= _createPackageGraph();
 
+  PackageGraph _createPackageGraph() {
     assertUpToDate();
     var packages = {
       for (var id in lockFile.packages.values) id.name: cache.load(id)
     };
     packages[root.name] = root;
 
-    _packageGraph = PackageGraph(this, lockFile, packages);
-    return _packageGraph;
+    return PackageGraph(this, lockFile, packages);
   }
 
-  PackageGraph _packageGraph;
+  PackageGraph? _packageGraph;
 
   /// Where the lock file and package configurations are to be found.
   ///
   /// Global packages (except those from path source)
   /// store these in the global cache.
-  String get _configRoot =>
-      isCached ? p.join(cache.rootDir, 'global_packages', root.name) : root.dir;
+  String? get _configRoot => isCached ? globalDir : root.dir;
 
   /// The path to the entrypoint's ".packages" file.
   ///
   /// This file is being slowly deprecated in favor of
   /// `.dart_tool/package_config.json`. Pub will still create it, but will
   /// not require it or make use of it within pub.
-  String get packagesFile => p.normalize(p.join(_configRoot, '.packages'));
+  String get packagesFile => p.normalize(p.join(_configRoot!, '.packages'));
 
   /// The path to the entrypoint's ".dart_tool/package_config.json" file.
   String get packageConfigFile =>
-      p.normalize(p.join(_configRoot, '.dart_tool', 'package_config.json'));
+      p.normalize(p.join(_configRoot!, '.dart_tool', 'package_config.json'));
 
   /// The path to the entrypoint package's pubspec.
   String get pubspecPath => p.normalize(root.path('pubspec.yaml'));
 
   /// The path to the entrypoint package's lockfile.
-  String get lockFilePath => p.normalize(p.join(_configRoot, 'pubspec.lock'));
+  String get lockFilePath => p.normalize(p.join(_configRoot!, 'pubspec.lock'));
 
   /// The path to the entrypoint package's `.dart_tool/pub` cache directory.
   ///
@@ -156,11 +158,7 @@
   /// but the configuration is stored at the package itself.
   String get cachePath {
     if (isGlobal) {
-      return p.join(
-        cache.rootDir,
-        'global_packages',
-        root.name,
-      );
+      return globalDir!;
     } else {
       var newPath = root.path('.dart_tool/pub');
       var oldPath = root.path('.pub');
@@ -177,15 +175,25 @@
   String get _incrementalDillsPath => p.join(cachePath, 'incremental');
 
   /// Loads the entrypoint from a package at [rootDir].
-  Entrypoint(String rootDir, this.cache)
-      : root = Package.load(null, rootDir, cache.sources),
-        isGlobal = false;
+  Entrypoint(
+    String rootDir,
+    this.cache,
+  )   : root = Package.load(null, rootDir, cache.sources),
+        globalDir = null;
+
+  Entrypoint.inMemory(this.root, this.cache,
+      {required LockFile? lockFile, SolveResult? solveResult})
+      : _lockFile = lockFile,
+        globalDir = null {
+    if (solveResult != null) {
+      _packageGraph = PackageGraph.fromSolveResult(this, solveResult);
+    }
+  }
 
   /// Creates an entrypoint given package and lockfile objects.
-  /// If a SolveResult is already created it can be passes as an optimization.
-  Entrypoint.global(this.root, this._lockFile, this.cache,
-      {SolveResult solveResult})
-      : isGlobal = true {
+  /// If a SolveResult is already created it can be passed as an optimization.
+  Entrypoint.global(this.globalDir, this.root, this._lockFile, this.cache,
+      {SolveResult? solveResult}) {
     if (solveResult != null) {
       _packageGraph = PackageGraph.fromSolveResult(this, solveResult);
     }
@@ -194,7 +202,7 @@
   /// Gets the [Entrypoint] package for the current working directory.
   ///
   /// This will be null if the example folder doesn't have a `pubspec.yaml`.
-  Entrypoint get example {
+  Entrypoint? get example {
     if (_example != null) return _example;
     if (!fileExists(root.path('example', 'pubspec.yaml'))) {
       return null;
@@ -202,22 +210,28 @@
     return _example = Entrypoint(root.path('example'), cache);
   }
 
-  Entrypoint _example;
+  Entrypoint? _example;
 
   /// Writes .packages and .dart_tool/package_config.json
-  Future<void> writePackagesFiles() async {
-    writeTextFile(
-        packagesFile,
-        lockFile.packagesFile(cache,
-            entrypoint: root.name, relativeFrom: root.dir));
+  Future<void> writePackagesFiles({bool generateDotPackages = false}) async {
+    final entrypointName = isGlobal ? null : root.name;
+    if (generateDotPackages) {
+      writeTextFile(
+          packagesFile,
+          lockFile.packagesFile(cache,
+              entrypoint: entrypointName,
+              relativeFrom: isGlobal ? null : root.dir));
+    } else {
+      tryDeleteEntry(packagesFile);
+    }
     ensureDir(p.dirname(packageConfigFile));
     writeTextFile(
         packageConfigFile,
         await lockFile.packageConfigFile(cache,
-            entrypoint: root.name,
+            entrypoint: entrypointName,
             entrypointSdkConstraint:
                 root.pubspec.sdkConstraints[sdk.identifier],
-            relativeFrom: root.dir));
+            relativeFrom: isGlobal ? null : root.dir));
   }
 
   /// Gets all dependencies of the [root] package.
@@ -244,28 +258,29 @@
   /// Updates [lockFile] and [packageRoot] accordingly.
   Future<void> acquireDependencies(
     SolveType type, {
-    Iterable<String> unlock,
+    Iterable<String>? unlock,
     bool dryRun = false,
     bool precompile = false,
+    required bool generateDotPackages,
+    required PubAnalytics? analytics,
     bool onlyReportSuccessOrFailure = false,
   }) async {
-    final suffix = root.dir == null || root.dir == '.' ? '' : ' in ${root.dir}';
+    final suffix = root.isInMemory || root.dir == '.' ? '' : ' in ${root.dir}';
     SolveResult result;
     try {
       result = await log.progress('Resolving dependencies$suffix', () async {
-        // We require an SDK constraint lower-bound as of Dart 2.12.0
-        _checkSdkConstraintIsDefined(root.pubspec);
+        _checkSdkConstraint(root.pubspec);
         return resolveVersions(
           type,
           cache,
           root,
           lockFile: lockFile,
-          unlock: unlock,
+          unlock: unlock ?? [],
         );
       });
     } catch (e) {
       if (onlyReportSuccessOrFailure && (e is ApplicationException)) {
-        final directoryOption = root.dir == null || root.dir == '.'
+        final directoryOption = root.isInMemory || root.dir == '.'
             ? ''
             : ' --directory ${root.dir}';
         throw ApplicationException(
@@ -276,7 +291,7 @@
     }
 
     // Log once about all overridden packages.
-    if (warnAboutPreReleaseSdkOverrides && result.pubspecs != null) {
+    if (warnAboutPreReleaseSdkOverrides) {
       var overriddenPackages = (result.pubspecs.values
               .where((pubspec) => pubspec.dartSdkWasOverridden)
               .map((pubspec) => pubspec.name)
@@ -297,7 +312,7 @@
       await result.showReport(type, cache);
     }
     if (!dryRun) {
-      await Future.wait(result.packages.map(_get));
+      await result.downloadCachedPackages(cache);
       _saveLockFile(result);
     }
     if (onlyReportSuccessOrFailure) {
@@ -307,15 +322,19 @@
     }
 
     if (!dryRun) {
+      if (analytics != null) {
+        result.sendAnalytics(analytics);
+      }
+
       /// Build a package graph from the version solver results so we don't
       /// have to reload and reparse all the pubspecs.
       _packageGraph = PackageGraph.fromSolveResult(this, result);
 
-      await writePackagesFiles();
+      await writePackagesFiles(generateDotPackages: generateDotPackages);
 
       try {
         if (precompile) {
-          await precompileExecutables(changed: result.changedPackages);
+          await precompileExecutables();
         } else {
           _deleteExecutableSnapshots(changed: result.changedPackages);
         }
@@ -345,7 +364,7 @@
       }
     }
     final r = root.immediateDependencies.keys.expand((packageName) {
-      final package = packageGraph.packages[packageName];
+      final package = packageGraph.packages[packageName]!;
       return package.executablePaths
           .map((path) => Executable(packageName, path));
     }).toList();
@@ -353,7 +372,7 @@
   }
 
   /// Precompiles all [_builtExecutables].
-  Future<void> precompileExecutables({Iterable<String> changed}) async {
+  Future<void> precompileExecutables() async {
     migrateCache();
 
     final executables = _builtExecutables;
@@ -378,7 +397,7 @@
 
   /// Precompiles executable .dart file at [path] to a snapshot.
   Future<void> precompileExecutable(Executable executable) async {
-    return await log.progress('Building package executable', () async {
+    await log.progress('Building package executable', () async {
       ensureDir(p.dirname(pathOfExecutable(executable)));
       return waitAndPrintErrors([_precompileExecutable(executable)]);
     });
@@ -386,10 +405,11 @@
 
   Future<void> _precompileExecutable(Executable executable) async {
     final package = executable.package;
+
     await dart.precompile(
         executablePath: resolveExecutable(executable),
         outputPath: pathOfExecutable(executable),
-        incrementalDillOutputPath: incrementalDillPathOfExecutable(executable),
+        incrementalDillPath: incrementalDillPathOfExecutable(executable),
         packageConfigPath: packageConfigFile,
         name:
             '$package:${p.basenameWithoutExtension(executable.relativePath)}');
@@ -424,7 +444,7 @@
   /// The absolute path of [executable] resolved relative to [this].
   String resolveExecutable(Executable executable) {
     return p.join(
-      packageGraph.packages[executable.package].dir,
+      packageGraph.packages[executable.package]!.dir,
       executable.relativePath,
     );
   }
@@ -433,7 +453,7 @@
   ///
   /// If [changed] is passed, only dependencies whose contents might be changed
   /// if one of the given packages changes will have their executables deleted.
-  void _deleteExecutableSnapshots({Iterable<String> changed}) {
+  void _deleteExecutableSnapshots({Iterable<String>? changed}) {
     if (!dirExists(_snapshotPath)) return;
 
     // If we don't know what changed, we can't safely re-use any snapshots.
@@ -441,7 +461,8 @@
       deleteEntry(_snapshotPath);
       return;
     }
-    changed = changed.toSet();
+    var changedDeps = changed;
+    changedDeps = changedDeps.toSet();
 
     // If the existing executable was compiled with a different SDK, we need to
     // recompile regardless of what changed.
@@ -462,27 +483,12 @@
           packageGraph.isPackageMutable(package) ||
           packageGraph
               .transitiveDependencies(package)
-              .any((dep) => changed.contains(dep.name))) {
+              .any((dep) => changedDeps.contains(dep.name))) {
         deleteEntry(entry);
       }
     }
   }
 
-  /// Makes sure the package at [id] is locally available.
-  ///
-  /// This automatically downloads the package to the system-wide cache as well
-  /// if it requires network access to retrieve (specifically, if the package's
-  /// source is a [CachedSource]).
-  Future<void> _get(PackageId id) async {
-    return await http.withDependencyType(root.dependencyType(id.name),
-        () async {
-      if (id.isRoot) return;
-
-      var source = cache.source(id.source);
-      if (source is CachedSource) await source.downloadToSystemCache(id);
-    });
-  }
-
   /// Throws a [DataError] if the `.dart_tool/package_config.json` file doesn't
   /// exist or if it's out-of-date relative to the lockfile or the pubspec.
   ///
@@ -555,8 +561,8 @@
     }
 
     for (var match in _sdkConstraint.allMatches(lockFileText)) {
-      var identifier = match[1] == 'sdk' ? 'dart' : match[1].trim();
-      var sdk = sdks[identifier];
+      var identifier = match[1] == 'sdk' ? 'dart' : match[1]!.trim();
+      var sdk = sdks[identifier]!;
 
       // Don't complain if there's an SDK constraint for an unavailable SDK. For
       // example, the Flutter SDK being unavailable just means that we aren't
@@ -564,8 +570,8 @@
       // able to `pub run` non-Flutter tools even in a Flutter app.
       if (!sdk.isAvailable) continue;
 
-      var parsedConstraint = VersionConstraint.parse(match[2]);
-      if (!parsedConstraint.allows(sdk.version)) {
+      var parsedConstraint = VersionConstraint.parse(match[2]!);
+      if (!parsedConstraint.allows(sdk.version!)) {
         dataError('${sdk.name} ${sdk.version} is incompatible with your '
             "dependencies' SDK constraints. Please run \"$topLevelProgram pub get\" again.");
       }
@@ -709,7 +715,7 @@
 
     final packagePathsMapping = <String, String>{};
     for (final package in packages.keys) {
-      final packageUri = packages[package];
+      final packageUri = packages[package]!;
 
       // Pub only generates "file:" and relative URIs.
       if (packageUri.scheme != 'file' && packageUri.scheme.isNotEmpty) {
@@ -747,7 +753,7 @@
           '"pub" version, please run "$topLevelProgram pub get".');
     }
 
-    String packageConfigRaw;
+    late String packageConfigRaw;
     try {
       packageConfigRaw = readTextFile(packageConfigFile);
     } on FileException {
@@ -755,7 +761,7 @@
           'The "$packageConfigFile" file does not exist, please run "$topLevelProgram pub get".');
     }
 
-    PackageConfig cfg;
+    late PackageConfig cfg;
     try {
       cfg = PackageConfig.fromJson(json.decode(packageConfigRaw));
     } on FormatException {
@@ -844,7 +850,7 @@
     final windowsLineEndings = fileExists(lockFilePath) &&
         detectWindowsLineEndings(readTextFile(lockFilePath));
 
-    final serialized = _lockFile.serialize(root.dir);
+    final serialized = lockFile.serialize(root.dir);
     writeTextFile(lockFilePath,
         windowsLineEndings ? serialized.replaceAll('\n', '\r\n') : serialized);
   }
@@ -855,7 +861,7 @@
     // Cached packages don't have these.
     if (isCached) return;
 
-    var oldPath = p.join(_configRoot, '.pub');
+    var oldPath = p.join(_configRoot!, '.pub');
     if (!dirExists(oldPath)) return;
 
     var newPath = root.path('.dart_tool/pub');
@@ -870,10 +876,11 @@
   }
 
   /// We require an SDK constraint lower-bound as of Dart 2.12.0
-  void _checkSdkConstraintIsDefined(Pubspec pubspec) {
+  ///
+  /// We don't allow unknown sdks.
+  void _checkSdkConstraint(Pubspec pubspec) {
     final dartSdkConstraint = pubspec.sdkConstraints['dart'];
-    if (dartSdkConstraint is! VersionRange ||
-        (dartSdkConstraint is VersionRange && dartSdkConstraint.min == null)) {
+    if (dartSdkConstraint is! VersionRange || dartSdkConstraint.min == null) {
       // Suggest version range '>=2.10.0 <3.0.0', we avoid using:
       // [CompatibleWithVersionRange] because some pub versions don't support
       // caret syntax (e.g. '^2.10.0')
@@ -901,6 +908,24 @@
 See https://dart.dev/go/sdk-constraint
 ''');
     }
+    for (final sdk in pubspec.sdkConstraints.keys) {
+      if (!sdks.containsKey(sdk)) {
+        final environment = pubspec.fields.nodes['environment'] as YamlMap;
+        final keyNode = environment.nodes.entries
+            .firstWhere((e) => (e.key as YamlNode).value == sdk)
+            .key as YamlNode;
+        throw PubspecException('''
+$pubspecPath refers to an unknown sdk '$sdk'.
+
+Did you mean to add it as a dependency?
+
+Either remove the constraint, or upgrade to a version of pub that supports the
+given sdk.
+
+See https://dart.dev/go/sdk-constraint
+''', keyNode.span);
+      }
+    }
   }
 }
 
diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart
index 82a5dac..fcde747 100644
--- a/lib/src/exceptions.dart
+++ b/lib/src/exceptions.dart
@@ -11,7 +11,6 @@
 import 'package:yaml/yaml.dart';
 
 import 'dart.dart';
-import 'sdk.dart';
 
 /// An exception class for exceptions that are intended to be seen by the user.
 ///
@@ -89,18 +88,20 @@
 /// that other code in pub can use this to show a more detailed explanation of
 /// why the package was being requested.
 class PackageNotFoundException extends WrappedException {
-  /// If this failure was caused by an SDK being unavailable, this is that SDK.
-  final Sdk? missingSdk;
+  /// A hint indicating an action the user could take to resolve this problem.
+  ///
+  /// This will be printed after the package resolution conflict.
+  final String? hint;
 
   PackageNotFoundException(
     String message, {
     Object? innerError,
     StackTrace? innerTrace,
-    this.missingSdk,
+    this.hint,
   }) : super(message, innerError, innerTrace);
 
   @override
-  String toString() => "Package doesn't exist ($message).";
+  String toString() => 'Package not available ($message).';
 }
 
 /// Returns whether [error] is a user-facing error object.
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 08722e5..3f8f6a9 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 import 'dart:isolate';
@@ -19,12 +17,12 @@
 import 'isolate.dart' as isolate;
 import 'log.dart' as log;
 import 'log.dart';
+import 'pub_embeddable_command.dart';
 import 'solver/type.dart';
 import 'system_cache.dart';
 import 'utils.dart';
 
-/// Code shared between `run` `global run` and `run --dartdev` for extracting
-/// vm arguments from arguments.
+/// Extracting vm arguments from arguments.
 List<String> vmArgsFromArgResults(ArgResults argResults) {
   final experiments = argResults['enable-experiment'] as List;
   return [
@@ -50,11 +48,11 @@
 ///
 /// Returns the exit code of the spawned app.
 Future<int> runExecutable(
-    Entrypoint entrypoint, Executable executable, Iterable<String> args,
+    Entrypoint entrypoint, Executable executable, List<String> args,
     {bool enableAsserts = false,
-    Future<void> Function(Executable) recompile,
+    required Future<void> Function(Executable) recompile,
     List<String> vmArgs = const [],
-    @required bool alwaysUseSubprocess}) async {
+    required bool alwaysUseSubprocess}) async {
   final package = executable.package;
 
   // Make sure the package is an immediate dependency of the entrypoint or the
@@ -103,18 +101,7 @@
       await recompile(executable);
     }
     executablePath = snapshotPath;
-  } else {
-    if (executablePath == null) {
-      var message =
-          'Could not find ${log.bold(p.normalize(executable.relativePath))}';
-      if (entrypoint.isGlobal || package != entrypoint.root.name) {
-        message += ' in package ${log.bold(package)}';
-      }
-      log.error('$message.');
-      return exit_codes.NO_INPUT;
-    }
   }
-
   // We use an absolute path here not because the VM insists but because it's
   // helpful for the subprocess to be able to spawn Dart with
   // Platform.executableArguments and have that work regardless of the working
@@ -124,7 +111,7 @@
   try {
     return await _runDartProgram(
       executablePath,
-      args,
+      args.toList(),
       packageConfigAbsolute,
       enableAsserts: enableAsserts,
       vmArgs: vmArgs,
@@ -140,7 +127,7 @@
     await recompile(executable);
     return await _runDartProgram(
       executablePath,
-      args,
+      args.toList(),
       packageConfigAbsolute,
       enableAsserts: enableAsserts,
       vmArgs: vmArgs,
@@ -165,9 +152,9 @@
 /// a new process will always be started.
 Future<int> _runDartProgram(
     String path, List<String> args, String packageConfig,
-    {bool enableAsserts,
-    List<String> vmArgs,
-    @required bool alwaysUseSubprocess}) async {
+    {bool enableAsserts = false,
+    List<String> vmArgs = const <String>[],
+    required bool alwaysUseSubprocess}) async {
   path = p.absolute(path);
   packageConfig = p.absolute(packageConfig);
 
@@ -175,10 +162,8 @@
   // That provides better signal handling, and possibly faster startup.
   if ((!alwaysUseSubprocess) && vmArgs.isEmpty) {
     var argList = args.toList();
-    return await isolate.runUri(p.toUri(path), argList, null,
-        enableAsserts: enableAsserts,
-        automaticPackageResolution: packageConfig == null,
-        packageConfig: p.toUri(packageConfig));
+    return await isolate.runUri(p.toUri(path), argList, '',
+        enableAsserts: enableAsserts, packageConfig: p.toUri(packageConfig));
   } else {
     // By ignoring sigint, only the child process will get it when
     // they are sent to the current process group. That is what happens when
@@ -218,7 +203,23 @@
   }
 }
 
-/// Returns the path to dart program/snapshot to invoke for running [descriptor]
+/// The result of a `getExecutableForCommand` command resolution.
+@sealed
+class DartExecutableWithPackageConfig {
+  /// Can be a .dart file or a incremental snapshot.
+  final String executable;
+
+  /// The package_config.json to run [executable] with. Or <null> if the VM
+  /// should find it according to the standard rules.
+  final String? packageConfig;
+
+  DartExecutableWithPackageConfig({
+    required this.executable,
+    required this.packageConfig,
+  });
+}
+
+/// Returns the dart program/snapshot to invoke for running [descriptor]
 /// resolved according to the package configuration of the package at [root]
 /// (defaulting to the current working directory). Using the pub-cache at
 /// [pubCacheDir] (defaulting to the default pub cache).
@@ -236,7 +237,9 @@
 ///  [CommandResolutionFailedException].
 ///
 /// * Otherwise if the current package resolution is outdated do an implicit
-/// `pub get`, if that fails, throw a [CommandResolutionFailedException].
+///   `pub get`, if that fails, throw a [CommandResolutionFailedException].
+///
+///   This pub get will send analytics events to [analytics] if provided.
 ///
 /// * Otherwise let  `<current>` be the name of the package at [root], and
 ///   interpret [descriptor] as `[<package>][:<command>]`.
@@ -251,7 +254,7 @@
 /// * `` and `:` both resolves to `<current>:bin/<current>.dart` or
 ///   `bin/<current>:main.dart`.
 ///
-/// If that doesn't resolve as an existing file throw an exception.
+/// If that doesn't resolve as an existing file, throw an exception.
 ///
 /// ## Snapshotting
 ///
@@ -264,11 +267,12 @@
 ///
 /// Throws an [CommandResolutionFailedException] if the command is not found or
 /// if the entrypoint is not up to date (requires `pub get`) and a `pub get`.
-Future<String> getExecutableForCommand(
+Future<DartExecutableWithPackageConfig> getExecutableForCommand(
   String descriptor, {
   bool allowSnapshot = true,
-  String root,
-  String pubCacheDir,
+  String? root,
+  String? pubCacheDir,
+  PubAnalytics? analytics,
 }) async {
   root ??= p.current;
   var asPath = descriptor;
@@ -283,67 +287,128 @@
   }
 
   final asDirectFile = p.join(root, asPath);
-  if (fileExists(asDirectFile)) return p.relative(asDirectFile, from: root);
-  if (!fileExists(p.join(root, 'pubspec.yaml'))) {
-    throw CommandResolutionFailedException('Could not find file `$descriptor`');
+  if (fileExists(asDirectFile)) {
+    return DartExecutableWithPackageConfig(
+      executable: p.relative(asDirectFile, from: root),
+      packageConfig: null,
+    );
   }
+  if (!fileExists(p.join(root, 'pubspec.yaml'))) {
+    throw CommandResolutionFailedException._(
+        'Could not find file `$descriptor`',
+        CommandResolutionIssue.fileNotFound);
+  }
+  final entrypoint = Entrypoint(root, SystemCache(rootDir: pubCacheDir));
   try {
-    final entrypoint = Entrypoint(root, SystemCache(rootDir: pubCacheDir));
+    // TODO(sigurdm): it would be nicer with a 'isUpToDate' function.
+    entrypoint.assertUpToDate();
+  } on DataException {
     try {
-      // TODO(sigurdm): it would be nicer with a 'isUpToDate' function.
-      entrypoint.assertUpToDate();
-    } on DataException {
       await warningsOnlyUnlessTerminal(
-          () => entrypoint.acquireDependencies(SolveType.GET));
+        () => entrypoint.acquireDependencies(
+          SolveType.get,
+          analytics: analytics,
+          generateDotPackages: false,
+        ),
+      );
+    } on ApplicationException catch (e) {
+      throw CommandResolutionFailedException._(
+          e.toString(), CommandResolutionIssue.pubGetFailed);
     }
+  }
 
-    String command;
-    String package;
-    if (descriptor.contains(':')) {
-      final parts = descriptor.split(':');
-      if (parts.length > 2) {
-        throw CommandResolutionFailedException(
-            '[<package>[:command]] cannot contain multiple ":"');
-      }
-      package = parts[0];
-      if (package.isEmpty) package = entrypoint.root.name;
-      command = parts[1];
-    } else {
-      package = descriptor;
-      if (package.isEmpty) package = entrypoint.root.name;
-      command = package;
+  late final String command;
+  String package;
+  if (descriptor.contains(':')) {
+    final parts = descriptor.split(':');
+    if (parts.length > 2) {
+      throw CommandResolutionFailedException._(
+        '[<package>[:command]] cannot contain multiple ":"',
+        CommandResolutionIssue.parseError,
+      );
     }
+    package = parts[0];
+    if (package.isEmpty) package = entrypoint.root.name;
+    command = parts[1];
+  } else {
+    package = descriptor;
+    if (package.isEmpty) package = entrypoint.root.name;
+    command = package;
+  }
 
-    final executable = Executable(package, p.join('bin', '$command.dart'));
-    if (!entrypoint.packageGraph.packages.containsKey(package)) {
-      throw CommandResolutionFailedException(
-          'Could not find package `$package` or file `$descriptor`');
-    }
-    final path = entrypoint.resolveExecutable(executable);
-    if (!fileExists(path)) {
-      throw CommandResolutionFailedException(
-          'Could not find `bin${p.separator}$command.dart` in package `$package`.');
-    }
-    if (!allowSnapshot) {
-      return p.relative(path, from: root);
-    } else {
-      final snapshotPath = entrypoint.pathOfExecutable(executable);
-      if (!fileExists(snapshotPath) ||
-          entrypoint.packageGraph.isPackageMutable(package)) {
+  if (!entrypoint.packageGraph.packages.containsKey(package)) {
+    throw CommandResolutionFailedException._(
+      'Could not find package `$package` or file `$descriptor`',
+      CommandResolutionIssue.packageNotFound,
+    );
+  }
+  final executable = Executable(package, p.join('bin', '$command.dart'));
+  final packageConfig = p.join('.dart_tool', 'package_config.json');
+
+  final path = entrypoint.resolveExecutable(executable);
+  if (!fileExists(path)) {
+    throw CommandResolutionFailedException._(
+      'Could not find `bin${p.separator}$command.dart` in package `$package`.',
+      CommandResolutionIssue.noBinaryFound,
+    );
+  }
+  if (!allowSnapshot) {
+    return DartExecutableWithPackageConfig(
+      executable: p.relative(path, from: root),
+      packageConfig: packageConfig,
+    );
+  } else {
+    final snapshotPath = entrypoint.pathOfExecutable(executable);
+    if (!fileExists(snapshotPath) ||
+        entrypoint.packageGraph.isPackageMutable(package)) {
+      try {
         await warningsOnlyUnlessTerminal(
           () => entrypoint.precompileExecutable(executable),
         );
+      } on ApplicationException catch (e) {
+        throw CommandResolutionFailedException._(
+          e.toString(),
+          CommandResolutionIssue.compilationFailed,
+        );
       }
-      return p.relative(snapshotPath, from: root);
     }
-  } on ApplicationException catch (e) {
-    throw CommandResolutionFailedException(e.toString());
+    return DartExecutableWithPackageConfig(
+      executable: p.relative(snapshotPath, from: root),
+      packageConfig: packageConfig,
+    );
   }
 }
 
+/// Information on why no executable is returned.
+enum CommandResolutionIssue {
+  /// The command string looked like a file (contained '.' '/' or '\\'), but no
+  /// such file exists.
+  fileNotFound,
+
+  /// The command-string was '<package>:<binary>' or '<package>', and <package>
+  /// was not in dependencies.
+  packageNotFound,
+
+  /// The command string was '<package>:<binary>' or ':<binary>' and <binary>
+  /// was not found.
+  noBinaryFound,
+
+  /// Failed retrieving dependencies (pub get).
+  pubGetFailed,
+
+  /// Pre-compilation of the binary failed.
+  compilationFailed,
+
+  /// The command string did not have a valid form (eg. more than one ':').
+  parseError,
+}
+
+/// Indicates that a command string did not resolve to an executable.
+@sealed
 class CommandResolutionFailedException implements Exception {
   final String message;
-  CommandResolutionFailedException(this.message);
+  final CommandResolutionIssue issue;
+  CommandResolutionFailedException._(this.message, this.issue);
 
   @override
   String toString() {
diff --git a/lib/src/exit_codes.dart b/lib/src/exit_codes.dart
index e072ef2..c6acaf7 100644
--- a/lib/src/exit_codes.dart
+++ b/lib/src/exit_codes.dart
@@ -2,6 +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.
 
+// ignore_for_file: constant_identifier_names
+
 /// Exit code constants.
 ///
 /// From [the BSD sysexits manpage][manpage]. Not every constant here is used,
diff --git a/lib/src/feature.dart b/lib/src/feature.dart
index ca98bc1..94134ab 100644
--- a/lib/src/feature.dart
+++ b/lib/src/feature.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
@@ -41,7 +39,7 @@
     void enableFeature(Feature feature) {
       if (!enabledFeatures.add(feature)) return;
       for (var require in feature.requires) {
-        enableFeature(features[require]);
+        enableFeature(features[require]!);
       }
     }
 
@@ -57,8 +55,8 @@
   }
 
   Feature(this.name, Iterable<PackageRange> dependencies,
-      {Iterable<String> requires,
-      Map<String, VersionConstraint> sdkConstraints,
+      {Iterable<String>? requires,
+      Map<String, VersionConstraint>? sdkConstraints,
       this.onByDefault = true})
       : dependencies = UnmodifiableListView(dependencies.toList()),
         requires = requires == null
diff --git a/lib/src/git.dart b/lib/src/git.dart
index 8c86390..5fa7536 100644
--- a/lib/src/git.dart
+++ b/lib/src/git.dart
@@ -2,11 +2,11 @@
 // 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.
 
-// @dart=2.10
-
 /// Helper functionality for invoking Git.
 import 'dart:async';
 
+import 'package:path/path.dart' as p;
+
 import 'exceptions.dart';
 import 'io.dart';
 import 'log.dart' as log;
@@ -47,7 +47,7 @@
 /// Returns the stdout as a list of strings if it succeeded. Completes to an
 /// exception if it failed.
 Future<List<String>> run(List<String> args,
-    {String workingDir, Map<String, String> environment}) async {
+    {String? workingDir, Map<String, String>? environment}) async {
   if (!isInstalled) {
     fail('Cannot find a Git executable.\n'
         'Please ensure Git is correctly installed.');
@@ -55,7 +55,7 @@
 
   log.muteProgress();
   try {
-    var result = await runProcess(command, args,
+    final result = await runProcess(command!, args,
         workingDir: workingDir,
         environment: {...?environment, 'LANG': 'en_GB'});
     if (!result.success) {
@@ -70,13 +70,13 @@
 
 /// Like [run], but synchronous.
 List<String> runSync(List<String> args,
-    {String workingDir, Map<String, String> environment}) {
+    {String? workingDir, Map<String, String>? environment}) {
   if (!isInstalled) {
     fail('Cannot find a Git executable.\n'
         'Please ensure Git is correctly installed.');
   }
 
-  var result = runProcessSync(command, args,
+  final result = runProcessSync(command!, args,
       workingDir: workingDir, environment: environment);
   if (!result.success) {
     throw GitException(args, result.stdout.join('\n'), result.stderr.join('\n'),
@@ -88,7 +88,7 @@
 
 /// Returns the name of the git command-line app, or `null` if Git could not be
 /// found on the user's PATH.
-String get command {
+String? get command {
   if (_commandCache != null) return _commandCache;
 
   if (_tryGitCommand('git')) {
@@ -103,7 +103,23 @@
   return _commandCache;
 }
 
-String _commandCache;
+String? _commandCache;
+
+/// Returns the root of the git repo [dir] belongs to. Returns `null` if not
+/// in a git repo or git is not installed.
+String? repoRoot(String dir) {
+  if (isInstalled) {
+    try {
+      return p.normalize(
+        runSync(['rev-parse', '--show-toplevel'], workingDir: dir).first,
+      );
+    } on GitException {
+      // Not in a git folder.
+      return null;
+    }
+  }
+  return null;
+}
 
 /// Checks whether [command] is the Git command for this computer.
 bool _tryGitCommand(String command) {
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 49d681f..07e2513 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -2,12 +2,9 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -15,14 +12,15 @@
 import 'entrypoint.dart';
 import 'exceptions.dart';
 import 'executable.dart' as exec;
-import 'http.dart' as http;
 import 'io.dart';
 import 'lock_file.dart';
 import 'log.dart' as log;
 import 'package.dart';
 import 'package_name.dart';
+import 'pub_embeddable_command.dart';
 import 'pubspec.dart';
 import 'sdk.dart';
+import 'sdk/dart.dart';
 import 'solver.dart';
 import 'solver/incompatibility_cause.dart';
 import 'source/cached.dart';
@@ -63,6 +61,8 @@
   /// The directory where the lockfiles for activated packages are stored.
   String get _directory => p.join(cache.rootDir, 'global_packages');
 
+  String _packageDir(String packageName) => p.join(_directory, packageName);
+
   /// The directory where binstubs for global package executables are stored.
   String get _binStubDir => p.join(cache.rootDir, 'bin');
 
@@ -85,8 +85,9 @@
   /// If [overwriteBinStubs] is `true`, any binstubs that collide with
   /// existing binstubs in other packages will be overwritten by this one's.
   /// Otherwise, the previous ones will be preserved.
-  Future<void> activateGit(String repo, List<String> executables,
-      {Map<String, FeatureDependency> features, bool overwriteBinStubs}) async {
+  Future<void> activateGit(String repo, List<String>? executables,
+      {Map<String, FeatureDependency>? features,
+      required bool overwriteBinStubs}) async {
     var name = await cache.git.getPackageNameFromRepo(repo);
 
     // TODO(nweiz): Add some special handling for git repos that contain path
@@ -121,10 +122,10 @@
   /// [url] is an optional custom pub server URL. If not null, the package to be
   /// activated will be fetched from this URL instead of the default pub URL.
   Future<void> activateHosted(
-      String name, VersionConstraint constraint, List<String> executables,
-      {Map<String, FeatureDependency> features,
-      bool overwriteBinStubs,
-      Uri url}) async {
+      String name, VersionConstraint constraint, List<String>? executables,
+      {Map<String, FeatureDependency>? features,
+      required bool overwriteBinStubs,
+      Uri? url}) async {
     await _installInCache(
         cache.hosted.source
             .refFor(name, url: url)
@@ -143,33 +144,31 @@
   /// if [overwriteBinStubs] is `true`, any binstubs that collide with
   /// existing binstubs in other packages will be overwritten by this one's.
   /// Otherwise, the previous ones will be preserved.
-  Future<void> activatePath(String path, List<String> executables,
-      {bool overwriteBinStubs}) async {
+  Future<void> activatePath(String path, List<String>? executables,
+      {required bool overwriteBinStubs,
+      required PubAnalytics? analytics}) async {
     var entrypoint = Entrypoint(path, cache);
 
     // Get the package's dependencies.
-    await entrypoint.acquireDependencies(SolveType.GET);
+    await entrypoint.acquireDependencies(
+      SolveType.get,
+      analytics: analytics,
+      generateDotPackages: false,
+    );
     var name = entrypoint.root.name;
-
-    try {
-      var originalLockFile =
-          LockFile.load(_getLockFilePath(name), cache.sources);
-      // Call this just to log what the current active package is, if any.
-      _describeActive(originalLockFile, name);
-    } on IOException {
-      // Couldn't read the lock file. It probably doesn't exist.
-    }
+    _describeActive(name, cache);
 
     // Write a lockfile that points to the local package.
     var fullPath = canonicalize(entrypoint.root.dir);
     var id = cache.path.source.idFor(name, entrypoint.root.version, fullPath);
 
+    final tempDir = cache.createTempDir();
     // TODO(rnystrom): Look in "bin" and display list of binaries that
     // user can run.
-    _writeLockFile(name, LockFile([id]));
+    _writeLockFile(tempDir, LockFile([id]));
 
-    var binDir = p.join(_directory, name, 'bin');
-    if (dirExists(binDir)) deleteEntry(binDir);
+    tryDeleteEntry(_packageDir(name));
+    tryRenameDir(tempDir, _packageDir(name));
 
     _updateBinStubs(entrypoint, entrypoint.root, executables,
         overwriteBinStubs: overwriteBinStubs);
@@ -177,17 +176,12 @@
   }
 
   /// Installs the package [dep] and its dependencies into the system cache.
-  Future<void> _installInCache(PackageRange dep, List<String> executables,
-      {bool overwriteBinStubs}) async {
-    LockFile originalLockFile;
-    try {
-      originalLockFile =
-          LockFile.load(_getLockFilePath(dep.name), cache.sources);
-      // Call this just to log what the current active package is, if any.
-      _describeActive(originalLockFile, dep.name);
-    } on IOException {
-      // Couldn't read the lock file. It probably doesn't exist.
-    }
+  ///
+  /// If [silent] less logging will be printed.
+  Future<void> _installInCache(PackageRange dep, List<String>? executables,
+      {required bool overwriteBinStubs, bool silent = false}) async {
+    final name = dep.name;
+    LockFile? originalLockFile = _describeActive(name, cache);
 
     // Create a dummy package with just [dep] so we can do resolution on it.
     var root = Package.inMemory(Pubspec('pub global activate',
@@ -199,104 +193,90 @@
     // being available, report that as a [dataError].
     SolveResult result;
     try {
-      result = await log.progress('Resolving dependencies',
-          () => resolveVersions(SolveType.GET, cache, root));
+      result = await log.spinner(
+        'Resolving dependencies',
+        () => resolveVersions(SolveType.get, cache, root),
+        condition: !silent,
+      );
     } on SolveFailure catch (error) {
       for (var incompatibility
           in error.incompatibility.externalIncompatibilities) {
         if (incompatibility.cause != IncompatibilityCause.noVersions) continue;
-        if (incompatibility.terms.single.package.name != dep.name) continue;
+        if (incompatibility.terms.single.package.name != name) continue;
         dataError(error.toString());
       }
       rethrow;
     }
+    // We want the entrypoint to be rooted at 'dep' not the dummy-package.
+    result.packages.removeWhere((id) => id.name == 'pub global activate');
 
     final sameVersions = originalLockFile != null &&
         originalLockFile.samePackageIds(result.lockFile);
 
+    final PackageId id = result.lockFile.packages[name]!;
     if (sameVersions) {
       log.message('''
-The package ${dep.name} is already activated at newest available version.
-To recompile executables, first run `global deactivate ${dep.name}`.
+The package $name is already activated at newest available version.
+To recompile executables, first run `$topLevelProgram pub global deactivate $name`.
 ''');
     } else {
-      await result.showReport(SolveType.GET, cache);
+      // Only precompile binaries if we have a new resolution.
+      if (!silent) await result.showReport(SolveType.get, cache);
+
+      await result.downloadCachedPackages(cache);
+
+      final lockFile = result.lockFile;
+      final tempDir = cache.createTempDir();
+      _writeLockFile(tempDir, lockFile);
+
+      // Load the package graph from [result] so we don't need to re-parse all
+      // the pubspecs.
+      final entrypoint = Entrypoint.global(
+        tempDir,
+        cache.loadCached(id),
+        lockFile,
+        cache,
+        solveResult: result,
+      );
+
+      await entrypoint.writePackagesFiles();
+
+      await entrypoint.precompileExecutables();
+
+      tryDeleteEntry(_packageDir(name));
+      tryRenameDir(tempDir, _packageDir(name));
     }
-
-    // Make sure all of the dependencies are locally installed.
-    await Future.wait(result.packages.map((id) {
-      return http.withDependencyType(root.dependencyType(id.name), () async {
-        if (id.isRoot) return;
-
-        var source = cache.source(id.source);
-        if (source is CachedSource) await source.downloadToSystemCache(id);
-      });
-    }));
-
-    var lockFile = result.lockFile;
-    _writeLockFile(dep.name, lockFile);
-    await _writePackageConfigFiles(dep.name, lockFile);
-
-    // We want the entrypoint to be rooted at 'dep' not the dummy-package.
-    result.packages.removeWhere((id) => id.name == 'pub global activate');
-
-    var id = lockFile.packages[dep.name];
-    // Load the package graph from [result] so we don't need to re-parse all
-    // the pubspecs.
     final entrypoint = Entrypoint.global(
-      Package(
-        result.pubspecs[dep.name],
-        (cache.source(dep.source) as CachedSource).getDirectoryInCache(id),
-      ),
-      lockFile,
+      _packageDir(id.name),
+      cache.loadCached(id),
+      result.lockFile,
       cache,
       solveResult: result,
     );
-    if (!sameVersions) {
-      // Only precompile binaries if we have a new resolution.
-      await entrypoint.precompileExecutables();
-    }
-
     _updateBinStubs(
       entrypoint,
-      cache.load(entrypoint.lockFile.packages[dep.name]),
+      cache.load(entrypoint.lockFile.packages[dep.name]!),
       executables,
       overwriteBinStubs: overwriteBinStubs,
     );
-
-    log.message('Activated ${_formatPackage(id)}.');
-  }
-
-  Future<void> _writePackageConfigFiles(
-      String package, LockFile lockFile) async {
-    // TODO(sigurdm): Use [Entrypoint.writePackagesFiles] instead.
-    final packagesFilePath = _getPackagesFilePath(package);
-    final packageConfigFilePath = _getPackageConfigFilePath(package);
-    final dir = p.dirname(packagesFilePath);
-    writeTextFile(
-        packagesFilePath, lockFile.packagesFile(cache, relativeFrom: dir));
-    ensureDir(p.dirname(packageConfigFilePath));
-    writeTextFile(packageConfigFilePath,
-        await lockFile.packageConfigFile(cache, relativeFrom: dir));
+    if (!silent) log.message('Activated ${_formatPackage(id)}.');
   }
 
   /// Finishes activating package [package] by saving [lockFile] in the cache.
-  void _writeLockFile(String package, LockFile lockFile) {
-    ensureDir(p.join(_directory, package));
-
-    // TODO(nweiz): This cleans up Dart 1.6's old lockfile location. Remove it
-    // when Dart 1.6 is old enough that we don't think anyone will have these
-    // lockfiles anymore (issue 20703).
-    var oldPath = p.join(_directory, '$package.lock');
-    if (fileExists(oldPath)) deleteEntry(oldPath);
-
-    writeTextFile(_getLockFilePath(package),
-        lockFile.serialize(p.join(_directory, package)));
+  void _writeLockFile(String dir, LockFile lockFile) {
+    writeTextFile(p.join(dir, 'pubspec.lock'), lockFile.serialize(null));
   }
 
   /// Shows the user the currently active package with [name], if any.
-  void _describeActive(LockFile lockFile, String name) {
-    var id = lockFile.packages[name];
+  LockFile? _describeActive(String name, SystemCache cache) {
+    late final LockFile lockFile;
+    try {
+      lockFile = LockFile.load(_getLockFilePath(name), cache.sources);
+    } on IOException {
+      // Couldn't read the lock file. It probably doesn't exist.
+      return null;
+    }
+    var id = lockFile.packages[name]!;
 
     var source = id.source;
     if (source is GitSource) {
@@ -311,6 +291,7 @@
       log.message('Package ${log.bold(name)} is currently active at version '
           '${log.bold(id.version)}.');
     }
+    return lockFile;
   }
 
   /// Deactivates a previously-activated package named [name].
@@ -323,7 +304,7 @@
     _deleteBinStubs(name);
 
     var lockFile = LockFile.load(_getLockFilePath(name), cache.sources);
-    var id = lockFile.packages[name];
+    var id = lockFile.packages[name]!;
     log.message('Deactivated package ${_formatPackage(id)}.');
 
     deleteEntry(dir);
@@ -336,32 +317,18 @@
   /// Returns an [Entrypoint] loaded with the active package if found.
   Future<Entrypoint> find(String name) async {
     var lockFilePath = _getLockFilePath(name);
-    LockFile lockFile;
+    late LockFile lockFile;
     try {
       lockFile = LockFile.load(lockFilePath, cache.sources);
     } on IOException {
-      var oldLockFilePath = p.join(_directory, '$name.lock');
-      try {
-        // TODO(nweiz): This looks for Dart 1.6's old lockfile location.
-        // Remove it when Dart 1.6 is old enough that we don't think anyone
-        // will have these lockfiles anymore (issue 20703).
-        lockFile = LockFile.load(oldLockFilePath, cache.sources);
-      } on IOException {
-        // If we couldn't read the lock file, it's not activated.
-        dataError('No active package ${log.bold(name)}.');
-      }
-
-      // Move the old lockfile to its new location.
-      ensureDir(p.dirname(lockFilePath));
-      File(oldLockFilePath).renameSync(lockFilePath);
-      // Just make sure these files are created as well.
-      await _writePackageConfigFiles(name, lockFile);
+      // If we couldn't read the lock file, it's not activated.
+      dataError('No active package ${log.bold(name)}.');
     }
 
     // Remove the package itself from the lockfile. We put it in there so we
     // could find and load the [Package] object, but normally an entrypoint
     // doesn't expect to be in its own lockfile.
-    var id = lockFile.packages[name];
+    var id = lockFile.packages[name]!;
     lockFile = lockFile.removePackage(name);
 
     var source = cache.source(id.source);
@@ -369,7 +336,8 @@
     if (source is CachedSource) {
       // For cached sources, the package itself is in the cache and the
       // lockfile is the one we just loaded.
-      entrypoint = Entrypoint.global(cache.loadCached(id), lockFile, cache);
+      entrypoint = Entrypoint.global(
+          _packageDir(id.name), cache.loadCached(id), lockFile, cache);
     } else {
       // For uncached sources (i.e. path), the ID just points to the real
       // directory for the package.
@@ -383,7 +351,7 @@
         dataError('${log.bold(name)} ${entrypoint.root.version} requires '
             'unknown SDK "$name".');
       } else if (sdkName == 'dart') {
-        if (constraint.allows(sdk.version)) return;
+        if (constraint.allows((sdk as DartSdk).version)) return;
         dataError("${log.bold(name)} ${entrypoint.root.version} doesn't "
             'support Dart ${sdk.version}.');
       } else {
@@ -399,7 +367,7 @@
         dataError('${log.bold(name)} as globally activated requires '
             'unknown SDK "$name".');
       } else if (sdkName == 'dart') {
-        if (constraint.allows(sdk.version)) return;
+        if (constraint.allows((sdk as DartSdk).version)) return;
         dataError("${log.bold(name)} as globally activated doesn't "
             'support Dart ${sdk.version}, try: $topLevelProgram pub global activate $name');
       } else {
@@ -421,11 +389,11 @@
   ///
   /// Returns the exit code from the executable.
   Future<int> runExecutable(
-      Entrypoint entrypoint, exec.Executable executable, Iterable<String> args,
+      Entrypoint entrypoint, exec.Executable executable, List<String> args,
       {bool enableAsserts = false,
-      Future<void> Function(exec.Executable) recompile,
+      required Future<void> Function(exec.Executable) recompile,
       List<String> vmArgs = const [],
-      @required bool alwaysUseSubprocess}) async {
+      required bool alwaysUseSubprocess}) async {
     return await exec.runExecutable(
       entrypoint,
       executable,
@@ -445,16 +413,6 @@
   String _getLockFilePath(String name) =>
       p.join(_directory, name, 'pubspec.lock');
 
-  /// Gets the path to the .packages file for an activated cached package with
-  /// [name].
-  String _getPackagesFilePath(String name) =>
-      p.join(_directory, name, '.packages');
-
-  /// Gets the path to the `package_config.json` file for an
-  /// activated cached package with [name].
-  String _getPackageConfigFilePath(String name) =>
-      p.join(_directory, name, '.dart_tool', 'package_config.json');
-
   /// Shows the user a formatted list of globally activated packages.
   void listActivePackages() {
     if (!dirExists(_directory)) return;
@@ -535,23 +493,30 @@
     var failures = <String>[];
     if (dirExists(_directory)) {
       for (var entry in listDir(_directory)) {
-        PackageId id;
+        PackageId? id;
         try {
           id = _loadPackageId(entry);
           log.message('Reactivating ${log.bold(id.name)} ${id.version}...');
 
           var entrypoint = await find(id.name);
+          final packageExecutables = executables.remove(id.name) ?? [];
 
-          await _writePackageConfigFiles(id.name, entrypoint.lockFile);
-          await entrypoint.precompileExecutables();
-          var packageExecutables = executables.remove(id.name) ?? [];
-          _updateBinStubs(
-            entrypoint,
-            cache.load(id),
-            packageExecutables,
-            overwriteBinStubs: true,
-            suggestIfNotOnPath: false,
-          );
+          if (entrypoint.isCached) {
+            deleteEntry(entrypoint.globalDir!);
+            await _installInCache(
+              id.toRange(),
+              packageExecutables,
+              overwriteBinStubs: true,
+              silent: true,
+            );
+          } else {
+            await activatePath(
+              entrypoint.root.dir,
+              packageExecutables,
+              overwriteBinStubs: true,
+              analytics: null,
+            );
+          }
           successes.add(id.name);
         } catch (error, stackTrace) {
           var message = 'Failed to reactivate '
@@ -631,8 +596,8 @@
   /// If [suggestIfNotOnPath] is `true` (the default), this will warn the user if
   /// the bin directory isn't on their path.
   void _updateBinStubs(
-      Entrypoint entrypoint, Package package, List<String> executables,
-      {bool overwriteBinStubs, bool suggestIfNotOnPath = true}) {
+      Entrypoint entrypoint, Package package, List<String>? executables,
+      {required bool overwriteBinStubs, bool suggestIfNotOnPath = true}) {
     // Remove any previously activated binstubs for this package, in case the
     // list of executables has changed.
     _deleteBinStubs(package.name);
@@ -650,7 +615,7 @@
     for (var executable in allExecutables) {
       if (executables != null && !executables.contains(executable)) continue;
 
-      var script = package.pubspec.executables[executable];
+      var script = package.pubspec.executables[executable]!;
 
       var previousPackage = _createBinStub(
         package,
@@ -705,10 +670,7 @@
     // Show errors for any missing scripts.
     // TODO(rnystrom): This can print false positives since a script may be
     // produced by a transformer. Do something better.
-    var binFiles = package
-        .listFiles(beneath: 'bin', recursive: false)
-        .map(package.relative)
-        .toList();
+    var binFiles = package.executablePaths;
     for (var executable in installed) {
       var script = package.pubspec.executables[executable];
       var scriptPath = p.join('bin', '$script.dart');
@@ -734,19 +696,19 @@
   ///
   /// If a collision occurs, returns the name of the package that owns the
   /// existing binstub. Otherwise returns `null`.
-  String _createBinStub(
+  String? _createBinStub(
     Package package,
     String executable,
     String script, {
-    @required bool overwrite,
-    @required String snapshot,
+    required bool overwrite,
+    required String snapshot,
   }) {
     var binStubPath = p.join(_binStubDir, executable);
     if (Platform.isWindows) binStubPath += '.bat';
 
     // See if the binstub already exists. If so, it's for another package
     // since we already deleted all of this package's binstubs.
-    String previousPackage;
+    String? previousPackage;
     if (fileExists(binStubPath)) {
       var contents = readTextFile(binStubPath);
       previousPackage = _binStubProperty(contents, 'Package');
@@ -760,8 +722,9 @@
     // If the script was built to a snapshot, just try to invoke that
     // directly and skip pub global run entirely.
     String invocation;
+    late String binstub;
     if (Platform.isWindows) {
-      if (snapshot != null && fileExists(snapshot)) {
+      if (fileExists(snapshot)) {
         // We expect absolute paths from the precompiler since relative ones
         // won't be relative to the right directory when the user runs this.
         assert(p.isAbsolute(snapshot));
@@ -785,7 +748,7 @@
       } else {
         invocation = 'dart pub global run ${package.name}:$script %*';
       }
-      var batch = '''
+      binstub = '''
 @echo off
 rem This file was created by pub v${sdk.version}.
 rem Package: ${package.name}
@@ -794,9 +757,8 @@
 rem Script: $script
 $invocation
 ''';
-      writeTextFile(binStubPath, batch);
     } else {
-      if (snapshot != null && fileExists(snapshot)) {
+      if (fileExists(snapshot)) {
         // We expect absolute paths from the precompiler since relative ones
         // won't be relative to the right directory when the user runs this.
         assert(p.isAbsolute(snapshot));
@@ -817,7 +779,7 @@
       } else {
         invocation = 'dart pub global run ${package.name}:$script "\$@"';
       }
-      var bash = '''
+      binstub = '''
 #!/usr/bin/env sh
 # This file was created by pub v${sdk.version}.
 # Package: ${package.name}
@@ -826,25 +788,31 @@
 # Script: $script
 $invocation
 ''';
+    }
 
-      // Write this as the system encoding since the system is going to execute
-      // it and it might contain non-ASCII characters in the pathnames.
-      writeTextFile(binStubPath, bash, encoding: const SystemEncoding());
+    // Write the binstub to a temporary location, make it executable and move
+    // it into place afterwards to avoid races.
+    final tempDir = cache.createTempDir();
+    try {
+      final tmpPath = p.join(tempDir, binStubPath);
 
-      // Make it executable.
-      var result = Process.runSync('chmod', ['+x', binStubPath]);
-      if (result.exitCode != 0) {
-        // Couldn't make it executable so don't leave it laying around.
-        try {
-          deleteEntry(binStubPath);
-        } on IOException catch (err) {
-          // Do nothing. We're going to fail below anyway.
-          log.fine('Could not delete binstub:\n$err');
+      // Write this as the system encoding since the system is going to
+      // execute it and it might contain non-ASCII characters in the
+      // pathnames.
+      writeTextFile(tmpPath, binstub, encoding: const SystemEncoding());
+
+      if (Platform.isLinux || Platform.isMacOS) {
+        // Make it executable.
+        var result = Process.runSync('chmod', ['+x', tmpPath]);
+        if (result.exitCode != 0) {
+          // Couldn't make it executable so don't leave it laying around.
+          fail('Could not make "$tmpPath" executable (exit code '
+              '${result.exitCode}):\n${result.stderr}');
         }
-
-        fail('Could not make "$binStubPath" executable (exit code '
-            '${result.exitCode}):\n${result.stderr}');
       }
+      File(tmpPath).renameSync(binStubPath);
+    } finally {
+      deleteEntry(tempDir);
     }
 
     return previousPackage;
@@ -896,7 +864,7 @@
       if (result.exitCode == 0) return;
 
       var binDir = _binStubDir;
-      if (binDir.startsWith(Platform.environment['HOME'])) {
+      if (binDir.startsWith(Platform.environment['HOME']!)) {
         binDir = p.join(
             r'$HOME', p.relative(binDir, from: Platform.environment['HOME']));
       }
@@ -913,7 +881,7 @@
 
   /// Returns the value of the property named [name] in the bin stub script
   /// [source].
-  String _binStubProperty(String source, String name) {
+  String? _binStubProperty(String source, String name) {
     var pattern = RegExp(RegExp.escape(name) + r': ([a-zA-Z0-9_-]+)');
     var match = pattern.firstMatch(source);
     return match == null ? null : match[1];
diff --git a/lib/src/http.dart b/lib/src/http.dart
index 541931f..724962b 100644
--- a/lib/src/http.dart
+++ b/lib/src/http.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 /// Helpers for dealing with HTTP.
 import 'dart:async';
 import 'dart:convert';
@@ -12,7 +10,6 @@
 
 import 'package:http/http.dart' as http;
 import 'package:http/retry.dart';
-import 'package:pedantic/pedantic.dart';
 import 'package:pool/pool.dart';
 import 'package:stack_trace/stack_trace.dart';
 
@@ -44,7 +41,7 @@
 
   http.Client _inner;
 
-  _PubHttpClient([http.Client inner]) : _inner = inner ?? http.Client();
+  _PubHttpClient([http.Client? inner]) : _inner = inner ?? http.Client();
 
   @override
   Future<http.StreamedResponse> send(http.BaseRequest request) async {
@@ -132,8 +129,8 @@
     // careful not to log OAuth2 private data, though.
 
     var responseLog = StringBuffer();
-    var request = response.request;
-    var stopwatch = _requestStopwatches.remove(request)..stop();
+    var request = response.request!;
+    var stopwatch = _requestStopwatches.remove(request)!..stop();
     responseLog.writeln('HTTP response ${response.statusCode} '
         '${response.reasonPhrase} for ${request.method} ${request.url}');
     responseLog.writeln('took ${stopwatch.elapsed}');
@@ -173,12 +170,12 @@
 
   @override
   Future<http.StreamedResponse> send(http.BaseRequest request) async {
-    http.StreamedResponse streamedResponse;
+    late http.StreamedResponse streamedResponse;
     try {
       streamedResponse = await _inner.send(request);
     } on SocketException catch (error, stackTraceOrNull) {
       // Work around issue 23008.
-      var stackTrace = stackTraceOrNull ?? Chain.current();
+      var stackTrace = stackTraceOrNull;
 
       if (error.osError == null) rethrow;
 
@@ -191,14 +188,14 @@
       // with a retry. Failing to retry intermittent issues is likely to cause
       // customers to wrap pub in a retry loop which will not improve the
       // end-user experience.
-      if (error.osError.errorCode == 8 ||
-          error.osError.errorCode == -2 ||
-          error.osError.errorCode == -5 ||
-          error.osError.errorCode == 11001 ||
-          error.osError.errorCode == 11004) {
+      if (error.osError!.errorCode == 8 ||
+          error.osError!.errorCode == -2 ||
+          error.osError!.errorCode == -5 ||
+          error.osError!.errorCode == 11001 ||
+          error.osError!.errorCode == 11004) {
         fail('Could not resolve URL "${request.url.origin}".', error,
             stackTrace);
-      } else if (error.osError.errorCode == -12276) {
+      } else if (error.osError!.errorCode == -12276) {
         fail(
             'Unable to validate SSL certificate for '
             '"${request.url.origin}".',
@@ -213,7 +210,7 @@
     // 401 responses should be handled by the OAuth2 client. It's very
     // unlikely that they'll be returned by non-OAuth2 requests. We also want
     // to pass along 400 responses from the token endpoint.
-    var tokenRequest = streamedResponse.request.url == oauth2.tokenEndpoint;
+    var tokenRequest = streamedResponse.request!.url == oauth2.tokenEndpoint;
     if (status < 400 || status == 401 || (status == 400 && tokenRequest)) {
       return streamedResponse;
     }
@@ -342,7 +339,7 @@
 }
 
 /// Throws an error describing an invalid response from the server.
-void invalidServerResponse(http.Response response) =>
+Never invalidServerResponse(http.Response response) =>
     fail(log.red('Invalid server response:\n${response.body}'));
 
 /// Exception thrown when an HTTP operation fails.
diff --git a/lib/src/ignore.dart b/lib/src/ignore.dart
index ade58d0..ff22dff 100644
--- a/lib/src/ignore.dart
+++ b/lib/src/ignore.dart
@@ -94,7 +94,7 @@
   /// [1]: https://git-scm.com/docs/gitignore
   /// [2]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreignoreCase
   Ignore(
-    Iterable<String> patterns, {
+    List<String> patterns, {
     bool ignoreCase = false,
     void Function(String pattern, FormatException exception)? onInvalidPattern,
   }) : _rules = _parseIgnorePatterns(
@@ -464,6 +464,9 @@
         } else {
           expr += '.*';
         }
+      } else if (peekChar() == '/' || peekChar() == null) {
+        // /a/* should not match '/a/'
+        expr += '[^/]+';
       } else {
         // Handle a single '*'
         expr += '[^/]*';
@@ -520,7 +523,6 @@
     expr = '$expr/\$';
   } else {
     expr = '$expr/?\$';
-    // expr = '$expr\$';
   }
   try {
     return _IgnoreParseResult(
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 9622eaa..280dcbd 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -8,11 +8,12 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:cli_util/cli_util.dart'
+    show EnvironmentNotFoundException, applicationConfigHome;
 import 'package:http/http.dart' show ByteStream;
 import 'package:http_multi_server/http_multi_server.dart';
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
-import 'package:pedantic/pedantic.dart';
 import 'package:pool/pool.dart';
 import 'package:stack_trace/stack_trace.dart';
 
@@ -158,7 +159,9 @@
 String readTextFile(String file) => File(file).readAsStringSync();
 
 /// Reads the contents of the text file [file].
-Future<String> readTextFileAsync(String file) => File(file).readAsString();
+Future<String> readTextFileAsync(String file) {
+  return _descriptorPool.withResource(() => File(file).readAsString());
+}
 
 /// Reads the contents of the binary file [file].
 List<int> readBinaryFile(String file) {
@@ -358,52 +361,54 @@
 /// when we try to delete or move something while it's being scanned. To
 /// mitigate that, on Windows, this will retry the operation a few times if it
 /// fails.
-void _attempt(String description, void Function() operation) {
+///
+/// For some operations it makes sense to handle ERROR_DIR_NOT_EMPTY
+/// differently. They can pass [ignoreEmptyDir] = `true`.
+void _attempt(String description, void Function() operation,
+    {bool ignoreEmptyDir = false}) {
   if (!Platform.isWindows) {
     operation();
     return;
   }
 
   String? getErrorReason(FileSystemException error) {
+    // ERROR_ACCESS_DENIED
     if (error.osError?.errorCode == 5) {
       return 'access was denied';
     }
 
+    // ERROR_SHARING_VIOLATION
     if (error.osError?.errorCode == 32) {
       return 'it was in use by another process';
     }
 
-    if (error.osError?.errorCode == 145) {
+    // ERROR_DIR_NOT_EMPTY
+    if (!ignoreEmptyDir && _isDirectoryNotEmptyException(error)) {
       return 'of dart-lang/sdk#25353';
     }
 
     return null;
   }
 
-  for (var i = 0; i < 2; i++) {
+  for (var i = 0; i < 3; i++) {
     try {
       operation();
-      return;
+      break;
     } on FileSystemException catch (error) {
       var reason = getErrorReason(error);
       if (reason == null) rethrow;
 
-      log.io('Pub failed to $description because $reason. '
-          'Retrying in 50ms.');
-      sleep(Duration(milliseconds: 50));
+      if (i < 2) {
+        log.io('Pub failed to $description because $reason. '
+            'Retrying in 50ms.');
+        sleep(Duration(milliseconds: 50));
+      } else {
+        fail('Pub failed to $description because $reason.\n'
+            'This may be caused by a virus scanner or having a file\n'
+            'in the directory open in another application.');
+      }
     }
   }
-
-  try {
-    operation();
-  } on FileSystemException catch (error) {
-    var reason = getErrorReason(error);
-    if (reason == null) rethrow;
-
-    fail('Pub failed to $description because $reason.\n'
-        'This may be caused by a virus scanner or having a file\n'
-        'in the directory open in another application.');
-  }
 }
 
 /// Deletes whatever's at [path], whether it's a file, directory, or symlink.
@@ -448,14 +453,53 @@
 void renameDir(String from, String to) {
   _attempt('rename directory', () {
     log.io('Renaming directory $from to $to.');
-    try {
-      Directory(from).renameSync(to);
-    } on IOException {
-      // Ensure that [to] isn't left in an inconsistent state. See issue 12436.
-      if (entryExists(to)) deleteEntry(to);
+    Directory(from).renameSync(to);
+  }, ignoreEmptyDir: true);
+}
+
+/// Renames directory [from] to [to].
+/// If it fails with "destination not empty" we log and continue, assuming
+/// another process got there before us.
+void tryRenameDir(String from, String to) {
+  ensureDir(path.dirname(to));
+  try {
+    renameDir(from, to);
+  } on FileSystemException catch (e) {
+    tryDeleteEntry(from);
+    if (!_isDirectoryNotEmptyException(e)) {
       rethrow;
     }
-  });
+    log.fine('''
+Destination directory $to already existed.
+Assuming a concurrent pub invocation installed it.''');
+  }
+}
+
+void copyFile(String from, String to) {
+  log.io('Copying "$from" to "$to".');
+  File(from).copySync(to);
+}
+
+void renameFile(String from, String to) {
+  log.io('Renaming "$from" to "$to".');
+  File(from).renameSync(to);
+}
+
+bool _isDirectoryNotEmptyException(FileSystemException e) {
+  final errorCode = e.osError?.errorCode;
+  return
+      // On Linux rename will fail with ENOTEMPTY if directory exists:
+      // https://man7.org/linux/man-pages/man2/rename.2.html
+      // #define	ENOTEMPTY	39	/* Directory not empty */
+      // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/errno.h#n20
+      (Platform.isLinux && errorCode == 39) ||
+          // On Windows this may fail with ERROR_DIR_NOT_EMPTY
+          // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
+          (Platform.isWindows && errorCode == 145) ||
+          // On MacOS rename will fail with ENOTEMPTY if directory exists.
+          // #define ENOTEMPTY       66              /* Directory not empty */
+          // https://github.com/apple-oss-distributions/xnu/blob/bb611c8fecc755a0d8e56e2fa51513527c5b7a0e/bsd/sys/errno.h#L190
+          (Platform.isMacOS && errorCode == 66);
 }
 
 /// Creates a new symlink at path [symlink] that points to [target].
@@ -557,10 +601,6 @@
   return path.fromUri(url);
 })();
 
-/// A line-by-line stream of standard input.
-final Stream<String> _stdinLines =
-    ByteStream(stdin).toStringStream().transform(const LineSplitter());
-
 /// Displays a message and reads a yes/no confirmation from the user.
 ///
 /// Returns a [Future] that completes to `true` if the user confirms or `false`
@@ -576,7 +616,7 @@
 }
 
 /// Writes [prompt] and reads a line from stdin.
-Future<String> stdinPrompt(String prompt, {bool? echoMode}) {
+Future<String> stdinPrompt(String prompt, {bool? echoMode}) async {
   if (runningFromTest) {
     log.message(prompt);
   } else {
@@ -584,12 +624,16 @@
   }
   if (echoMode != null && stdin.hasTerminal) {
     final previousEchoMode = stdin.echoMode;
-    stdin.echoMode = echoMode;
-    return _stdinLines.first.whenComplete(() {
+    try {
+      stdin.echoMode = echoMode;
+      final result = stdin.readLineSync() ?? '';
+      stdout.write('\n');
+      return result;
+    } finally {
       stdin.echoMode = previousEchoMode;
-    });
+    }
   } else {
-    return _stdinLines.first;
+    return stdin.readLineSync() ?? '';
   }
 }
 
@@ -963,10 +1007,10 @@
   log.fine(buffer.toString());
 
   ArgumentError.checkNotNull(baseDir, 'baseDir');
-  baseDir = path.absolute(baseDir);
+  baseDir = path.normalize(path.absolute(baseDir));
 
   final tarContents = Stream.fromIterable(contents.map((entry) {
-    entry = path.absolute(entry);
+    entry = path.normalize(path.absolute(entry));
     if (!path.isWithin(baseDir, entry)) {
       throw ArgumentError('Entry $entry is not inside $baseDir.');
     }
@@ -1023,22 +1067,16 @@
 }
 
 /// The location for dart-specific configuration.
-final String dartConfigDir = () {
-  // TODO: Migrate to new value from cli_util
-  if (runningFromTest) {
+///
+/// `null` if no config dir could be found.
+final String? dartConfigDir = () {
+  if (runningFromTest &&
+      Platform.environment.containsKey('_PUB_TEST_CONFIG_DIR')) {
     return Platform.environment['_PUB_TEST_CONFIG_DIR'];
   }
-  String configDir;
-  if (Platform.isLinux) {
-    configDir = Platform.environment['XDG_CONFIG_HOME'] ??
-        path.join(Platform.environment['HOME']!, '.config');
-  } else if (Platform.isWindows) {
-    configDir = Platform.environment['APPDATA']!;
-  } else if (Platform.isMacOS) {
-    configDir = path.join(
-        Platform.environment['HOME']!, 'Library', 'Application Support');
-  } else {
-    configDir = path.join(Platform.environment['HOME']!, '.config');
+  try {
+    return applicationConfigHome('dart');
+  } on EnvironmentNotFoundException {
+    return null;
   }
-  return path.join(configDir, 'dart');
-}()!;
+}();
diff --git a/lib/src/language_version.dart b/lib/src/language_version.dart
index e294014..ad331b2 100644
--- a/lib/src/language_version.dart
+++ b/lib/src/language_version.dart
@@ -69,6 +69,21 @@
 
   bool get supportsNullSafety => this >= firstVersionWithNullSafety;
 
+  /// Minimum language version at which short hosted syntax is supported.
+  ///
+  /// This allows `hosted` dependencies to be expressed as:
+  /// ```yaml
+  /// dependencies:
+  ///   foo:
+  ///     hosted: https://some-pub.com/path
+  ///     version: ^1.0.0
+  /// ```
+  ///
+  /// At older versions, `hosted` dependencies had to be a map with a `url` and
+  /// a `name` key.
+  bool get supportsShorterHostedSyntax =>
+      this >= firstVersionWithShorterHostedSyntax;
+
   @override
   int compareTo(LanguageVersion other) {
     if (major != other.major) return major.compareTo(other.major);
@@ -89,6 +104,7 @@
 
   static const defaultLanguageVersion = LanguageVersion(2, 7);
   static const firstVersionWithNullSafety = LanguageVersion(2, 12);
+  static const firstVersionWithShorterHostedSyntax = LanguageVersion(2, 15);
 
   /// Transform language version to string that can be parsed with
   /// [LanguageVersion.parse].
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart
index b2139fe..8f563ab 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -2,12 +2,9 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:collection/collection.dart' hide mapMap;
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:source_span/source_span.dart';
@@ -52,10 +49,10 @@
   /// `dependency_overrides` sections, respectively. These are consumed by the
   /// analysis server to provide better auto-completion.
   LockFile(Iterable<PackageId> ids,
-      {Map<String, VersionConstraint> sdkConstraints,
-      Set<String> mainDependencies,
-      Set<String> devDependencies,
-      Set<String> overriddenDependencies})
+      {Map<String, VersionConstraint>? sdkConstraints,
+      Set<String>? mainDependencies,
+      Set<String>? devDependencies,
+      Set<String>? overriddenDependencies})
       : this._(
             Map.fromIterable(ids.where((id) => !id.isRoot),
                 key: (id) => id.name),
@@ -94,10 +91,10 @@
   /// [filePath] is the system-native path to the lockfile on disc. It may be
   /// `null`.
   static LockFile _parse(
-      String filePath, String contents, SourceRegistry sources) {
+      String? filePath, String contents, SourceRegistry sources) {
     if (contents.trim() == '') return LockFile.empty();
 
-    Uri sourceUrl;
+    Uri? sourceUrl;
     if (filePath != null) sourceUrl = p.toUri(filePath);
     var parsed = loadYamlNode(contents, sourceUrl: sourceUrl);
 
@@ -145,7 +142,7 @@
         var description = spec['description'];
 
         // Let the source parse the description.
-        var source = sources[sourceName];
+        var source = sources[sourceName]!;
         PackageId id;
         try {
           id = source.parseId(name, version, description,
@@ -173,8 +170,6 @@
 
   /// Asserts that [node] is a version constraint, and parses it.
   static VersionConstraint _parseVersionConstraint(YamlNode node) {
-    if (node == null) return null;
-
     _validate(node.value is String,
         'Invalid version constraint: must be a string.', node);
 
@@ -199,9 +194,9 @@
   }
 
   /// If [condition] is `false` throws a format error with [message] for [node].
-  static void _validate(bool condition, String message, YamlNode node) {
+  static void _validate(bool condition, String message, YamlNode? node) {
     if (condition) return;
-    throw SourceSpanFormatException(message, node.span);
+    throw SourceSpanFormatException(message, node!.span);
   }
 
   /// Returns a copy of this LockFile with a package named [name] removed.
@@ -222,8 +217,8 @@
   /// directory.
   String packagesFile(
     SystemCache cache, {
-    String entrypoint,
-    @required String relativeFrom,
+    String? entrypoint,
+    String? relativeFrom,
   }) {
     var header = '''
 This file is deprecated. Tools should instead consume 
@@ -233,9 +228,9 @@
 
 Generated by pub on ${DateTime.now()}.''';
 
-    var map =
-        Map<String, Uri>.fromIterable(ordered(packages.keys), value: (name) {
-      var id = packages[name];
+    var map = Map<String, Uri>.fromIterable(ordered<String>(packages.keys),
+        value: (name) {
+      var id = packages[name]!;
       var source = cache.source(id.source);
       return p.toUri(
           p.join(source.getDirectory(id, relativeFrom: relativeFrom), 'lib'));
@@ -259,13 +254,13 @@
   /// current package has no SDK constraint.
   Future<String> packageConfigFile(
     SystemCache cache, {
-    String entrypoint,
-    VersionConstraint entrypointSdkConstraint,
-    @required String relativeFrom,
+    String? entrypoint,
+    VersionConstraint? entrypointSdkConstraint,
+    String? relativeFrom,
   }) async {
     final entries = <PackageConfigEntry>[];
     for (final name in ordered(packages.keys)) {
-      final id = packages[name];
+      final id = packages[name]!;
       final source = cache.source(id.source);
       final rootPath = source.getDirectory(id, relativeFrom: relativeFrom);
       Uri rootUri;
@@ -311,17 +306,18 @@
   /// Returns the serialized YAML text of the lock file.
   ///
   /// [packageDir] is the containing directory of the root package, used to
-  /// properly serialize package descriptions.
-  String serialize(String packageDir) {
+  /// serialize relative path package descriptions. If it is null, they will be
+  /// serialized as absolute.
+  String serialize(String? packageDir) {
     // Convert the dependencies to a simple object.
     var packageMap = {};
     packages.forEach((name, package) {
       var description =
-          package.source.serializeDescription(packageDir, package.description);
+          package.source!.serializeDescription(packageDir, package.description);
 
       packageMap[name] = {
         'version': package.version.toString(),
-        'source': package.source.name,
+        'source': package.source!.name,
         'description': description,
         'dependency': _dependencyType(package.name)
       };
diff --git a/lib/src/log.dart b/lib/src/log.dart
index ff7fe29..db24d7a 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -12,9 +12,11 @@
 import 'package:source_span/source_span.dart';
 import 'package:stack_trace/stack_trace.dart';
 
+import 'entrypoint.dart';
 import 'exceptions.dart';
 import 'io.dart';
 import 'progress.dart';
+import 'sdk.dart';
 import 'transcript.dart';
 import 'utils.dart';
 
@@ -24,7 +26,7 @@
 final json = _JsonLogger();
 
 /// The current logging verbosity.
-Verbosity verbosity = Verbosity.NORMAL;
+Verbosity verbosity = Verbosity.normal;
 
 /// In cases where there's a ton of log spew, make sure we don't eat infinite
 /// memory.
@@ -32,11 +34,11 @@
 /// This can occur when the backtracking solver stumbles into a pathological
 /// dependency graph. It generally will find a solution, but it may log
 /// thousands and thousands of entries to get there.
-const _MAX_TRANSCRIPT = 10000;
+const _maxTranscript = 10000;
 
 /// The list of recorded log messages. Will only be recorded if
 /// [recordTranscript()] is called.
-Transcript<_Entry>? _transcript;
+final Transcript<_Entry> _transcript = Transcript(_maxTranscript);
 
 /// The currently-animated progress indicator, if any.
 ///
@@ -58,34 +60,34 @@
 /// An enum type for defining the different logging levels a given message can
 /// be associated with.
 ///
-/// By default, [ERROR] and [WARNING] messages are printed to sterr. [MESSAGE]
+/// By default, [error] and [warning] messages are printed to sterr. [message]
 /// messages are printed to stdout, and others are ignored.
 class Level {
   /// An error occurred and an operation could not be completed.
   ///
   /// Usually shown to the user on stderr.
-  static const ERROR = Level._('ERR ');
+  static const error = Level._('ERR ');
 
   /// Something unexpected happened, but the program was able to continue,
   /// though possibly in a degraded fashion.
-  static const WARNING = Level._('WARN');
+  static const warning = Level._('WARN');
 
   /// A message intended specifically to be shown to the user.
-  static const MESSAGE = Level._('MSG ');
+  static const message = Level._('MSG ');
 
   /// Some interaction with the external world occurred, such as a network
   /// operation, process spawning, or file IO.
-  static const IO = Level._('IO  ');
+  static const io = Level._('IO  ');
 
   /// Incremental output during pub's version constraint solver.
-  static const SOLVER = Level._('SLVR');
+  static const solver = Level._('SLVR');
 
   /// Fine-grained and verbose additional information.
   ///
   /// Used to provide program state context for other logs (such as what pub
   /// was doing when an IO operation occurred) or just more detail for an
   /// operation.
-  static const FINE = Level._('FINE');
+  static const fine = Level._('FINE');
 
   const Level._(this.name);
 
@@ -99,73 +101,83 @@
 /// displayed.
 class Verbosity {
   /// Silence all logging.
-  static const NONE = Verbosity._('none', {
-    Level.ERROR: null,
-    Level.WARNING: null,
-    Level.MESSAGE: null,
-    Level.IO: null,
-    Level.SOLVER: null,
-    Level.FINE: null
+  static const none = Verbosity._('none', {
+    Level.error: null,
+    Level.warning: null,
+    Level.message: null,
+    Level.io: null,
+    Level.solver: null,
+    Level.fine: null
   });
 
   /// Shows only errors.
-  static const ERROR = Verbosity._('error', {
-    Level.ERROR: _logToStderr,
-    Level.WARNING: null,
-    Level.MESSAGE: null,
-    Level.IO: null,
-    Level.SOLVER: null,
-    Level.FINE: null
+  static const error = Verbosity._('error', {
+    Level.error: _logToStderr,
+    Level.warning: null,
+    Level.message: null,
+    Level.io: null,
+    Level.solver: null,
+    Level.fine: null
   });
 
   /// Shows only errors and warnings.
-  static const WARNING = Verbosity._('warning', {
-    Level.ERROR: _logToStderr,
-    Level.WARNING: _logToStderr,
-    Level.MESSAGE: null,
-    Level.IO: null,
-    Level.SOLVER: null,
-    Level.FINE: null
+  static const warning = Verbosity._('warning', {
+    Level.error: _logToStderr,
+    Level.warning: _logToStderr,
+    Level.message: null,
+    Level.io: null,
+    Level.solver: null,
+    Level.fine: null
   });
 
   /// The default verbosity which shows errors, warnings, and messages.
-  static const NORMAL = Verbosity._('normal', {
-    Level.ERROR: _logToStderr,
-    Level.WARNING: _logToStderr,
-    Level.MESSAGE: _logToStdout,
-    Level.IO: null,
-    Level.SOLVER: null,
-    Level.FINE: null
+  static const normal = Verbosity._('normal', {
+    Level.error: _logToStderr,
+    Level.warning: _logToStderr,
+    Level.message: _logToStdout,
+    Level.io: null,
+    Level.solver: null,
+    Level.fine: null
   });
 
   /// Shows errors, warnings, messages, and IO event logs.
-  static const IO = Verbosity._('io', {
-    Level.ERROR: _logToStderrWithLabel,
-    Level.WARNING: _logToStderrWithLabel,
-    Level.MESSAGE: _logToStdoutWithLabel,
-    Level.IO: _logToStderrWithLabel,
-    Level.SOLVER: null,
-    Level.FINE: null
+  static const io = Verbosity._('io', {
+    Level.error: _logToStderrWithLabel,
+    Level.warning: _logToStderrWithLabel,
+    Level.message: _logToStdoutWithLabel,
+    Level.io: _logToStderrWithLabel,
+    Level.solver: null,
+    Level.fine: null
   });
 
   /// Shows errors, warnings, messages, and version solver logs.
-  static const SOLVER = Verbosity._('solver', {
-    Level.ERROR: _logToStderr,
-    Level.WARNING: _logToStderr,
-    Level.MESSAGE: _logToStdout,
-    Level.IO: null,
-    Level.SOLVER: _logToStdout,
-    Level.FINE: null
+  static const solver = Verbosity._('solver', {
+    Level.error: _logToStderr,
+    Level.warning: _logToStderr,
+    Level.message: _logToStdout,
+    Level.io: null,
+    Level.solver: _logToStdout,
+    Level.fine: null
   });
 
   /// Shows all logs.
-  static const ALL = Verbosity._('all', {
-    Level.ERROR: _logToStderrWithLabel,
-    Level.WARNING: _logToStderrWithLabel,
-    Level.MESSAGE: _logToStdoutWithLabel,
-    Level.IO: _logToStderrWithLabel,
-    Level.SOLVER: _logToStderrWithLabel,
-    Level.FINE: _logToStderrWithLabel
+  static const all = Verbosity._('all', {
+    Level.error: _logToStderrWithLabel,
+    Level.warning: _logToStderrWithLabel,
+    Level.message: _logToStdoutWithLabel,
+    Level.io: _logToStderrWithLabel,
+    Level.solver: _logToStderrWithLabel,
+    Level.fine: _logToStderrWithLabel
+  });
+
+  /// Shows all logs.
+  static const testing = Verbosity._('testing', {
+    Level.error: _logToStderrWithLabel,
+    Level.warning: _logToStderrWithLabel,
+    Level.message: _logToStdoutWithLabel,
+    Level.io: _logToStderrWithLabel,
+    Level.solver: _logToStderrWithLabel,
+    Level.fine: _logToStderrWithLabel
   });
 
   const Verbosity._(this.name, this._loggers);
@@ -188,7 +200,7 @@
   _Entry(this.level, this.lines);
 }
 
-/// Logs [message] at [Level.ERROR].
+/// Logs [message] at [Level.error].
 ///
 /// If [error] is passed, it's appended to [message]. If [trace] is passed, it's
 /// printed at log level fine.
@@ -198,24 +210,24 @@
     message = message.isEmpty ? '$error' : '$message: $error';
     if (error is Error && trace == null) trace = error.stackTrace;
   }
-  write(Level.ERROR, message);
-  if (trace != null) write(Level.FINE, Chain.forTrace(trace));
+  write(Level.error, message);
+  if (trace != null) write(Level.fine, Chain.forTrace(trace));
 }
 
-/// Logs [message] at [Level.WARNING].
-void warning(message) => write(Level.WARNING, message);
+/// Logs [message] at [Level.warning].
+void warning(message) => write(Level.warning, message);
 
-/// Logs [message] at [Level.MESSAGE].
-void message(message) => write(Level.MESSAGE, message);
+/// Logs [message] at [Level.message].
+void message(message) => write(Level.message, message);
 
-/// Logs [message] at [Level.IO].
-void io(message) => write(Level.IO, message);
+/// Logs [message] at [Level.io].
+void io(message) => write(Level.io, message);
 
-/// Logs [message] at [Level.SOLVER].
-void solver(message) => write(Level.SOLVER, message);
+/// Logs [message] at [Level.solver].
+void solver(message) => write(Level.solver, message);
 
-/// Logs [message] at [Level.FINE].
-void fine(message) => write(Level.FINE, message);
+/// Logs [message] at [Level.fine].
+void fine(message) => write(Level.fine, message);
 
 /// Logs [message] at [level].
 void write(Level level, message) {
@@ -233,10 +245,10 @@
   var logFn = verbosity._loggers[level];
   if (logFn != null) logFn(entry);
 
-  if (_transcript != null) _transcript!.add(entry);
+  _transcript.add(entry);
 }
 
-/// Logs the spawning of an [executable] process with [arguments] at [IO]
+/// Logs the spawning of an [executable] process with [arguments] at [io]
 /// level.
 void process(
     String executable, List<String> arguments, String workingDirectory) {
@@ -313,18 +325,10 @@
   }
 }
 
-/// Enables recording of log entries.
-void recordTranscript() {
-  _transcript = Transcript<_Entry>(_MAX_TRANSCRIPT);
-}
-
-/// If [recordTranscript()] was called, then prints the previously recorded log
-/// transcript to stderr.
-void dumpTranscript() {
-  if (_transcript == null) return;
-
+/// Prints the recorded log transcript to stderr.
+void dumpTranscriptToStdErr() {
   stderr.writeln('---- Log transcript ----');
-  _transcript!.forEach((entry) {
+  _transcript.forEach((entry) {
     _printToStream(stderr, entry, showLabel: true);
   }, (discarded) {
     stderr.writeln('---- ($discarded discarded) ----');
@@ -332,6 +336,68 @@
   stderr.writeln('---- End log transcript ----');
 }
 
+String _limit(String input, int limit) {
+  const snip = '[...]';
+  if (input.length < limit - snip.length) return input;
+  return '${input.substring(0, limit ~/ 2 - snip.length)}'
+      '$snip'
+      '${input.substring(limit)}';
+}
+
+/// Prints relevant system information and the log transcript to [path].
+void dumpTranscriptToFile(String path, String command, Entrypoint? entrypoint) {
+  final buffer = StringBuffer();
+  buffer.writeln('''
+Information about the latest pub run.
+
+If you believe something is not working right, you can go to 
+https://github.com/dart-lang/pub/issues/new to post a new issue and attach this file.
+
+Before making this file public, make sure to remove any sensitive information!
+
+Pub version: ${sdk.version}
+Created: ${DateTime.now().toIso8601String()}
+FLUTTER_ROOT: ${Platform.environment['FLUTTER_ROOT'] ?? '<not set>'}
+PUB_HOSTED_URL: ${Platform.environment['PUB_HOSTED_URL'] ?? '<not set>'}
+PUB_CACHE: "${Platform.environment['PUB_CACHE'] ?? '<not set>'}"
+Command: $command
+Platform: ${Platform.operatingSystem}
+''');
+
+  if (entrypoint != null) {
+    buffer.writeln('---- ${p.absolute(entrypoint.pubspecPath)} ----');
+    if (fileExists(entrypoint.pubspecPath)) {
+      buffer.writeln(_limit(readTextFile(entrypoint.pubspecPath), 5000));
+    } else {
+      buffer.writeln('<No pubspec.yaml>');
+    }
+    buffer.writeln('---- End pubspec.yaml ----');
+    buffer.writeln('---- ${p.absolute(entrypoint.lockFilePath)} ----');
+    if (fileExists(entrypoint.lockFilePath)) {
+      buffer.writeln(_limit(readTextFile(entrypoint.lockFilePath), 5000));
+    } else {
+      buffer.writeln('<No pubspec.lock>');
+    }
+    buffer.writeln('---- End pubspec.lock ----');
+  }
+
+  buffer.writeln('---- Log transcript ----');
+
+  _transcript.forEach((entry) {
+    _printToStream(buffer, entry, showLabel: true);
+  }, (discarded) {
+    buffer.writeln('---- ($discarded entries discarded) ----');
+  });
+  buffer.writeln('---- End log transcript ----');
+  ensureDir(p.dirname(path));
+  try {
+    writeTextFile(path, buffer.toString(), dontLogContents: true);
+  } on IOException catch (e) {
+    stderr.writeln('Failed writing log to `$path` ($e), writing it to stderr:');
+    dumpTranscriptToStdErr();
+  }
+}
+
 /// Filter out normal pub output when not attached to a terminal
 ///
 /// Unless the user has overriden the verbosity,
@@ -339,8 +405,8 @@
 /// This is useful to not pollute stdout when the output is piped somewhere.
 Future<T> warningsOnlyUnlessTerminal<T>(FutureOr<T> Function() callback) async {
   final oldVerbosity = verbosity;
-  if (verbosity == Verbosity.NORMAL && !stdout.hasTerminal) {
-    verbosity = Verbosity.WARNING;
+  if (verbosity == Verbosity.normal && !stdout.hasTerminal) {
+    verbosity = Verbosity.warning;
   }
   final result = await callback();
   verbosity = oldVerbosity;
@@ -353,7 +419,7 @@
 /// If anything else is logged during this (including another call to
 /// [progress]) that cancels the progress animation, although the total time
 /// will still be printed once it finishes. If [fine] is passed, the progress
-/// information will only be visible at [Level.FINE].
+/// information will only be visible at [Level.fine].
 Future<T> progress<T>(String message, Future<T> Function() callback) {
   _stopProgress();
 
@@ -492,7 +558,7 @@
   _printToStream(sink, entry, showLabel: showLabel);
 }
 
-void _printToStream(IOSink sink, _Entry entry, {required bool showLabel}) {
+void _printToStream(StringSink sink, _Entry entry, {required bool showLabel}) {
   _stopProgress();
 
   var firstLine = true;
diff --git a/lib/src/null_safety_analysis.dart b/lib/src/null_safety_analysis.dart
index fb1c8ad..5b882a8 100644
--- a/lib/src/null_safety_analysis.dart
+++ b/lib/src/null_safety_analysis.dart
@@ -2,13 +2,11 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
-import 'package:analyzer/dart/analysis/context_builder.dart';
-import 'package:analyzer/dart/analysis/context_locator.dart';
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:cli_util/cli_util.dart';
 import 'package:path/path.dart' as path;
 import 'package:source_span/source_span.dart';
@@ -74,16 +72,17 @@
   /// If [packageId] is a relative path dependency [containingPath] must be
   /// provided with an absolute path to resolve it against.
   Future<NullSafetyAnalysisResult> nullSafetyCompliance(PackageId packageId,
-      {String containingPath}) async {
+      {String? containingPath}) async {
     // A space in the name prevents clashes with other package names.
     final fakeRootName = '${packageId.name} importer';
     final fakeRoot = Package.inMemory(Pubspec(fakeRootName,
         fields: {
           'dependencies': {
             packageId.name: {
-              packageId.source.name: packageId.source is PathSource
+              packageId.source!.name: packageId.source is PathSource
                   ? (packageId.description['relative']
-                      ? path.join(containingPath, packageId.description['path'])
+                      ? path.join(
+                          containingPath!, packageId.description['path'])
                       : packageId.description['path'])
                   : packageId.description,
               'version': packageId.version.toString(),
@@ -93,7 +92,7 @@
         sources: _systemCache.sources));
 
     final rootPubspec =
-        await packageId.source.bind(_systemCache).describe(packageId);
+        await packageId.source!.bind(_systemCache).describe(packageId);
     final rootLanguageVersion = rootPubspec.languageVersion;
     if (!rootLanguageVersion.supportsNullSafety) {
       final span =
@@ -110,7 +109,7 @@
     SolveResult result;
     try {
       result = await resolveVersions(
-        SolveType.GET,
+        SolveType.get,
         _systemCache,
         fakeRoot,
       );
@@ -119,10 +118,10 @@
           'Could not resolve constraints: $e');
     }
     return nullSafetyComplianceOfPackages(
-        result.packages.where((id) => id.name != fakeRootName),
-        Package(rootPubspec,
-            packageId.source.bind(_systemCache).getDirectory(packageId)),
-        containingPath);
+      result.packages.where((id) => id.name != fakeRootName),
+      Package(rootPubspec,
+          packageId.source!.bind(_systemCache).getDirectory(packageId)),
+    );
   }
 
   /// Decides if all dependendencies (transitively) have a language version
@@ -139,14 +138,13 @@
   Future<NullSafetyAnalysisResult> nullSafetyComplianceOfPackages(
     Iterable<PackageId> packages,
     Package rootPackage,
-    String containingPath,
   ) async {
-    NullSafetyAnalysisResult firstBadPackage;
+    NullSafetyAnalysisResult? firstBadPackage;
     for (final dependencyId in packages) {
       final packageInternalAnalysis =
           await _packageInternallyGoodCache.putIfAbsent(dependencyId, () async {
         Pubspec pubspec;
-        BoundSource boundSource;
+        BoundSource? boundSource;
         String packageDir;
         if (dependencyId.source == null) {
           pubspec = rootPackage.pubspec;
@@ -177,14 +175,13 @@
         final libDir =
             path.absolute(path.normalize(path.join(packageDir, 'lib')));
         if (dirExists(libDir)) {
-          final analysisSession = ContextBuilder()
-              .createContext(
-                sdkPath: getSdkPath(),
-                contextRoot: ContextLocator().locateRoots(
-                  includedPaths: [path.normalize(packageDir)],
-                ).first,
-              )
-              .currentSession;
+          var contextCollection = AnalysisContextCollection(
+            includedPaths: [path.normalize(packageDir)],
+            resourceProvider: PhysicalResourceProvider.INSTANCE,
+            sdkPath: getSdkPath(),
+          );
+          var analysisContext = contextCollection.contexts.first;
+          var analysisSession = analysisContext.currentSession;
 
           for (final file in listDir(libDir,
               recursive: true, includeDirs: false, includeHidden: true)) {
@@ -192,7 +189,7 @@
               final fileUrl =
                   'package:${dependencyId.name}/${path.relative(file, from: libDir)}';
               final someUnitResult =
-                  analysisSession.getParsedUnit2(path.normalize(file));
+                  analysisSession.getParsedUnit(path.normalize(file));
               ParsedUnitResult unitResult;
               if (someUnitResult is ParsedUnitResult) {
                 unitResult = someUnitResult;
@@ -225,7 +222,6 @@
         }
         return NullSafetyAnalysisResult(NullSafetyCompliance.compliant, null);
       });
-      assert(packageInternalAnalysis != null);
       if (packageInternalAnalysis.compliance ==
           NullSafetyCompliance.analysisFailed) {
         return packageInternalAnalysis;
@@ -251,12 +247,12 @@
   final NullSafetyCompliance compliance;
 
   /// `null` if compliance == [NullSafetyCompliance.compliant].
-  final String reason;
+  final String? reason;
 
   NullSafetyAnalysisResult(this.compliance, this.reason);
 }
 
-SourceSpan _tryGetSpanFromYamlMap(Object map, String key) {
+SourceSpan? _tryGetSpanFromYamlMap(Object? map, String key) {
   if (map is YamlMap) {
     return map.nodes[key]?.span;
   }
diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart
index c1284e9..7229bb1 100644
--- a/lib/src/oauth2.dart
+++ b/lib/src/oauth2.dart
@@ -2,11 +2,10 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:oauth2/oauth2.dart';
 import 'package:path/path.dart' as path;
 import 'package:shelf/shelf.dart' as shelf;
@@ -64,19 +63,21 @@
 ///
 /// This should always be the same as the credentials file stored in the system
 /// cache.
-Credentials _credentials;
+Credentials? _credentials;
 
 /// Delete the cached credentials, if they exist.
 void _clearCredentials(SystemCache cache) {
   _credentials = null;
   var credentialsFile = _credentialsFile(cache);
-  if (entryExists(credentialsFile)) deleteEntry(credentialsFile);
+  if (credentialsFile != null && entryExists(credentialsFile)) {
+    deleteEntry(credentialsFile);
+  }
 }
 
 /// Try to delete the cached credentials.
 void logout(SystemCache cache) {
   var credentialsFile = _credentialsFile(cache);
-  if (entryExists(_credentialsFile(cache))) {
+  if (credentialsFile != null && entryExists(credentialsFile)) {
     log.message('Logging out of pub.dartlang.org.');
     log.message('Deleting $credentialsFile');
     _clearCredentials(cache);
@@ -151,14 +152,14 @@
 ///
 /// If the credentials can't be loaded for any reason, the returned [Future]
 /// completes to `null`.
-Credentials loadCredentials(SystemCache cache) {
+Credentials? loadCredentials(SystemCache cache) {
   log.fine('Loading OAuth2 credentials.');
 
   try {
     if (_credentials != null) return _credentials;
 
     var path = _credentialsFile(cache);
-    if (!fileExists(path)) return null;
+    if (path == null || !fileExists(path)) return null;
 
     var credentials = Credentials.fromJson(readTextFile(path));
     if (credentials.isExpired && !credentials.canRefresh) {
@@ -181,8 +182,10 @@
   log.fine('Saving OAuth2 credentials.');
   _credentials = credentials;
   var credentialsPath = _credentialsFile(cache);
-  ensureDir(path.dirname(credentialsPath));
-  writeTextFile(credentialsPath, credentials.toJson(), dontLogContents: true);
+  if (credentialsPath != null) {
+    ensureDir(path.dirname(credentialsPath));
+    writeTextFile(credentialsPath, credentials.toJson(), dontLogContents: true);
+  }
 }
 
 /// The path to the file in which the user's OAuth2 credentials are stored.
@@ -191,10 +194,18 @@
 /// best place for storing secrets, as it might be shared.
 ///
 /// To provide backwards compatibility we use the legacy file if only it exists.
-String _credentialsFile(SystemCache cache) {
-  final newCredentialsFile = path.join(dartConfigDir, 'pub-credentials.json');
-  return [newCredentialsFile, _legacyCredentialsFile(cache)]
-      .firstWhere(fileExists, orElse: () => newCredentialsFile);
+///
+/// Returns `null` if there is no good place for the file.
+String? _credentialsFile(SystemCache cache) {
+  final configDir = dartConfigDir;
+
+  final newCredentialsFile =
+      configDir == null ? null : path.join(configDir, 'pub-credentials.json');
+  var file = [
+    if (newCredentialsFile != null) newCredentialsFile,
+    _legacyCredentialsFile(cache)
+  ].firstWhereOrNull(fileExists);
+  return file ?? newCredentialsFile;
 }
 
 String _legacyCredentialsFile(SystemCache cache) {
@@ -223,7 +234,7 @@
     }
 
     log.message('Authorization received, processing...');
-    var queryString = request.url.query ?? '';
+    var queryString = request.url.query;
     // Closing the server here is safe, since it will wait until the response
     // is sent to actually shut down.
     server.close();
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 6a3b0ff..d100157 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -2,10 +2,9 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -36,19 +35,26 @@
     return a.version.compareTo(b.version);
   }
 
+  final String? _dir;
+
   /// The path to the directory containing the package.
-  final String dir;
+  ///
+  /// It is an error to access this on an in-memory package.
+  String get dir {
+    if (isInMemory) {
+      throw UnsupportedError(
+          'Package directory cannot be used for an in-memory package');
+    }
+
+    return _dir!;
+  }
 
   /// An in-memory package can be created for doing a resolution without having
   /// a package on disk. Paths should not be resolved for these.
-  bool get _isInMemory => dir == null;
+  bool get isInMemory => _dir == null;
 
   /// The name of the package.
-  String get name {
-    if (pubspec.name != null) return pubspec.name;
-    if (dir != null) return p.basename(dir);
-    return null;
-  }
+  String get name => pubspec.name;
 
   /// The package's version.
   Version get version => pubspec.version;
@@ -77,10 +83,12 @@
       ..addAll(dependencyOverrides);
   }
 
-  /// Returns a list of asset ids for all Dart executables in this package's bin
+  /// Returns a list of paths to all Dart executables in this package's bin
   /// directory.
   List<String> get executablePaths {
-    return ordered(listFiles(beneath: 'bin', recursive: false))
+    final binDir = p.join(dir, 'bin');
+    if (!dirExists(binDir)) return <String>[];
+    return ordered(listDir(p.join(dir, 'bin'), includeDirs: false))
         .where((executable) => p.extension(executable) == '.dart')
         .map((executable) => p.relative(executable, from: dir))
         .toList();
@@ -95,7 +103,7 @@
   /// If multiple READMEs are found, this uses the same conventions as
   /// pub.dartlang.org for choosing the primary one: the README with the fewest
   /// extensions that is lexically ordered first is chosen.
-  String get readmePath {
+  String? get readmePath {
     var readmes = listFiles(recursive: false)
         .map(p.basename)
         .where((entry) => entry.contains(_readmeRegexp));
@@ -112,61 +120,57 @@
 
   /// Returns the path to the CHANGELOG file at the root of the entrypoint, or
   /// null if no CHANGELOG file is found.
-  String get changelogPath {
-    return listFiles(recursive: false).firstWhere(
-        (entry) => p.basename(entry).contains(_changelogRegexp),
-        orElse: () => null);
+  String? get changelogPath {
+    return listFiles(recursive: false).firstWhereOrNull(
+        (entry) => p.basename(entry).contains(_changelogRegexp));
   }
 
   /// Returns whether or not this package is in a Git repo.
-  bool get inGitRepo {
-    if (_inGitRepoCache != null) return _inGitRepoCache;
+  late final bool inGitRepo = computeInGitRepoCache();
 
-    if (dir == null || !git.isInstalled) {
-      _inGitRepoCache = false;
+  bool computeInGitRepoCache() {
+    if (isInMemory || !git.isInstalled) {
+      return false;
     } else {
       // If the entire package directory is ignored, don't consider it part of a
       // git repo. `git check-ignore` will return a status code of 0 for
       // ignored, 1 for not ignored, and 128 for not a Git repo.
-      var result = runProcessSync(git.command, ['check-ignore', '--quiet', '.'],
+      var result = runProcessSync(
+          git.command!, ['check-ignore', '--quiet', '.'],
           workingDir: dir);
-      _inGitRepoCache = result.exitCode == 1;
+      return result.exitCode == 1;
     }
-
-    return _inGitRepoCache;
   }
 
-  bool _inGitRepoCache;
-
   /// Loads the package whose root directory is [packageDir].
   ///
   /// [name] is the expected name of that package (e.g. the name given in the
   /// dependency), or `null` if the package being loaded is the entrypoint
   /// package.
-  Package.load(String name, this.dir, SourceRegistry sources)
-      : pubspec = Pubspec.load(dir, sources, expectedName: name);
+  Package.load(String? name, String this._dir, SourceRegistry sources)
+      : pubspec = Pubspec.load(_dir, sources, expectedName: name);
 
   /// Constructs a package with the given pubspec.
   ///
   /// The package will have no directory associated with it.
-  Package.inMemory(this.pubspec) : dir = null;
+  Package.inMemory(this.pubspec) : _dir = null;
 
   /// Creates a package with [pubspec] located at [dir].
-  Package(this.pubspec, this.dir);
+  Package(this.pubspec, String this._dir);
 
   /// Given a relative path within this package, returns its absolute path.
   ///
   /// This is similar to `p.join(dir, part1, ...)`, except that subclasses may
   /// override it to report that certain paths exist elsewhere than within
   /// [dir].
-  String path(String part1,
-      [String part2,
-      String part3,
-      String part4,
-      String part5,
-      String part6,
-      String part7]) {
-    if (_isInMemory) {
+  String path(String? part1,
+      [String? part2,
+      String? part3,
+      String? part4,
+      String? part5,
+      String? part6,
+      String? part7]) {
+    if (isInMemory) {
       throw StateError("Package $name is in-memory and doesn't have paths "
           'on disk.');
     }
@@ -176,7 +180,7 @@
   /// Given an absolute path within this package (such as that returned by
   /// [path] or [listFiles]), returns it relative to the package root.
   String relative(String path) {
-    if (dir == null) {
+    if (isInMemory) {
       throw StateError("Package $name is in-memory and doesn't have paths "
           'on disk.');
     }
@@ -184,7 +188,7 @@
   }
 
   /// Returns the type of dependency from this package onto [name].
-  DependencyType dependencyType(String name) {
+  DependencyType dependencyType(String? name) {
     if (pubspec.fields['dependencies']?.containsKey(name) ?? false) {
       return DependencyType.direct;
     } else if (pubspec.fields['dev_dependencies']?.containsKey(name) ?? false) {
@@ -220,22 +224,15 @@
   ///
   /// Note that the returned paths won't always be beneath [dir]. To safely
   /// convert them to paths relative to the package root, use [relative].
-  List<String> listFiles({String beneath, bool recursive = true}) {
+  List<String> listFiles({String? beneath, bool recursive = true}) {
     // An in-memory package has no files.
-    if (dir == null) return [];
+    if (isInMemory) return [];
 
-    var root = dir;
-    if (git.isInstalled) {
-      try {
-        root = p.normalize(
-          git.runSync(['rev-parse', '--show-toplevel'], workingDir: dir).first,
-        );
-      } on git.GitException {
-        // Not in a git folder.
-      }
-    }
+    var packageDir = dir;
+    var root = git.repoRoot(packageDir) ?? packageDir;
     beneath = p
-        .toUri(p.normalize(p.relative(p.join(dir, beneath ?? '.'), from: root)))
+        .toUri(p.normalize(
+            p.relative(p.join(packageDir, beneath ?? '.'), from: root)))
         .path;
     if (beneath == './') beneath = '.';
     String resolve(String path) {
@@ -279,7 +276,7 @@
             : (fileExists(gitIgnore) ? gitIgnore : null);
 
         final rules = [
-          if (dir == '.') ..._basicIgnoreRules,
+          if (dir == beneath) ..._basicIgnoreRules,
           if (ignoreFile != null) readTextFile(ignoreFile),
         ];
         return rules.isEmpty
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index fad20f7..d64808b 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -2,9 +2,7 @@
 // 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.
 
-// @dart=2.10
-
-import 'package:meta/meta.dart';
+import 'dart:convert';
 
 import 'package:pub_semver/pub_semver.dart';
 
@@ -22,38 +20,36 @@
 
   /// Date-time the `.dart_tool/package_config.json` file was generated.
   ///
-  /// This property is **optional** and may be `null` if not given.
-  DateTime generated;
+  /// `null` if not given.
+  DateTime? generated;
 
   /// Tool that generated the `.dart_tool/package_config.json` file.
   ///
   /// For `pub` this is always `'pub'`.
   ///
-  /// This property is **optional** and may be `null` if not given.
-  String generator;
+  /// `null` if not given.
+  String? generator;
 
   /// Version of the tool that generated the `.dart_tool/package_config.json`
   /// file.
   ///
   /// For `pub` this is the Dart SDK version from which `pub get` was called.
   ///
-  /// This property is **optional** and may be `null` if not given.
-  Version generatorVersion;
+  /// `null` if not given.
+  Version? generatorVersion;
 
   /// Additional properties not in the specification for the
   /// `.dart_tool/package_config.json` file.
   Map<String, dynamic> additionalProperties;
 
   PackageConfig({
-    @required this.configVersion,
-    @required this.packages,
+    required this.configVersion,
+    required this.packages,
     this.generated,
     this.generator,
     this.generatorVersion,
-    this.additionalProperties,
-  }) {
-    additionalProperties ??= {};
-  }
+    Map<String, dynamic>? additionalProperties,
+  }) : additionalProperties = additionalProperties ?? {};
 
   /// Create [PackageConfig] from JSON [data].
   ///
@@ -63,7 +59,7 @@
     if (data is! Map<String, dynamic>) {
       throw FormatException('package_config.json must be a JSON object');
     }
-    final root = data as Map<String, dynamic>;
+    final root = data;
 
     void _throw(String property, String mustBe) => throw FormatException(
         '"$property" in .dart_tool/package_config.json $mustBe');
@@ -87,7 +83,7 @@
     }
 
     // Read the 'generated' property
-    DateTime generated;
+    DateTime? generated;
     final generatedRaw = root['generated'];
     if (generatedRaw != null) {
       if (generatedRaw is! String) {
@@ -104,7 +100,7 @@
     }
 
     // Read the 'generatorVersion' property
-    Version generatorVersion;
+    Version? generatorVersion;
     final generatorVersionRaw = root['generatorVersion'];
     if (generatorVersionRaw != null) {
       if (generatorVersionRaw is! String) {
@@ -134,13 +130,13 @@
   }
 
   /// Convert to JSON structure.
-  Map<String, Object> toJson() => {
+  Map<String, Object?> toJson() => {
         'configVersion': configVersion,
         'packages': packages.map((p) => p.toJson()).toList(),
-        'generated': generated?.toUtc()?.toIso8601String(),
+        'generated': generated?.toUtc().toIso8601String(),
         'generator': generator,
         'generatorVersion': generatorVersion?.toString(),
-      }..addAll(additionalProperties ?? {});
+      }..addAll(additionalProperties);
 }
 
 class PackageConfigEntry {
@@ -158,31 +154,27 @@
   /// Import statements in Dart programs are resolved relative to this folder.
   /// This must be in the sub-tree under [rootUri].
   ///
-  /// This property is **optional** and may be `null` if not given.
-  Uri packageUri;
+  /// `null` if not given.
+  Uri? packageUri;
 
   /// Language version used by package.
   ///
   /// Given as `<major>.<minor>` version, similar to the `// @dart = X.Y`
   /// comment. This is derived from the lower-bound on the Dart SDK requirement
   /// in the `pubspec.yaml` for the given package.
-  ///
-  /// This property is **optional** and may be `null` if not given.
-  LanguageVersion languageVersion;
+  LanguageVersion? languageVersion;
 
   /// Additional properties not in the specification for the
   /// `.dart_tool/package_config.json` file.
-  Map<String, dynamic> additionalProperties;
+  Map<String, dynamic>? additionalProperties;
 
   PackageConfigEntry({
-    @required this.name,
-    @required this.rootUri,
+    required this.name,
+    required this.rootUri,
     this.packageUri,
     this.languageVersion,
-    this.additionalProperties,
-  }) {
-    additionalProperties ??= {};
-  }
+    this.additionalProperties = const {},
+  });
 
   /// Create [PackageConfigEntry] from JSON [data].
   ///
@@ -193,9 +185,9 @@
       throw FormatException(
           'packages[] entries in package_config.json must be JSON objects');
     }
-    final root = data as Map<String, dynamic>;
+    final root = data;
 
-    void _throw(String property, String mustBe) => throw FormatException(
+    Never _throw(String property, String mustBe) => throw FormatException(
         '"packages[].$property" in .dart_tool/package_config.json $mustBe');
 
     final name = root['name'];
@@ -203,7 +195,7 @@
       _throw('name', 'must be a string');
     }
 
-    Uri rootUri;
+    final Uri rootUri;
     final rootUriRaw = root['rootUri'];
     if (rootUriRaw is! String) {
       _throw('rootUri', 'must be a string');
@@ -214,7 +206,7 @@
       _throw('rootUri', 'must be a URI');
     }
 
-    Uri packageUri;
+    Uri? packageUri;
     var packageUriRaw = root['packageUri'];
     if (packageUriRaw != null) {
       if (packageUriRaw is! String) {
@@ -230,7 +222,7 @@
       }
     }
 
-    LanguageVersion languageVersion;
+    LanguageVersion? languageVersion;
     final languageVersionRaw = root['languageVersion'];
     if (languageVersionRaw != null) {
       if (languageVersionRaw is! String) {
@@ -252,10 +244,16 @@
   }
 
   /// Convert to JSON structure.
-  Map<String, Object> toJson() => {
+  Map<String, Object?> toJson() => {
         'name': name,
         'rootUri': rootUri.toString(),
-        if (packageUri != null) 'packageUri': packageUri?.toString(),
+        if (packageUri != null) 'packageUri': packageUri.toString(),
         if (languageVersion != null) 'languageVersion': '$languageVersion',
       }..addAll(additionalProperties ?? {});
+
+  @override
+  String toString() {
+    // TODO: implement toString
+    return JsonEncoder.withIndent('  ').convert(toJson());
+  }
 }
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index ed1f30f..45351d7 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart' hide mapMap;
 
 import 'entrypoint.dart';
@@ -32,7 +30,7 @@
   final Map<String, Package> packages;
 
   /// A map of transitive dependencies for each package.
-  Map<String, Set<Package>> _transitiveDependencies;
+  Map<String, Set<Package>>? _transitiveDependencies;
 
   PackageGraph(this.entrypoint, this.lockFile, this.packages);
 
@@ -47,7 +45,7 @@
         value: (id) {
           if (id.name == entrypoint.root.name) return entrypoint.root;
 
-          return Package(result.pubspecs[id.name],
+          return Package(result.pubspecs[id.name]!,
               entrypoint.cache.source(id.source).getDirectory(id));
         });
 
@@ -69,13 +67,12 @@
       _transitiveDependencies =
           mapMap<String, Set<String>, String, Set<Package>>(closure,
               value: (depender, names) {
-        var set = names.map((name) => packages[name]).toSet();
-        set.add(packages[depender]);
+        var set = names.map((name) => packages[name]!).toSet();
+        set.add(packages[depender]!);
         return set;
       });
     }
-
-    return _transitiveDependencies[package];
+    return _transitiveDependencies![package]!;
   }
 
   bool _isPackageCached(String package) {
@@ -86,7 +83,7 @@
     if (package == entrypoint.root.name) {
       return entrypoint.isCached;
     } else {
-      var id = lockFile.packages[package];
+      var id = lockFile.packages[package]!;
       return entrypoint.cache.source(id.source) is CachedSource;
     }
   }
diff --git a/lib/src/package_name.dart b/lib/src/package_name.dart
index a301338..d83f466 100644
--- a/lib/src/package_name.dart
+++ b/lib/src/package_name.dart
@@ -2,16 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
 import 'package.dart';
 import 'source.dart';
-import 'source/git.dart';
 import 'source/hosted.dart';
-import 'source/path.dart';
 import 'utils.dart';
 
 /// The equality to use when comparing the feature sets of two package names.
@@ -25,7 +21,7 @@
   /// The [Source] used to look up this package.
   ///
   /// If this is a root package, this will be `null`.
-  final Source source;
+  final Source? source;
 
   /// The metadata used by the package's [source] to identify and locate it.
   ///
@@ -53,25 +49,31 @@
   /// `this.toRef() == other.toRef()`.
   bool samePackage(PackageName other) {
     if (other.name != name) return false;
-    if (source == null) return other.source == null;
+    var thisSource = source;
+    if (thisSource == null) return other.source == null;
 
-    return other.source == source &&
-        source.descriptionsEqual(description, other.description);
+    return other.source == thisSource &&
+        thisSource.descriptionsEqual(description, other.description);
   }
 
   @override
+  bool operator ==(Object other) =>
+      throw UnimplementedError('Subclass should implement ==');
+
+  @override
   int get hashCode {
-    if (source == null) return name.hashCode;
+    var thisSource = source;
+    if (thisSource == null) return name.hashCode;
     return name.hashCode ^
-        source.hashCode ^
-        source.hashDescription(description);
+        thisSource.hashCode ^
+        thisSource.hashDescription(description);
   }
 
   /// Returns a string representation of this package name.
   ///
   /// If [detail] is passed, it controls exactly which details are included.
   @override
-  String toString([PackageDetail detail]);
+  String toString([PackageDetail? detail]);
 }
 
 /// A reference to a [Package], but not any particular version(s) of it.
@@ -82,14 +84,14 @@
   /// Since an ID's description is an implementation detail of its source, this
   /// should generally not be called outside of [Source] subclasses. A reference
   /// can be obtained from a user-supplied description using [Source.parseRef].
-  PackageRef(String name, Source source, description)
+  PackageRef(String name, Source? source, description)
       : super._(name, source, description);
 
   /// Creates a reference to the given root package.
   PackageRef.root(Package package) : super._(package.name, null, package.name);
 
   @override
-  String toString([PackageDetail detail]) {
+  String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
     if (isRoot) return name;
 
@@ -97,7 +99,7 @@
     if (detail.showSource ?? source is! HostedSource) {
       buffer.write(' from $source');
       if (detail.showDescription) {
-        buffer.write(' ${source.formatDescription(description)}');
+        buffer.write(' ${source!.formatDescription(description)}');
       }
     }
 
@@ -106,6 +108,9 @@
 
   @override
   bool operator ==(other) => other is PackageRef && samePackage(other);
+
+  @override
+  int get hashCode => super.hashCode ^ 'PackageRef'.hashCode;
 }
 
 /// A reference to a specific version of a package.
@@ -130,7 +135,7 @@
   ///
   /// Since an ID's description is an implementation detail of its source, this
   /// should generally not be called outside of [Source] subclasses.
-  PackageId(String name, Source source, this.version, description)
+  PackageId(String name, Source? source, this.version, description)
       : super._(name, source, description);
 
   /// Creates an ID for the given root package.
@@ -149,7 +154,7 @@
   PackageRange toRange() => withConstraint(version);
 
   @override
-  String toString([PackageDetail detail]) {
+  String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
 
     var buffer = StringBuffer(name);
@@ -158,7 +163,7 @@
     if (!isRoot && (detail.showSource ?? source is! HostedSource)) {
       buffer.write(' from $source');
       if (detail.showDescription) {
-        buffer.write(' ${source.formatDescription(description)}');
+        buffer.write(' ${source!.formatDescription(description)}');
       }
     }
 
@@ -179,8 +184,8 @@
   ///
   /// Since an ID's description is an implementation detail of its source, this
   /// should generally not be called outside of [Source] subclasses.
-  PackageRange(String name, Source source, this.constraint, description,
-      {Map<String, FeatureDependency> features})
+  PackageRange(String name, Source? source, this.constraint, description,
+      {Map<String, FeatureDependency>? features})
       : features = features == null
             ? const {}
             : UnmodifiableMapView(Map.from(features)),
@@ -220,7 +225,7 @@
   }
 
   @override
-  String toString([PackageDetail detail]) {
+  String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
 
     var buffer = StringBuffer(name);
@@ -231,7 +236,7 @@
     if (!isRoot && (detail.showSource ?? source is! HostedSource)) {
       buffer.write(' from $source');
       if (detail.showDescription) {
-        buffer.write(' ${source.formatDescription(description)}');
+        buffer.write(' ${source!.formatDescription(description)}');
       }
     }
 
@@ -246,9 +251,7 @@
   bool get _showVersionConstraint {
     if (isRoot) return false;
     if (!constraint.isAny) return true;
-    if (source is PathSource) return false;
-    if (source is GitSource) return false;
-    return true;
+    return source!.hasMultipleVersions;
   }
 
   /// Returns a new [PackageRange] with [features] merged with [this.features].
@@ -267,9 +270,10 @@
     var range = constraint as VersionRange;
     if (!range.includeMin) return this;
     if (range.includeMax) return this;
-    if (range.min == null) return this;
-    if (range.max == range.min.nextBreaking.firstPreRelease) {
-      return withConstraint(VersionConstraint.compatibleWith(range.min));
+    var min = range.min;
+    if (min == null) return this;
+    if (range.max == min.nextBreaking.firstPreRelease) {
+      return withConstraint(VersionConstraint.compatibleWith(min));
     } else {
       return this;
     }
@@ -328,13 +332,13 @@
   /// If this is `null`, the version is shown for all packages other than root
   /// [PackageId]s or [PackageRange]s with `git` or `path` sources and `any`
   /// constraints.
-  final bool showVersion;
+  final bool? showVersion;
 
   /// Whether to show the package source.
   ///
   /// If this is `null`, the source is shown for all non-hosted, non-root
   /// packages. It's always `true` if [showDescription] is `true`.
-  final bool showSource;
+  final bool? showSource;
 
   /// Whether to show the package description.
   ///
@@ -348,9 +352,9 @@
 
   const PackageDetail(
       {this.showVersion,
-      bool showSource,
-      bool showDescription,
-      bool showFeatures})
+      bool? showSource,
+      bool? showDescription,
+      bool? showFeatures})
       : showSource = showDescription == true ? true : showSource,
         showDescription = showDescription ?? false,
         showFeatures = showFeatures ?? true;
@@ -358,8 +362,8 @@
   /// Returns a [PackageDetail] with the maximum amount of detail between [this]
   /// and [other].
   PackageDetail max(PackageDetail other) => PackageDetail(
-      showVersion: showVersion || other.showVersion,
-      showSource: showSource || other.showSource,
+      showVersion: showVersion! || other.showVersion!,
+      showSource: showSource! || other.showSource!,
       showDescription: showDescription || other.showDescription,
       showFeatures: showFeatures || other.showFeatures);
 }
diff --git a/lib/src/progress.dart b/lib/src/progress.dart
index 06ad9e9..b9c6931 100644
--- a/lib/src/progress.dart
+++ b/lib/src/progress.dart
@@ -34,7 +34,7 @@
   Progress(this._message, {bool fine = false}) {
     _stopwatch.start();
 
-    var level = fine ? log.Level.FINE : log.Level.MESSAGE;
+    var level = fine ? log.Level.fine : log.Level.message;
 
     // The animation is only shown when it would be meaningful to a human.
     // That means we're writing a visible message to a TTY at normal log levels
@@ -43,7 +43,7 @@
         !log.verbosity.isLevelVisible(level) ||
         log.json.enabled ||
         fine ||
-        log.verbosity.isLevelVisible(log.Level.FINE)) {
+        log.verbosity.isLevelVisible(log.Level.fine)) {
       // Not animating, so just log the start and wait until the task is
       // completed.
       log.write(level, '$_message...');
diff --git a/lib/src/pub_embeddable_command.dart b/lib/src/pub_embeddable_command.dart
index 4240ad1..b9e59c6 100644
--- a/lib/src/pub_embeddable_command.dart
+++ b/lib/src/pub_embeddable_command.dart
@@ -2,8 +2,10 @@
 // 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.
 
-// @dart=2.10
+import 'package:meta/meta.dart';
+import 'package:usage/usage.dart';
 
+import 'command.dart' show PubCommand, PubTopLevel;
 import 'command.dart';
 import 'command/add.dart';
 import 'command/build.dart';
@@ -24,6 +26,22 @@
 import 'log.dart' as log;
 import 'log.dart';
 
+/// The information needed for the embedded pub command to send analytics.
+@sealed
+class PubAnalytics {
+  /// Name of the custom dimension of the dependency kind.
+  final String dependencyKindCustomDimensionName;
+
+  final Analytics? Function() _analyticsGetter;
+
+  Analytics? get analytics => _analyticsGetter();
+
+  PubAnalytics(
+    this._analyticsGetter, {
+    required this.dependencyKindCustomDimensionName,
+  });
+}
+
 /// Exposes the `pub` commands as a command to be embedded in another command
 /// runner such as `dart pub`.
 class PubEmbeddableCommand extends PubCommand implements PubTopLevel {
@@ -37,11 +55,16 @@
   @override
   String get directory => argResults['directory'];
 
-  PubEmbeddableCommand() : super() {
+  @override
+  final PubAnalytics? analytics;
+
+  final bool Function() isVerbose;
+
+  PubEmbeddableCommand(this.analytics, this.isVerbose) : super() {
     argParser.addFlag('trace',
         help: 'Print debugging information when an error occurs.');
     argParser.addFlag('verbose',
-        abbr: 'v', negatable: false, help: 'Shortcut for "--verbosity=all".');
+        abbr: 'v', negatable: false, help: 'Print detailed logging.');
     argParser.addOption(
       'directory',
       abbr: 'C',
@@ -80,12 +103,15 @@
   }
 
   @override
-  bool get captureStackChains => argResults['verbose'];
+  bool get captureStackChains => _isVerbose;
 
   @override
-  Verbosity get verbosity =>
-      argResults['verbose'] ? Verbosity.ALL : Verbosity.NORMAL;
+  Verbosity get verbosity => _isVerbose ? Verbosity.all : Verbosity.normal;
 
   @override
-  bool get trace => argResults['verbose'];
+  bool get trace => _isVerbose;
+
+  bool get _isVerbose {
+    return argResults['verbose'] || isVerbose();
+  }
 }
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 5a3d3c0..cc75b2d 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -2,12 +2,9 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:collection/collection.dart' hide mapMap;
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:source_span/source_span.dart';
@@ -19,23 +16,18 @@
 import 'language_version.dart';
 import 'log.dart';
 import 'package_name.dart';
+import 'pubspec_parse.dart';
 import 'sdk.dart';
 import 'source_registry.dart';
 import 'utils.dart';
 
-/// A regular expression matching allowed package names.
-///
-/// This allows dot-separated valid Dart identifiers. The dots are there for
-/// compatibility with Google's internal Dart packages, but they may not be used
-/// when publishing a package to pub.dartlang.org.
-final _packageName =
-    RegExp('^${identifierRegExp.pattern}(\\.${identifierRegExp.pattern})*\$');
+export 'pubspec_parse.dart' hide PubspecBase;
 
 /// The default SDK upper bound constraint for packages that don't declare one.
 ///
 /// This provides a sane default for packages that don't have an upper bound.
 final VersionRange _defaultUpperBoundSdkConstraint =
-    VersionConstraint.parse('<2.0.0');
+    VersionConstraint.parse('<2.0.0') as VersionRange;
 
 /// Whether or not to allow the pre-release SDK for packages that have an
 /// upper bound Dart SDK constraint of <2.0.0.
@@ -72,7 +64,7 @@
 /// accessed. This allows a partially-invalid pubspec to be used if only the
 /// valid portions are relevant. To get a list of all errors in the pubspec, use
 /// [allErrors].
-class Pubspec {
+class Pubspec extends PubspecBase {
   // If a new lazily-initialized field is added to this class and the
   // initialization can throw a [PubspecException], that error should also be
   // exposed through [allErrors].
@@ -82,145 +74,64 @@
   ///
   /// This will be null if this was created using [new Pubspec] or [new
   /// Pubspec.empty].
-  final SourceRegistry _sources;
+  final SourceRegistry? _sources;
 
   /// The location from which the pubspec was loaded.
   ///
   /// This can be null if the pubspec was created in-memory or if its location
   /// is unknown.
-  Uri get _location => fields.span.sourceUrl;
-
-  /// All pubspec fields.
-  ///
-  /// This includes the fields from which other properties are derived.
-  final YamlMap fields;
-
-  /// Whether or not to apply the [_defaultUpperBoundsSdkConstraint] to this
-  /// pubspec.
-  final bool _includeDefaultSdkConstraint;
-
-  /// Whether or not the SDK version was overridden from <2.0.0 to
-  /// <2.0.0-dev.infinity.
-  bool get dartSdkWasOverridden => _dartSdkWasOverridden;
-  bool _dartSdkWasOverridden = false;
-
-  /// The package's name.
-  String get name {
-    if (_name != null) return _name;
-
-    var name = fields['name'];
-    if (name == null) {
-      throw PubspecException('Missing the required "name" field.', fields.span);
-    } else if (name is! String) {
-      throw PubspecException(
-          '"name" field must be a string.', fields.nodes['name'].span);
-    } else if (!_packageName.hasMatch(name)) {
-      throw PubspecException('"name" field must be a valid Dart identifier.',
-          fields.nodes['name'].span);
-    } else if (reservedWords.contains(name)) {
-      throw PubspecException('"name" field may not be a Dart reserved word.',
-          fields.nodes['name'].span);
-    }
-
-    _name = name;
-    return _name;
-  }
-
-  String _name;
-
-  /// The package's version.
-  Version get version {
-    if (_version != null) return _version;
-
-    var version = fields['version'];
-    if (version == null) {
-      _version = Version.none;
-      return _version;
-    }
-
-    var span = fields.nodes['version'].span;
-    if (version is num) {
-      var fixed = '$version.0';
-      if (version is int) {
-        fixed = '$fixed.0';
-      }
-      _error(
-          '"version" field must have three numeric components: major, '
-          'minor, and patch. Instead of "$version", consider "$fixed".',
-          span);
-    }
-    if (version is! String) {
-      _error('"version" field must be a string.', span);
-    }
-
-    _version = _wrapFormatException(
-        'version number', span, () => Version.parse(version));
-    return _version;
-  }
-
-  Version _version;
+  Uri? get _location => fields.span.sourceUrl;
 
   /// The additional packages this package depends on.
-  Map<String, PackageRange> get dependencies {
-    if (_dependencies != null) return _dependencies;
-    _dependencies =
-        _parseDependencies('dependencies', fields.nodes['dependencies']);
-    return _dependencies;
-  }
+  Map<String, PackageRange> get dependencies => _dependencies ??=
+      _parseDependencies('dependencies', fields.nodes['dependencies']);
 
-  Map<String, PackageRange> _dependencies;
+  Map<String, PackageRange>? _dependencies;
 
   /// The packages this package depends on when it is the root package.
-  Map<String, PackageRange> get devDependencies {
-    if (_devDependencies != null) return _devDependencies;
-    _devDependencies = _parseDependencies(
-        'dev_dependencies', fields.nodes['dev_dependencies']);
-    return _devDependencies;
-  }
+  Map<String, PackageRange> get devDependencies => _devDependencies ??=
+      _parseDependencies('dev_dependencies', fields.nodes['dev_dependencies']);
 
-  Map<String, PackageRange> _devDependencies;
+  Map<String, PackageRange>? _devDependencies;
 
   /// The dependency constraints that this package overrides when it is the
   /// root package.
   ///
   /// Dependencies here will replace any dependency on a package with the same
   /// name anywhere in the dependency graph.
-  Map<String, PackageRange> get dependencyOverrides {
-    if (_dependencyOverrides != null) return _dependencyOverrides;
-    _dependencyOverrides = _parseDependencies(
-        'dependency_overrides', fields.nodes['dependency_overrides']);
-    return _dependencyOverrides;
-  }
+  Map<String, PackageRange> get dependencyOverrides =>
+      _dependencyOverrides ??= _parseDependencies(
+          'dependency_overrides', fields.nodes['dependency_overrides']);
 
-  Map<String, PackageRange> _dependencyOverrides;
+  Map<String, PackageRange>? _dependencyOverrides;
 
-  Map<String, Feature> get features {
-    if (_features != null) return _features;
-    var features = fields['features'];
+  late final Map<String, Feature> features = _computeFeatures();
+
+  Map<String, Feature> _computeFeatures() {
+    final features = fields['features'];
     if (features == null) {
-      _features = const {};
-      return _features;
+      return const {};
     }
 
-    if (features is! Map) {
-      _error('"features" field must be a map.', fields.nodes['features'].span);
+    if (features is! YamlMap) {
+      _error('"features" field must be a map.', fields.nodes['features']!.span);
     }
 
-    _features = mapMap(features.nodes,
-        key: (nameNode, _) => _validateFeatureName(nameNode),
-        value: (nameNode, specNode) {
+    return mapMap(features.nodes,
+        key: (dynamic nameNode, dynamic _) => _validateFeatureName(nameNode),
+        value: (dynamic nameNode, dynamic specNode) {
           if (specNode.value == null) {
             return Feature(nameNode.value, const []);
           }
 
-          if (specNode is! Map) {
+          if (specNode is! YamlMap) {
             _error('A feature specification must be a map.', specNode.span);
           }
 
           var onByDefault = specNode['default'] ?? true;
           if (onByDefault is! bool) {
             _error('Default must be true or false.',
-                specNode.nodes['default'].span);
+                specNode.nodes['default']!.span);
           }
 
           var requires = _parseStringList(specNode.nodes['requires'],
@@ -238,18 +149,24 @@
               sdkConstraints: sdkConstraints,
               onByDefault: onByDefault);
         });
-    return _features;
   }
 
-  Map<String, Feature> _features;
-
   /// A map from SDK identifiers to constraints on those SDK versions.
   Map<String, VersionConstraint> get sdkConstraints {
     _ensureEnvironment();
-    return _sdkConstraints;
+    return _sdkConstraints!;
   }
 
-  Map<String, VersionConstraint> _sdkConstraints;
+  Map<String, VersionConstraint>? _sdkConstraints;
+
+  /// Whether or not to apply the [_defaultUpperBoundsSdkConstraint] to this
+  /// pubspec.
+  final bool _includeDefaultSdkConstraint;
+
+  /// Whether or not the SDK version was overridden from <2.0.0 to
+  /// <2.0.0-dev.infinity.
+  bool get dartSdkWasOverridden => _dartSdkWasOverridden;
+  bool _dartSdkWasOverridden = false;
 
   /// The original Dart SDK constraint as written in the pubspec.
   ///
@@ -257,10 +174,10 @@
   /// `sdkConstraints["dart"]`.
   VersionConstraint get originalDartSdkConstraint {
     _ensureEnvironment();
-    return _originalDartSdkConstraint ?? sdkConstraints['dart'];
+    return _originalDartSdkConstraint ?? sdkConstraints['dart']!;
   }
 
-  VersionConstraint _originalDartSdkConstraint;
+  VersionConstraint? _originalDartSdkConstraint;
 
   /// Ensures that the top-level "environment" field has been parsed and
   /// [_sdkConstraints] is set accordingly.
@@ -300,17 +217,19 @@
     if (!_allowPreReleaseSdk) return false;
     if (!sdk.version.isPreRelease) return false;
     if (sdkConstraint.includeMax) return false;
-    if (sdkConstraint.min != null &&
-        sdkConstraint.min.isPreRelease &&
-        equalsIgnoringPreRelease(sdkConstraint.min, sdk.version)) {
+    var minSdkConstraint = sdkConstraint.min;
+    if (minSdkConstraint != null &&
+        minSdkConstraint.isPreRelease &&
+        equalsIgnoringPreRelease(sdkConstraint.min!, sdk.version)) {
       return false;
     }
-    if (sdkConstraint.max == null) return false;
-    if (sdkConstraint.max.isPreRelease &&
-        !sdkConstraint.max.isFirstPreRelease) {
+    var maxSdkConstraint = sdkConstraint.max;
+    if (maxSdkConstraint == null) return false;
+    if (maxSdkConstraint.max.isPreRelease &&
+        !maxSdkConstraint.isFirstPreRelease) {
       return false;
     }
-    return equalsIgnoringPreRelease(sdkConstraint.max, sdk.version);
+    return equalsIgnoringPreRelease(maxSdkConstraint, sdk.version);
   }
 
   /// Parses the "environment" field in [parent] and returns a map from SDK
@@ -325,9 +244,9 @@
       };
     }
 
-    if (yaml is! Map) {
+    if (yaml is! YamlMap) {
       _error('"environment" field must be a map.',
-          parent.nodes['environment'].span);
+          parent.nodes['environment']!.span);
     }
 
     var constraints = {
@@ -353,141 +272,6 @@
     return constraints;
   }
 
-  /// The URL of the server that the package should default to being published
-  /// to, "none" if the package should not be published, or `null` if it should
-  /// be published to the default server.
-  ///
-  /// If this does return a URL string, it will be a valid parseable URL.
-  String get publishTo {
-    if (_parsedPublishTo) return _publishTo;
-
-    var publishTo = fields['publish_to'];
-    if (publishTo != null) {
-      var span = fields.nodes['publish_to'].span;
-
-      if (publishTo is! String) {
-        _error('"publish_to" field must be a string.', span);
-      }
-
-      // It must be "none" or a valid URL.
-      if (publishTo != 'none') {
-        _wrapFormatException('"publish_to" field', span, () {
-          var url = Uri.parse(publishTo);
-          if (url.scheme.isEmpty) {
-            throw FormatException('must be an absolute URL.');
-          }
-        });
-      }
-    }
-
-    _parsedPublishTo = true;
-    _publishTo = publishTo;
-    return _publishTo;
-  }
-
-  bool _parsedPublishTo = false;
-  String _publishTo;
-
-  /// The list of patterns covering _false-positive secrets_ in the package.
-  ///
-  /// This is a list of git-ignore style patterns for files that should be
-  /// ignored when trying to detect possible leaks of secrets during
-  /// package publication.
-  List<String> get falseSecrets {
-    if (_falseSecrets == null) {
-      final falseSecrets = <String>[];
-
-      // Throws a [PubspecException]
-      void _falseSecretsError(SourceSpan span) => _error(
-            '"false_secrets" field must be a list of git-ignore style patterns',
-            span,
-          );
-
-      final falseSecretsNode = fields.nodes['false_secrets'];
-      if (falseSecretsNode != null) {
-        if (falseSecretsNode is YamlList) {
-          for (final node in falseSecretsNode.nodes) {
-            final value = node.value;
-            if (value is! String) {
-              _falseSecretsError(node.span);
-            }
-            falseSecrets.add(value);
-          }
-        } else {
-          _falseSecretsError(falseSecretsNode.span);
-        }
-      }
-
-      _falseSecrets = List.unmodifiable(falseSecrets);
-    }
-    return _falseSecrets;
-  }
-
-  List<String> _falseSecrets;
-
-  /// The executables that should be placed on the user's PATH when this
-  /// package is globally activated.
-  ///
-  /// It is a map of strings to string. Each key is the name of the command
-  /// that will be placed on the user's PATH. The value is the name of the
-  /// .dart script (without extension) in the package's `bin` directory that
-  /// should be run for that command. Both key and value must be "simple"
-  /// strings: alphanumerics, underscores and hypens only. If a value is
-  /// omitted, it is inferred to use the same name as the key.
-  Map<String, String> get executables {
-    if (_executables != null) return _executables;
-
-    _executables = {};
-    var yaml = fields['executables'];
-    if (yaml == null) return _executables;
-
-    if (yaml is! Map) {
-      _error('"executables" field must be a map.',
-          fields.nodes['executables'].span);
-    }
-
-    yaml.nodes.forEach((key, value) {
-      if (key.value is! String) {
-        _error('"executables" keys must be strings.', key.span);
-      }
-
-      final keyPattern = RegExp(r'^[a-zA-Z0-9_-]+$');
-      if (!keyPattern.hasMatch(key.value)) {
-        _error(
-            '"executables" keys may only contain letters, '
-            'numbers, hyphens and underscores.',
-            key.span);
-      }
-
-      if (value.value == null) {
-        value = key;
-      } else if (value.value is! String) {
-        _error('"executables" values must be strings or null.', value.span);
-      }
-
-      final valuePattern = RegExp(r'[/\\]');
-      if (valuePattern.hasMatch(value.value)) {
-        _error('"executables" values may not contain path separators.',
-            value.span);
-      }
-
-      _executables[key.value] = value.value;
-    });
-
-    return _executables;
-  }
-
-  Map<String, String> _executables;
-
-  /// Whether the package is private and cannot be published.
-  ///
-  /// This is specified in the pubspec by setting "publish_to" to "none".
-  bool get isPrivate => publishTo == 'none';
-
-  /// Whether or not the pubspec has no contents.
-  bool get isEmpty =>
-      name == null && version == Version.none && dependencies.isEmpty;
-
   /// The language version implied by the sdk constraint.
   LanguageVersion get languageVersion =>
       LanguageVersion.fromSdkConstraint(originalDartSdkConstraint);
@@ -497,7 +281,7 @@
   /// If [expectedName] is passed and the pubspec doesn't have a matching name
   /// field, this will throw a [PubspecException].
   factory Pubspec.load(String packageDir, SourceRegistry sources,
-      {String expectedName}) {
+      {String? expectedName}) {
     var pubspecPath = path.join(packageDir, 'pubspec.yaml');
     var pubspecUri = path.toUri(pubspecPath);
     if (!fileExists(pubspecPath)) {
@@ -513,16 +297,15 @@
         expectedName: expectedName, location: pubspecUri);
   }
 
-  Pubspec(this._name,
-      {Version version,
-      Iterable<PackageRange> dependencies,
-      Iterable<PackageRange> devDependencies,
-      Iterable<PackageRange> dependencyOverrides,
-      Map fields,
-      SourceRegistry sources,
-      Map<String, VersionConstraint> sdkConstraints})
-      : _version = version,
-        _dependencies = dependencies == null
+  Pubspec(String name,
+      {Version? version,
+      Iterable<PackageRange>? dependencies,
+      Iterable<PackageRange>? devDependencies,
+      Iterable<PackageRange>? dependencyOverrides,
+      Map? fields,
+      SourceRegistry? sources,
+      Map<String, VersionConstraint>? sdkConstraints})
+      : _dependencies = dependencies == null
             ? null
             : Map.fromIterable(dependencies, key: (range) => range.name),
         _devDependencies = devDependencies == null
@@ -534,18 +317,22 @@
         _sdkConstraints = sdkConstraints ??
             UnmodifiableMapView({'dart': VersionConstraint.any}),
         _includeDefaultSdkConstraint = false,
-        fields = fields == null ? YamlMap() : YamlMap.wrap(fields),
-        _sources = sources;
-
+        _sources = sources,
+        super(
+          fields == null ? YamlMap() : YamlMap.wrap(fields),
+          name: name,
+          version: version,
+        );
   Pubspec.empty()
       : _sources = null,
-        _name = null,
-        _version = Version.none,
         _dependencies = {},
         _devDependencies = {},
         _sdkConstraints = {'dart': VersionConstraint.any},
         _includeDefaultSdkConstraint = false,
-        fields = YamlMap();
+        super(
+          YamlMap(),
+          version: Version.none,
+        );
 
   /// Returns a Pubspec object for an already-parsed map representing its
   /// contents.
@@ -555,11 +342,11 @@
   ///
   /// [location] is the location from which this pubspec was loaded.
   Pubspec.fromMap(Map fields, this._sources,
-      {String expectedName, Uri location})
-      : fields = fields is YamlMap
+      {String? expectedName, Uri? location})
+      : _includeDefaultSdkConstraint = true,
+        super(fields is YamlMap
             ? fields
-            : YamlMap.wrap(fields, sourceUrl: location),
-        _includeDefaultSdkConstraint = true {
+            : YamlMap.wrap(fields, sourceUrl: location)) {
     // If [expectedName] is passed, ensure that the actual 'name' field exists
     // and matches the expectation.
     if (expectedName == null) return;
@@ -568,7 +355,7 @@
     throw PubspecException(
         '"name" field doesn\'t match expected name '
         '"$expectedName".',
-        this.fields.nodes['name'].span);
+        this.fields.nodes['name']!.span);
   }
 
   /// Parses the pubspec stored at [filePath] whose text is [contents].
@@ -576,7 +363,7 @@
   /// If the pubspec doesn't define a version for itself, it defaults to
   /// [Version.none].
   factory Pubspec.parse(String contents, SourceRegistry sources,
-      {String expectedName, Uri location}) {
+      {String? expectedName, Uri? location}) {
     YamlNode pubspecNode;
     try {
       pubspecNode = loadYamlNode(contents, sourceUrl: location);
@@ -625,7 +412,7 @@
 
   /// Parses the dependency field named [field], and returns the corresponding
   /// map of dependency names to dependencies.
-  Map<String, PackageRange> _parseDependencies(String field, YamlNode node) {
+  Map<String, PackageRange> _parseDependencies(String field, YamlNode? node) {
     var dependencies = <String, PackageRange>{};
 
     // Allow an empty dependencies key.
@@ -635,31 +422,28 @@
       _error('"$field" field must be a map.', node.span);
     }
 
-    var map = node as YamlMap;
-    var nonStringNode = map.nodes.keys
+    var nonStringNode = node.nodes.keys
         .firstWhere((e) => e.value is! String, orElse: () => null);
     if (nonStringNode != null) {
       _error('A dependency name must be a string.', nonStringNode.span);
     }
 
-    map.nodes.forEach((nameNode, specNode) {
+    node.nodes.forEach((nameNode, specNode) {
       var name = nameNode.value;
       var spec = specNode.value;
       if (fields['name'] != null && name == this.name) {
         _error('A package may not list itself as a dependency.', nameNode.span);
       }
 
-      YamlNode descriptionNode;
-      String sourceName;
+      YamlNode? descriptionNode;
+      String? sourceName;
 
       VersionConstraint versionConstraint = VersionRange();
       var features = const <String, FeatureDependency>{};
       if (spec == null) {
-        descriptionNode = nameNode;
-        sourceName = _sources.defaultSource.name;
+        sourceName = _sources!.defaultSource.name;
       } else if (spec is String) {
-        descriptionNode = nameNode;
-        sourceName = _sources.defaultSource.name;
+        sourceName = _sources!.defaultSource.name;
         versionConstraint = _parseVersionConstraint(specNode);
       } else if (spec is Map) {
         // Don't write to the immutable YAML map.
@@ -682,7 +466,6 @@
         } else if (sourceNames.isEmpty) {
           // Default to a hosted dependency if no source is specified.
           sourceName = 'hosted';
-          descriptionNode = nameNode;
         }
 
         sourceName ??= sourceNames.single;
@@ -699,13 +482,18 @@
 
       // Let the source validate the description.
       var ref = _wrapFormatException('description', descriptionNode?.span, () {
-        String pubspecPath;
-        if (_location != null && _isFileUri(_location)) {
+        String? pubspecPath;
+        var location = _location;
+        if (location != null && _isFileUri(location)) {
           pubspecPath = path.fromUri(_location);
         }
 
-        return _sources[sourceName].parseRef(name, descriptionNode?.value,
-            containingPath: pubspecPath);
+        return _sources![sourceName]!.parseRef(
+          name,
+          descriptionNode?.value,
+          containingPath: pubspecPath,
+          languageVersion: languageVersion,
+        );
       }, targetPackage: name);
 
       dependencies[name] =
@@ -722,13 +510,13 @@
   /// bound and it is compatible with [defaultUpperBoundConstraint].
   ///
   /// If [ignoreUpperBound] the max constraint is ignored.
-  VersionConstraint _parseVersionConstraint(YamlNode node,
-      {VersionConstraint defaultUpperBoundConstraint,
+  VersionConstraint _parseVersionConstraint(YamlNode? node,
+      {VersionConstraint? defaultUpperBoundConstraint,
       bool ignoreUpperBound = false}) {
     if (node?.value == null) {
       return defaultUpperBoundConstraint ?? VersionConstraint.any;
     }
-    if (node.value is! String) {
+    if (node!.value is! String) {
       _error('A version constraint must be a string.', node.span);
     }
 
@@ -751,13 +539,13 @@
 
   /// Parses [node] to a map from feature names to whether those features are
   /// enabled.
-  Map<String, FeatureDependency> _parseDependencyFeatures(YamlNode node) {
+  Map<String, FeatureDependency> _parseDependencyFeatures(YamlNode? node) {
     if (node?.value == null) return const {};
-    if (node is! YamlMap) _error('Features must be a map.', node.span);
+    if (node is! YamlMap) _error('Features must be a map.', node!.span);
 
-    return mapMap((node as YamlMap).nodes,
-        key: (nameNode, _) => _validateFeatureName(nameNode),
-        value: (_, valueNode) {
+    return mapMap(node.nodes,
+        key: (dynamic nameNode, dynamic _) => _validateFeatureName(nameNode),
+        value: (dynamic _, dynamic valueNode) {
           var value = valueNode.value;
           if (value is bool) {
             return value
@@ -778,7 +566,7 @@
     var name = node.value;
     if (name is! String) {
       _error('A feature name must be a string.', node.span);
-    } else if (!_packageName.hasMatch(name)) {
+    } else if (!packageNameRegExp.hasMatch(name)) {
       _error('A feature name must be a valid Dart identifier.', node.span);
     }
 
@@ -788,8 +576,8 @@
   /// Verifies that [node] is a list of strings and returns it.
   ///
   /// If [validate] is passed, it's called for each string in [node].
-  List<String> _parseStringList(YamlNode node,
-      {void Function(String value, SourceSpan) validate}) {
+  List<String> _parseStringList(YamlNode? node,
+      {void Function(String value, SourceSpan)? validate}) {
     var list = _parseList(node);
     for (var element in list.nodes) {
       var value = element.value;
@@ -803,7 +591,7 @@
   }
 
   /// Verifies that [node] is a list and returns it.
-  YamlList _parseList(YamlNode node) {
+  YamlList _parseList(YamlNode? node) {
     if (node == null || node.value == null) return YamlList();
     if (node is YamlList) return node;
     _error('Must be a list.', node.span);
@@ -819,11 +607,14 @@
   /// If [targetPackage] is provided, the value is used to describe the
   /// dependency that caused the problem.
   T _wrapFormatException<T>(
-      String description, SourceSpan span, T Function() fn,
-      {String targetPackage}) {
+      String description, SourceSpan? span, T Function() fn,
+      {String? targetPackage}) {
     try {
       return fn();
     } on FormatException catch (e) {
+      // If we already have a pub exception with a span, re-use that
+      if (e is PubspecException) rethrow;
+
       var msg = 'Invalid $description';
       if (targetPackage != null) {
         msg = '$msg in the "$name" pubspec on the "$targetPackage" dependency';
@@ -834,20 +625,11 @@
   }
 
   /// Throws a [PubspecException] with the given message.
-  @alwaysThrows
-  void _error(String message, SourceSpan span) {
+  Never _error(String message, SourceSpan? span) {
     throw PubspecException(message, span);
   }
 }
 
-/// An exception thrown when parsing a pubspec.
-///
-/// These exceptions are often thrown lazily while accessing pubspec properties.
-class PubspecException extends SourceSpanFormatException
-    implements ApplicationException {
-  PubspecException(String message, SourceSpan span) : super(message, span);
-}
-
 /// Returns whether [uri] is a file URI.
 ///
 /// This is slightly more complicated than just checking if the scheme is
diff --git a/lib/src/pubspec_parse.dart b/lib/src/pubspec_parse.dart
new file mode 100644
index 0000000..68f9ce7
--- /dev/null
+++ b/lib/src/pubspec_parse.dart
@@ -0,0 +1,262 @@
+// Copyright (c) 2021, 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:meta/meta.dart';
+import 'package:pub_semver/pub_semver.dart';
+import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+import 'exceptions.dart' show ApplicationException;
+import 'utils.dart' show identifierRegExp, reservedWords;
+
+/// A regular expression matching allowed package names.
+///
+/// This allows dot-separated valid Dart identifiers. The dots are there for
+/// compatibility with Google's internal Dart packages, but they may not be used
+/// when publishing a package to pub.dartlang.org.
+final packageNameRegExp =
+    RegExp('^${identifierRegExp.pattern}(\\.${identifierRegExp.pattern})*\$');
+
+/// Helper class for pubspec parsing to:
+/// - extract the fields and methods that are reusable outside of `pub` client, and
+/// - help null-safety migration a bit.
+///
+/// This class should be eventually extracted to a separate library, or re-merged with `Pubspec`.
+abstract class PubspecBase {
+  /// All pubspec fields.
+  ///
+  /// This includes the fields from which other properties are derived.
+  final YamlMap fields;
+
+  PubspecBase(
+    this.fields, {
+    String? name,
+    Version? version,
+  })  : _name = name,
+        _version = version;
+
+  /// The package's name.
+  String get name => _name ??= _lookupName();
+
+  String _lookupName() {
+    final name = fields['name'];
+    if (name == null) {
+      throw PubspecException('Missing the required "name" field.', fields.span);
+    } else if (name is! String) {
+      throw PubspecException(
+          '"name" field must be a string.', fields.nodes['name']?.span);
+    } else if (!packageNameRegExp.hasMatch(name)) {
+      throw PubspecException('"name" field must be a valid Dart identifier.',
+          fields.nodes['name']?.span);
+    } else if (reservedWords.contains(name)) {
+      throw PubspecException('"name" field may not be a Dart reserved word.',
+          fields.nodes['name']?.span);
+    }
+
+    return name;
+  }
+
+  String? _name;
+
+  /// The package's version.
+  Version get version {
+    if (_version != null) return _version!;
+
+    final version = fields['version'];
+    if (version == null) {
+      _version = Version.none;
+      return _version!;
+    }
+
+    final span = fields.nodes['version']?.span;
+    if (version is num) {
+      var fixed = '$version.0';
+      if (version is int) {
+        fixed = '$fixed.0';
+      }
+      _error(
+          '"version" field must have three numeric components: major, '
+          'minor, and patch. Instead of "$version", consider "$fixed".',
+          span);
+    }
+    if (version is! String) {
+      _error('"version" field must be a string.', span);
+    }
+
+    _version = _wrapFormatException(
+        'version number', span, () => Version.parse(version));
+    return _version!;
+  }
+
+  Version? _version;
+
+  /// The URL of the server that the package should default to being published
+  /// to, "none" if the package should not be published, or `null` if it should
+  /// be published to the default server.
+  ///
+  /// If this does return a URL string, it will be a valid parseable URL.
+  String? get publishTo {
+    if (_parsedPublishTo) return _publishTo;
+
+    final publishTo = fields['publish_to'];
+    if (publishTo != null) {
+      final span = fields.nodes['publish_to']?.span;
+
+      if (publishTo is! String) {
+        _error('"publish_to" field must be a string.', span);
+      }
+
+      // It must be "none" or a valid URL.
+      if (publishTo != 'none') {
+        _wrapFormatException('"publish_to" field', span, () {
+          final url = Uri.parse(publishTo);
+          if (url.scheme.isEmpty) {
+            throw FormatException('must be an absolute URL.');
+          }
+        });
+      }
+    }
+
+    _parsedPublishTo = true;
+    _publishTo = publishTo;
+    return _publishTo;
+  }
+
+  bool _parsedPublishTo = false;
+  String? _publishTo;
+
+  /// The list of patterns covering _false-positive secrets_ in the package.
+  ///
+  /// This is a list of git-ignore style patterns for files that should be
+  /// ignored when trying to detect possible leaks of secrets during
+  /// package publication.
+  List<String> get falseSecrets {
+    if (_falseSecrets == null) {
+      final falseSecrets = <String>[];
+
+      // Throws a [PubspecException]
+      void _falseSecretsError(SourceSpan span) => _error(
+            '"false_secrets" field must be a list of git-ignore style patterns',
+            span,
+          );
+
+      final falseSecretsNode = fields.nodes['false_secrets'];
+      if (falseSecretsNode != null) {
+        if (falseSecretsNode is YamlList) {
+          for (final node in falseSecretsNode.nodes) {
+            final value = node.value;
+            if (value is! String) {
+              _falseSecretsError(node.span);
+            }
+            falseSecrets.add(value);
+          }
+        } else {
+          _falseSecretsError(falseSecretsNode.span);
+        }
+      }
+
+      _falseSecrets = List.unmodifiable(falseSecrets);
+    }
+    return _falseSecrets!;
+  }
+
+  List<String>? _falseSecrets;
+
+  /// The executables that should be placed on the user's PATH when this
+  /// package is globally activated.
+  ///
+  /// It is a map of strings to string. Each key is the name of the command
+  /// that will be placed on the user's PATH. The value is the name of the
+  /// .dart script (without extension) in the package's `bin` directory that
+  /// should be run for that command. Both key and value must be "simple"
+  /// strings: alphanumerics, underscores and hypens only. If a value is
+  /// omitted, it is inferred to use the same name as the key.
+  Map<String, String> get executables {
+    if (_executables != null) return _executables!;
+
+    _executables = {};
+    var yaml = fields['executables'];
+    if (yaml == null) return _executables!;
+
+    if (yaml is! Map) {
+      _error('"executables" field must be a map.',
+          fields.nodes['executables']?.span);
+    }
+
+    yaml.nodes.forEach((key, value) {
+      if (key.value is! String) {
+        _error('"executables" keys must be strings.', key.span);
+      }
+
+      final keyPattern = RegExp(r'^[a-zA-Z0-9_-]+$');
+      if (!keyPattern.hasMatch(key.value)) {
+        _error(
+            '"executables" keys may only contain letters, '
+            'numbers, hyphens and underscores.',
+            key.span);
+      }
+
+      if (value.value == null) {
+        value = key;
+      } else if (value.value is! String) {
+        _error('"executables" values must be strings or null.', value.span);
+      }
+
+      final valuePattern = RegExp(r'[/\\]');
+      if (valuePattern.hasMatch(value.value)) {
+        _error('"executables" values may not contain path separators.',
+            value.span);
+      }
+
+      _executables![key.value] = value.value;
+    });
+
+    return _executables!;
+  }
+
+  Map<String, String>? _executables;
+
+  /// Whether the package is private and cannot be published.
+  ///
+  /// This is specified in the pubspec by setting "publish_to" to "none".
+  bool get isPrivate => publishTo == 'none';
+
+  /// Runs [fn] and wraps any [FormatException] it throws in a
+  /// [PubspecException].
+  ///
+  /// [description] should be a noun phrase that describes whatever's being
+  /// parsed or processed by [fn]. [span] should be the location of whatever's
+  /// being processed within the pubspec.
+  ///
+  /// If [targetPackage] is provided, the value is used to describe the
+  /// dependency that caused the problem.
+  T _wrapFormatException<T>(
+      String description, SourceSpan? span, T Function() fn,
+      {String? targetPackage}) {
+    try {
+      return fn();
+    } on FormatException catch (e) {
+      var msg = 'Invalid $description';
+      if (targetPackage != null) {
+        msg = '$msg in the "$name" pubspec on the "$targetPackage" dependency';
+      }
+      msg = '$msg: ${e.message}';
+      throw PubspecException(msg, span);
+    }
+  }
+
+  /// Throws a [PubspecException] with the given message.
+  @alwaysThrows
+  void _error(String message, SourceSpan? span) {
+    throw PubspecException(message, span);
+  }
+}
+
+/// An exception thrown when parsing a pubspec.
+///
+/// These exceptions are often thrown lazily while accessing pubspec properties.
+class PubspecException extends SourceSpanFormatException
+    implements ApplicationException {
+  PubspecException(String message, SourceSpan? span) : super(message, span);
+}
diff --git a/lib/src/pubspec_utils.dart b/lib/src/pubspec_utils.dart
index d58fb62..ee904a7 100644
--- a/lib/src/pubspec_utils.dart
+++ b/lib/src/pubspec_utils.dart
@@ -2,7 +2,7 @@
 // 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.
 
-// @dart=2.10
+import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
 
@@ -42,7 +42,7 @@
 Future<Pubspec> constrainedToAtLeastNullSafetyPubspec(
     Pubspec original, SystemCache cache) async {
   /// Get the first version of [package] opting in to null-safety.
-  Future<VersionRange> constrainToFirstWithNullSafety(
+  Future<VersionConstraint> constrainToFirstWithNullSafety(
       PackageRange packageRange) async {
     final ref = packageRange.toRef();
     final available = await cache.source(ref.source).getVersions(ref);
@@ -65,7 +65,7 @@
     Map<String, PackageRange> constrained,
   ) async {
     final result = await Future.wait(constrained.keys.map((name) async {
-      final packageRange = constrained[name];
+      final packageRange = constrained[name]!;
       var unconstrainedRange = packageRange;
 
       /// We only need to remove the upper bound if it is a hosted package.
@@ -106,7 +106,7 @@
 /// not specified or empty, then all packages will have their upper bounds
 /// removed.
 Pubspec stripVersionUpperBounds(Pubspec original,
-    {Iterable<String> stripOnly}) {
+    {Iterable<String>? stripOnly}) {
   ArgumentError.checkNotNull(original, 'original');
   stripOnly ??= [];
 
@@ -116,12 +116,12 @@
     final result = <PackageRange>[];
 
     for (final name in constrained.keys) {
-      final packageRange = constrained[name];
+      final packageRange = constrained[name]!;
       var unconstrainedRange = packageRange;
 
       /// We only need to remove the upper bound if it is a hosted package.
       if (packageRange.source is HostedSource &&
-          (stripOnly.isEmpty || stripOnly.contains(packageRange.name))) {
+          (stripOnly!.isEmpty || stripOnly.contains(packageRange.name))) {
         unconstrainedRange = PackageRange(
             packageRange.name,
             packageRange.source,
diff --git a/lib/src/rate_limited_scheduler.dart b/lib/src/rate_limited_scheduler.dart
index 6e62310..85d73cd 100644
--- a/lib/src/rate_limited_scheduler.dart
+++ b/lib/src/rate_limited_scheduler.dart
@@ -2,13 +2,9 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:collection';
 
-import 'package:meta/meta.dart';
-import 'package:pedantic/pedantic.dart';
 import 'package:pool/pool.dart';
 
 /// Handles rate-limited scheduling of tasks.
@@ -65,7 +61,7 @@
   final Set<J> _started = {};
 
   RateLimitedScheduler(Future<V> Function(J) runJob,
-      {@required int maxConcurrentOperations})
+      {required int maxConcurrentOperations})
       : _runJob = runJob,
         _pool = Pool(maxConcurrentOperations);
 
@@ -77,7 +73,7 @@
       return;
     }
     final task = _queue.removeFirst();
-    final completer = _cache[task.jobId];
+    final completer = _cache[task.jobId]!;
 
     if (!_started.add(task.jobId)) {
       return;
@@ -95,7 +91,9 @@
     // become uncaught.
     //
     // They will still show up for other listeners of the future.
-    await completer.future.catchError((_) {});
+    try {
+      await completer.future;
+    } catch (_) {}
   }
 
   /// Calls [callback] with a function that can pre-schedule jobs.
@@ -140,7 +138,7 @@
 
   /// Returns the result of running [jobId] if that is already done.
   /// Otherwise returns `null`.
-  V peek(J jobId) => _results[jobId];
+  V? peek(J jobId) => _results[jobId];
 }
 
 class _Task<J> {
diff --git a/lib/src/solver.dart b/lib/src/solver.dart
index 18d3c23..74dbfa8 100644
--- a/lib/src/solver.dart
+++ b/lib/src/solver.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'lock_file.dart';
@@ -33,8 +31,8 @@
   SolveType type,
   SystemCache cache,
   Package root, {
-  LockFile lockFile,
-  Iterable<String> unlock,
+  LockFile? lockFile,
+  Iterable<String> unlock = const [],
 }) {
   lockFile ??= LockFile.empty();
   return VersionSolver(
@@ -42,7 +40,7 @@
     cache,
     root,
     lockFile,
-    unlock ?? [],
+    unlock,
   ).solve();
 }
 
@@ -60,12 +58,12 @@
 /// If [unlock] is empty [SolveType.get] interprets this as lock everything,
 /// while [SolveType.upgrade] and [SolveType.downgrade] interprets an empty
 /// [unlock] as unlock everything.
-Future<SolveResult> tryResolveVersions(
+Future<SolveResult?> tryResolveVersions(
   SolveType type,
   SystemCache cache,
   Package root, {
-  LockFile lockFile,
-  Iterable<String> unlock,
+  LockFile? lockFile,
+  Iterable<String>? unlock,
 }) async {
   try {
     return await resolveVersions(
@@ -73,7 +71,7 @@
       cache,
       root,
       lockFile: lockFile,
-      unlock: unlock,
+      unlock: unlock ?? [],
     );
   } on SolveFailure {
     return null;
diff --git a/lib/src/solver/assignment.dart b/lib/src/solver/assignment.dart
index 645eafb..d4c7bdf 100644
--- a/lib/src/solver/assignment.dart
+++ b/lib/src/solver/assignment.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../package_name.dart';
 import 'incompatibility.dart';
 import 'term.dart';
@@ -19,7 +17,7 @@
 
   /// The incompatibility that caused this assignment to be derived, or `null`
   /// if the assignment isn't a derivation.
-  final Incompatibility cause;
+  final Incompatibility? cause;
 
   /// Whether this assignment is a decision, as opposed to a derivation.
   bool get isDecision => cause == null;
diff --git a/lib/src/solver/failure.dart b/lib/src/solver/failure.dart
index 1a3897b..60f70c5 100644
--- a/lib/src/solver/failure.dart
+++ b/lib/src/solver/failure.dart
@@ -2,14 +2,11 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 
 import '../exceptions.dart';
 import '../log.dart' as log;
 import '../package_name.dart';
-import '../sdk.dart';
 import '../utils.dart';
 import 'incompatibility.dart';
 import 'incompatibility_cause.dart';
@@ -30,7 +27,7 @@
   ///
   /// If multiple [PackageNotFoundException]s caused the error, it's undefined
   /// which one is returned.
-  PackageNotFoundException get packageNotFound {
+  PackageNotFoundException? get packageNotFound {
     for (var incompatibility in incompatibility.externalIncompatibilities) {
       var cause = incompatibility.cause;
       if (cause is PackageNotFoundCause) return cause.exception;
@@ -70,7 +67,7 @@
   /// incompatibility, and why its terms are incompatible. The number is
   /// optional and indicates the explicit number that should be associated with
   /// the line so it can be referred to later on.
-  final _lines = <Pair<String, int>>[];
+  final _lines = <Pair<String, int?>>[];
 
   // A map from incompatibilities to the line numbers that were written for
   // those incompatibilities.
@@ -82,51 +79,33 @@
 
   /// Populates [_derivations] for [incompatibility] and its transitive causes.
   void _countDerivations(Incompatibility incompatibility) {
-    if (_derivations.containsKey(incompatibility)) {
-      _derivations[incompatibility]++;
-    } else {
-      _derivations[incompatibility] = 1;
+    _derivations.update(incompatibility, (value) => value + 1, ifAbsent: () {
       var cause = incompatibility.cause;
       if (cause is ConflictCause) {
         _countDerivations(cause.conflict);
         _countDerivations(cause.other);
       }
-    }
+      return 1;
+    });
   }
 
   String write() {
     var buffer = StringBuffer();
 
-    // SDKs whose version constraints weren't matched.
-    var sdkConstraintCauses = <Sdk>{};
-
-    // SDKs implicated in any way in the solve failure.
-    var sdkCauses = <Sdk>{};
-
-    for (var incompatibility in _root.externalIncompatibilities) {
-      var cause = incompatibility.cause;
-      if (cause is PackageNotFoundCause && cause.sdk != null) {
-        sdkCauses.add(cause.sdk);
-      } else if (cause is SdkCause) {
-        sdkCauses.add(cause.sdk);
-        sdkConstraintCauses.add(cause.sdk);
-      }
+    // Find all notices from incompatibility causes. This allows an
+    // [IncompatibilityCause] to provide a notice that is printed before the
+    // explanation of the conflict.
+    // Notably, this is used for stating which SDK version is currently
+    // installed, if an SDK is incompatible with a dependency.
+    final notices = _root.externalIncompatibilities
+        .map((c) => c.cause.notice)
+        .whereNotNull()
+        .toSet() // Avoid duplicates
+        .sortedBy((n) => n); // sort for consistency
+    for (final n in notices) {
+      buffer.writeln(n);
     }
-
-    // If the failure was caused in part by unsatisfied SDK constraints,
-    // indicate the actual versions so we don't have to list them (possibly
-    // multiple times) in the main body of the error message.
-    //
-    // Iterate through [sdks] to ensure that SDKs versions are printed in a
-    // consistent order
-    var wroteLine = false;
-    for (var sdk in sdks.values) {
-      if (!sdkConstraintCauses.contains(sdk)) continue;
-      if (!sdk.isAvailable) continue;
-      wroteLine = true;
-      buffer.writeln('The current ${sdk.name} SDK version is ${sdk.version}.');
-    }
-    if (wroteLine) buffer.writeln();
+    if (notices.isNotEmpty) buffer.writeln();
 
     if (_root.cause is ConflictCause) {
       _visit(_root, const {});
@@ -160,15 +139,21 @@
       buffer.writeln(wordWrap(message, prefix: ' ' * (padding + 2)));
     }
 
-    // Iterate through [sdks] to ensure that SDKs versions are printed in a
-    // consistent order
-    for (var sdk in sdks.values) {
-      if (!sdkCauses.contains(sdk)) continue;
-      if (sdk.isAvailable) continue;
-      if (sdk.installMessage == null) continue;
+    // Iterate through all hints, these are intended to be actionable, such as:
+    //  * How to install an SDK, and,
+    //  * How to provide authentication.
+    // Hence, it makes sense to show these at the end of the explanation, as the
+    // user will ideally see these before reading the actual conflict and
+    // understand how to fix the issue.
+    _root.externalIncompatibilities
+        .map((c) => c.cause.hint)
+        .whereNotNull()
+        .toSet() // avoid duplicates
+        .sortedBy((hint) => hint) // sort hints for consistent ordering.
+        .forEach((hint) {
       buffer.writeln();
-      buffer.writeln(sdk.installMessage);
-    }
+      buffer.writeln(hint);
+    });
 
     return buffer.toString();
   }
@@ -203,23 +188,24 @@
       {bool conclusion = false}) {
     // Add explicit numbers for incompatibilities that are written far away
     // from their successors or that are used for multiple derivations.
-    var numbered = conclusion || _derivations[incompatibility] > 1;
+    var numbered = conclusion || _derivations[incompatibility]! > 1;
     var conjunction = conclusion || incompatibility == _root ? 'So,' : 'And';
     var incompatibilityString =
         log.bold(incompatibility.toString(detailsForIncompatibility));
 
-    var cause = incompatibility.cause as ConflictCause;
-    var detailsForCause = _detailsForCause(cause);
-    if (cause.conflict.cause is ConflictCause &&
-        cause.other.cause is ConflictCause) {
-      var conflictLine = _lineNumbers[cause.conflict];
-      var otherLine = _lineNumbers[cause.other];
+    var conflictClause = incompatibility.cause as ConflictCause;
+    var detailsForCause = _detailsForCause(conflictClause);
+    var cause = conflictClause.conflict.cause;
+    var otherCause = conflictClause.other.cause;
+    if (cause is ConflictCause && otherCause is ConflictCause) {
+      var conflictLine = _lineNumbers[conflictClause.conflict];
+      var otherLine = _lineNumbers[conflictClause.other];
       if (conflictLine != null && otherLine != null) {
         _write(
             incompatibility,
             'Because ' +
-                cause.conflict.andToString(
-                    cause.other, detailsForCause, conflictLine, otherLine) +
+                conflictClause.conflict.andToString(conflictClause.other,
+                    detailsForCause, conflictLine, otherLine) +
                 ', $incompatibilityString.',
             numbered: numbered);
       } else if (conflictLine != null || otherLine != null) {
@@ -227,13 +213,13 @@
         Incompatibility withoutLine;
         int line;
         if (conflictLine != null) {
-          withLine = cause.conflict;
-          withoutLine = cause.other;
+          withLine = conflictClause.conflict;
+          withoutLine = conflictClause.other;
           line = conflictLine;
         } else {
-          withLine = cause.other;
-          withoutLine = cause.conflict;
-          line = otherLine;
+          withLine = conflictClause.other;
+          withoutLine = conflictClause.conflict;
+          line = otherLine!;
         }
 
         _visit(withoutLine, detailsForCause);
@@ -243,35 +229,38 @@
             '($line), $incompatibilityString.',
             numbered: numbered);
       } else {
-        var singleLineConflict = _isSingleLine(cause.conflict.cause);
-        var singleLineOther = _isSingleLine(cause.other.cause);
+        var singleLineConflict = _isSingleLine(cause);
+        var singleLineOther = _isSingleLine(otherCause);
         if (singleLineOther || singleLineConflict) {
-          var first = singleLineOther ? cause.conflict : cause.other;
-          var second = singleLineOther ? cause.other : cause.conflict;
+          var first =
+              singleLineOther ? conflictClause.conflict : conflictClause.other;
+          var second =
+              singleLineOther ? conflictClause.other : conflictClause.conflict;
           _visit(first, detailsForCause);
           _visit(second, detailsForCause);
           _write(incompatibility, 'Thus, $incompatibilityString.',
               numbered: numbered);
         } else {
-          _visit(cause.conflict, {}, conclusion: true);
+          _visit(conflictClause.conflict, {}, conclusion: true);
           _lines.add(Pair('', null));
 
-          _visit(cause.other, detailsForCause);
+          _visit(conflictClause.other, detailsForCause);
           _write(
               incompatibility,
               '$conjunction because '
-              '${cause.conflict.toString(detailsForCause)} '
-              '(${_lineNumbers[cause.conflict]}), '
+              '${conflictClause.conflict.toString(detailsForCause)} '
+              '(${_lineNumbers[conflictClause.conflict]}), '
               '$incompatibilityString.',
               numbered: numbered);
         }
       }
-    } else if (cause.conflict.cause is ConflictCause ||
-        cause.other.cause is ConflictCause) {
-      var derived =
-          cause.conflict.cause is ConflictCause ? cause.conflict : cause.other;
-      var ext =
-          cause.conflict.cause is ConflictCause ? cause.other : cause.conflict;
+    } else if (cause is ConflictCause || otherCause is ConflictCause) {
+      var derived = cause is ConflictCause
+          ? conflictClause.conflict
+          : conflictClause.other;
+      var ext = cause is ConflictCause
+          ? conflictClause.other
+          : conflictClause.conflict;
 
       var derivedLine = _lineNumbers[derived];
       if (derivedLine != null) {
@@ -313,7 +302,7 @@
       _write(
           incompatibility,
           'Because '
-          '${cause.conflict.andToString(cause.other, detailsForCause)}, '
+          '${conflictClause.conflict.andToString(conflictClause.other, detailsForCause)}, '
           '$incompatibilityString.',
           numbered: numbered);
     }
@@ -347,7 +336,7 @@
   bool _isCollapsible(Incompatibility incompatibility) {
     // If [incompatibility] is used for multiple derivations, it will need a
     // line number and so will need to be written explicitly.
-    if (_derivations[incompatibility] > 1) return false;
+    if (_derivations[incompatibility]! > 1) return false;
 
     var cause = incompatibility.cause as ConflictCause;
     // If [incompatibility] is derived from two derived incompatibilities,
diff --git a/lib/src/solver/incompatibility.dart b/lib/src/solver/incompatibility.dart
index 1dd6d96..9e39865 100644
--- a/lib/src/solver/incompatibility.dart
+++ b/lib/src/solver/incompatibility.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub_semver/pub_semver.dart';
 
 import '../package_name.dart';
@@ -67,14 +65,12 @@
       var byRef = byName.putIfAbsent(term.package.name, () => {});
       var ref = term.package.toRef();
       if (byRef.containsKey(ref)) {
-        byRef[ref] = byRef[ref].intersect(term);
-
         // If we have two terms that refer to the same package but have a null
         // intersection, they're mutually exclusive, making this incompatibility
         // irrelevant, since we already know that mutually exclusive version
         // ranges are incompatible. We should never derive an irrelevant
         // incompatibility.
-        assert(byRef[ref] != null);
+        byRef[ref] = byRef[ref]!.intersect(term)!;
       } else {
         byRef[ref] = term;
       }
@@ -100,7 +96,7 @@
   /// If [details] is passed, it controls the amount of detail that's written
   /// for packages with the given names.
   @override
-  String toString([Map<String, PackageDetail> details]) {
+  String toString([Map<String, PackageDetail>? details]) {
     if (cause == IncompatibilityCause.dependency) {
       assert(terms.length == 2);
 
@@ -223,7 +219,7 @@
   /// If [thisLine] and/or [otherLine] are passed, they indicate line numbers
   /// that should be associated with [this] and [other], respectively.
   String andToString(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     var requiresBoth = _tryRequiresBoth(other, details, thisLine, otherLine);
     if (requiresBoth != null) return requiresBoth;
 
@@ -246,8 +242,8 @@
   /// and Y", this returns that expression.
   ///
   /// Otherwise, this returns `null`.
-  String _tryRequiresBoth(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+  String? _tryRequiresBoth(Incompatibility other,
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     if (terms.length == 1 || other.terms.length == 1) return null;
 
     var thisPositive = _singleTermWhere((term) => term.isPositive);
@@ -281,8 +277,8 @@
   /// Z", this returns that expression.
   ///
   /// Otherwise, this returns `null`.
-  String _tryRequiresThrough(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+  String? _tryRequiresThrough(Incompatibility other,
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     if (terms.length == 1 || other.terms.length == 1) return null;
 
     var thisNegative = _singleTermWhere((term) => !term.isPositive);
@@ -294,9 +290,9 @@
 
     Incompatibility prior;
     Term priorNegative;
-    int priorLine;
+    int? priorLine;
     Incompatibility latter;
-    int latterLine;
+    int? latterLine;
     if (thisNegative != null &&
         otherPositive != null &&
         thisNegative.package.name == otherPositive.package.name &&
@@ -358,14 +354,14 @@
   /// forbidden", this returns that expression.
   ///
   /// Otherwise, this returns `null`.
-  String _tryRequiresForbidden(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+  String? _tryRequiresForbidden(Incompatibility other,
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     if (terms.length != 1 && other.terms.length != 1) return null;
 
     Incompatibility prior;
     Incompatibility latter;
-    int priorLine;
-    int latterLine;
+    int? priorLine;
+    int? latterLine;
     if (terms.length == 1) {
       prior = other;
       latter = this;
@@ -439,8 +435,8 @@
   /// term.
   ///
   /// Otherwise, returns `null`.
-  Term _singleTermWhere(bool Function(Term) filter) {
-    Term found;
+  Term? _singleTermWhere(bool Function(Term) filter) {
+    Term? found;
     for (var term in terms) {
       if (!filter(term)) continue;
       if (found != null) return null;
@@ -450,7 +446,7 @@
   }
 
   /// Returns a terse representation of [term]'s package ref.
-  String _terseRef(Term term, Map<String, PackageDetail> details) =>
+  String _terseRef(Term term, Map<String, PackageDetail>? details) =>
       term.package
           .toRef()
           .toString(details == null ? null : details[term.package.name]);
@@ -459,12 +455,12 @@
   ///
   /// If [allowEvery] is `true`, this will return "every version of foo" instead
   /// of "foo any".
-  String _terse(Term term, Map<String, PackageDetail> details,
+  String _terse(Term? term, Map<String, PackageDetail>? details,
       {bool allowEvery = false}) {
-    if (allowEvery && term.constraint.isAny) {
+    if (allowEvery && term!.constraint.isAny) {
       return 'every version of ${_terseRef(term, details)}';
     } else {
-      return term.package
+      return term!.package
           .toString(details == null ? null : details[term.package.name]);
     }
   }
diff --git a/lib/src/solver/incompatibility_cause.dart b/lib/src/solver/incompatibility_cause.dart
index 45aa5ca..a2a2327 100644
--- a/lib/src/solver/incompatibility_cause.dart
+++ b/lib/src/solver/incompatibility_cause.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub_semver/pub_semver.dart';
 
 import '../exceptions.dart';
@@ -12,6 +10,8 @@
 
 /// The reason an [Incompatibility]'s terms are incompatible.
 abstract class IncompatibilityCause {
+  const IncompatibilityCause._();
+
   /// The incompatibility represents the requirement that the root package
   /// exists.
   static const IncompatibilityCause root = _Cause('root');
@@ -29,11 +29,26 @@
 
   /// The incompatibility indicates that the package has an unknown source.
   static const IncompatibilityCause unknownSource = _Cause('unknown source');
+
+  /// Human readable notice / information providing context for this
+  /// incompatibility.
+  ///
+  /// This may be multiple lines, and will be printed before the explanation.
+  /// This is used highlight information that is useful for understanding the
+  /// why this conflict happened.
+  String? get notice => null;
+
+  /// Human readable hint indicating how this incompatibility may be resolved.
+  ///
+  /// This may be multiple lines, and will be printed after the explanation.
+  /// This should only be included if it is actionable and likely to resolve the
+  /// issue for the user.
+  String? get hint => null;
 }
 
 /// The incompatibility was derived from two existing incompatibilities during
 /// conflict resolution.
-class ConflictCause implements IncompatibilityCause {
+class ConflictCause extends IncompatibilityCause {
   /// The incompatibility that was originally found to be in conflict, from
   /// which the target incompatibility was derived.
   final Incompatibility conflict;
@@ -42,14 +57,14 @@
   /// from which the target incompatibility was derived.
   final Incompatibility other;
 
-  ConflictCause(this.conflict, this.other);
+  ConflictCause(this.conflict, this.other) : super._();
 }
 
 /// A class for stateless [IncompatibilityCause]s.
-class _Cause implements IncompatibilityCause {
+class _Cause extends IncompatibilityCause {
   final String _name;
 
-  const _Cause(this._name);
+  const _Cause(this._name) : super._();
 
   @override
   String toString() => _name;
@@ -57,27 +72,49 @@
 
 /// The incompatibility represents a package's SDK constraint being
 /// incompatible with the current SDK.
-class SdkCause implements IncompatibilityCause {
+class SdkCause extends IncompatibilityCause {
   /// The union of all the incompatible versions' constraints on the SDK.
-  final VersionConstraint constraint;
+  // TODO(zarah): Investigate if this can be non-nullable
+  final VersionConstraint? constraint;
 
   /// The SDK with which the package was incompatible.
   final Sdk sdk;
 
-  SdkCause(this.constraint, this.sdk);
+  @override
+  String? get notice {
+    // If the SDK is not available, then we have an actionable [hint] printed
+    // after the explanation. So we don't need to state that the SDK is not
+    // available.
+    if (!sdk.isAvailable) {
+      return null;
+    }
+    // If the SDK is available and we have an incompatibility, then the user has
+    // the wrong SDK version (one that is not compatible with any solution).
+    // Thus, it makes sense to highlight the current SDK version.
+    return 'The current ${sdk.name} SDK version is ${sdk.version}.';
+  }
+
+  @override
+  String? get hint {
+    // If the SDK is available, then installing it won't help
+    if (sdk.isAvailable) {
+      return null;
+    }
+    // Return an install message for the SDK, if there is an install message.
+    return sdk.installMessage;
+  }
+
+  SdkCause(this.constraint, this.sdk) : super._();
 }
 
 /// The incompatibility represents a package that couldn't be found by its
 /// source.
-class PackageNotFoundCause implements IncompatibilityCause {
+class PackageNotFoundCause extends IncompatibilityCause {
   /// The exception indicating why the package couldn't be found.
   final PackageNotFoundException exception;
 
-  /// If the incompatibility was caused by an SDK being unavailable, this is
-  /// that SDK.
-  ///
-  /// Otherwise `null`.
-  Sdk get sdk => exception.missingSdk;
+  PackageNotFoundCause(this.exception) : super._();
 
-  PackageNotFoundCause(this.exception);
+  @override
+  String? get hint => exception.hint;
 }
diff --git a/lib/src/solver/package_lister.dart b/lib/src/solver/package_lister.dart
index 136b0db..8e1abed 100644
--- a/lib/src/solver/package_lister.dart
+++ b/lib/src/solver/package_lister.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:async/async.dart';
@@ -34,7 +32,7 @@
   ///
   /// This is `null` if this package isn't locked or if the current version
   /// solve isn't a `pub get`.
-  final PackageId _locked;
+  final PackageId? _locked;
 
   // The version of this package that, if retracted, is still allowed in the
   // current version solve.
@@ -43,7 +41,7 @@
   // present in `pubspec.lock` or pinned in `dependency_overrides`.
   //
   // This is `null` if there is no retracted version that can be allowed.
-  final Version _allowedRetractedVersion;
+  final Version? _allowedRetractedVersion;
 
   /// The source from which [_ref] comes.
   final BoundSource _source;
@@ -78,25 +76,26 @@
 
   /// The versions of [_ref] that have been downloaded and cached, or `null` if
   /// they haven't been downloaded yet.
-  List<PackageId> get cachedVersions => _cachedVersions;
-  List<PackageId> _cachedVersions;
+  List<PackageId>? get cachedVersions => _cachedVersions;
+  List<PackageId>? _cachedVersions;
 
   /// All versions of the package, sorted by [Version.compareTo].
   Future<List<PackageId>> get _versions => _versionsMemo.runOnce(() async {
-        _cachedVersions = await withDependencyType(
+        var cachedVersions = (await withDependencyType(
             _dependencyType,
             () => _source.getVersions(_ref,
-                allowedRetractedVersion: _allowedRetractedVersion));
-        _cachedVersions.sort((id1, id2) => id1.version.compareTo(id2.version));
-        return _cachedVersions;
+                allowedRetractedVersion: _allowedRetractedVersion)))
+          ..sort((id1, id2) => id1.version.compareTo(id2.version));
+        _cachedVersions = cachedVersions;
+        return cachedVersions;
       });
   final _versionsMemo = AsyncMemoizer<List<PackageId>>();
 
   /// The most recent version of this package (or the oldest, if we're
   /// downgrading).
-  Future<PackageId> get latest =>
+  Future<PackageId?> get latest =>
       _latestMemo.runOnce(() => bestVersion(VersionConstraint.any));
-  final _latestMemo = AsyncMemoizer<PackageId>();
+  final _latestMemo = AsyncMemoizer<PackageId?>();
 
   /// Creates a package lister for the dependency identified by [ref].
   PackageLister(
@@ -125,7 +124,7 @@
 
   /// Returns the number of versions of this package that match [constraint].
   Future<int> countVersions(VersionConstraint constraint) async {
-    if (_locked != null && constraint.allows(_locked.version)) return 1;
+    if (_locked != null && constraint.allows(_locked!.version)) return 1;
     try {
       return (await _versions)
           .where((id) => constraint.allows(id.version))
@@ -144,32 +143,32 @@
   ///
   /// Throws a [PackageNotFoundException] if this lister's package doesn't
   /// exist.
-  Future<PackageId> bestVersion(VersionConstraint constraint) async {
-    if (_locked != null && constraint.allows(_locked.version)) return _locked;
+  Future<PackageId?> bestVersion(VersionConstraint? constraint) async {
+    if (_locked != null && constraint!.allows(_locked!.version)) return _locked;
 
     var versions = await _versions;
 
     // If [constraint] has a minimum (or a maximum in downgrade mode), we can
     // bail early once we're past it.
-    var isPastLimit = (Version _) => false;
+    var isPastLimit = (Version? _) => false;
     if (constraint is VersionRange) {
       if (_isDowngrade) {
         var max = constraint.max;
-        if (max != null) isPastLimit = (version) => version > max;
+        if (max != null) isPastLimit = (version) => version! > max;
       } else {
         var min = constraint.min;
-        if (min != null) isPastLimit = (version) => version < min;
+        if (min != null) isPastLimit = (version) => version! < min;
       }
     }
 
     // Return the most preferable version that matches [constraint]: the latest
     // non-prerelease version if one exists, or the latest prerelease version
     // otherwise.
-    PackageId bestPrerelease;
+    PackageId? bestPrerelease;
     for (var id in _isDowngrade ? versions : versions.reversed) {
-      if (isPastLimit != null && isPastLimit(id.version)) break;
+      if (isPastLimit(id.version)) break;
 
-      if (!constraint.allows(id.version)) continue;
+      if (!constraint!.allows(id.version)) continue;
       if (!id.version.isPreRelease) return id;
       bestPrerelease ??= id;
     }
@@ -210,7 +209,7 @@
 
     if (_cachedVersions == null &&
         _locked != null &&
-        id.version == _locked.version) {
+        id.version == _locked!.version) {
       if (_listedLockedVersion) return const [];
 
       var depender = id.toRange();
@@ -252,7 +251,8 @@
 
     var versions = await _versions;
     var index = lowerBound(versions, id,
-        compare: (id1, id2) => id1.version.compareTo(id2.version));
+        compare: (dynamic id1, dynamic id2) =>
+            id1.version.compareTo(id2.version));
     assert(index < versions.length);
     assert(versions[index].version == id.version);
 
@@ -289,7 +289,7 @@
           _alreadyListedDependencies[package] ?? VersionConstraint.empty);
 
       return _dependency(
-          _ref.withConstraint(constraint), dependencies[package]);
+          _ref.withConstraint(constraint), dependencies[package]!);
     }).toList();
   }
 
@@ -303,7 +303,7 @@
   /// version of [sdk], returns an [Incompatibility] indicating that.
   ///
   /// Otherwise, returns `null`.
-  Future<Incompatibility> _checkSdkConstraint(int index, Sdk sdk) async {
+  Future<Incompatibility?> _checkSdkConstraint(int index, Sdk sdk) async {
     var versions = await _versions;
 
     bool allowsSdk(Pubspec pubspec) => _matchesSdkConstraint(pubspec, sdk);
@@ -322,7 +322,7 @@
 
     var sdkConstraint = await foldAsync(
         slice(versions, bounds.first, bounds.last + 1), VersionConstraint.empty,
-        (previous, version) async {
+        (dynamic previous, dynamic version) async {
       var pubspec = await _describeSafe(version);
       return previous.union(
           pubspec.sdkConstraints[sdk.identifier] ?? VersionConstraint.any);
@@ -364,7 +364,7 @@
   /// If a package is absent from the return value, that indicates indicate that
   /// all versions above or below [index] (according to [upper]) have the same
   /// dependency.
-  Future<Map<String, Version>> _dependencyBounds(
+  Future<Map<String, Version?>> _dependencyBounds(
       Map<String, PackageRange> dependencies, int index,
       {bool upper = true}) async {
     var versions = await _versions;
@@ -427,7 +427,7 @@
     var constraint = pubspec.sdkConstraints[sdk.identifier];
     if (constraint == null) return true;
 
-    return sdk.isAvailable && constraint.allows(sdk.version);
+    return sdk.isAvailable && constraint.allows(sdk.version!);
   }
 }
 
@@ -447,7 +447,7 @@
 
   @override
   Future<List<PackageId>> getVersions(PackageRef ref,
-      {Duration maxAge, Version allowedRetractedVersion}) {
+      {Duration? maxAge, Version? allowedRetractedVersion}) {
     assert(ref.isRoot);
     return Future.value([PackageId.root(_package)]);
   }
@@ -463,11 +463,11 @@
   @override
   SystemCache get systemCache => throw _unsupported;
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) =>
+  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration? maxAge) =>
       throw _unsupported;
   @override
   Future<Pubspec> doDescribe(PackageId id) => throw _unsupported;
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) =>
+  String getDirectory(PackageId id, {String? relativeFrom}) =>
       throw _unsupported;
 }
diff --git a/lib/src/solver/partial_solution.dart b/lib/src/solver/partial_solution.dart
index 57c22bf..40d2611 100644
--- a/lib/src/solver/partial_solution.dart
+++ b/lib/src/solver/partial_solution.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../package_name.dart';
 import 'assignment.dart';
 import 'incompatibility.dart';
@@ -27,7 +25,7 @@
   /// negative [Assignment]s that refer to that package.
   ///
   /// This is derived from [_assignments].
-  final _positive = <String, Term>{};
+  final _positive = <String, Term?>{};
 
   /// The union of all negative [Assignment]s for each package.
   ///
@@ -43,8 +41,8 @@
   /// Returns all [PackageRange]s that have been assigned but are not yet
   /// satisfied.
   Iterable<PackageRange> get unsatisfied => _positive.values
-      .where((term) => !_decisions.containsKey(term.package.name))
-      .map((term) => term.package);
+      .where((term) => !_decisions.containsKey(term!.package.name))
+      .map((term) => term!.package);
 
   // The current decision level—that is, the length of [decisions].
   int get decisionLevel => _decisions.length;
@@ -70,7 +68,7 @@
   }
 
   /// Adds an assignment of [package] as a derivation.
-  void derive(PackageName package, bool isPositive, Incompatibility cause) {
+  void derive(PackageRange package, bool isPositive, Incompatibility cause) {
     _assign(Assignment.derivation(
         package, isPositive, cause, decisionLevel, _assignments.length));
   }
@@ -119,7 +117,7 @@
     var negativeByRef = _negative[name];
     var oldNegative = negativeByRef == null ? null : negativeByRef[ref];
     var term =
-        oldNegative == null ? assignment : assignment.intersect(oldNegative);
+        oldNegative == null ? assignment : assignment.intersect(oldNegative)!;
 
     if (term.isPositive) {
       _negative.remove(name);
@@ -134,7 +132,7 @@
   ///
   /// Throws a [StateError] if [term] isn't satisfied by [this].
   Assignment satisfier(Term term) {
-    Term assignedTerm;
+    Term? assignedTerm;
     for (var assignment in _assignments) {
       if (assignment.package.name != term.package.name) continue;
 
@@ -153,7 +151,7 @@
           : assignedTerm.intersect(assignment);
 
       // As soon as we have enough assignments to satisfy [term], return them.
-      if (assignedTerm.satisfies(term)) return assignment;
+      if (assignedTerm!.satisfies(term)) return assignment;
     }
 
     throw StateError('[BUG] $term is not satisfied.');
diff --git a/lib/src/solver/reformat_ranges.dart b/lib/src/solver/reformat_ranges.dart
index bdb64b8..5c763a6 100644
--- a/lib/src/solver/reformat_ranges.dart
+++ b/lib/src/solver/reformat_ranges.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
 
 import '../package_name.dart';
@@ -48,7 +47,7 @@
   var range = term.package.constraint as VersionRange;
 
   var min = _reformatMin(versions, range);
-  var tuple = _reformatMax(versions, range);
+  var tuple = reformatMax(versions, range);
   var max = tuple?.first;
   var includeMax = tuple?.last;
 
@@ -67,39 +66,46 @@
 
 /// Returns the new minimum version to use for [range], or `null` if it doesn't
 /// need to be reformatted.
-Version _reformatMin(List<PackageId> versions, VersionRange range) {
-  if (range.min == null) return null;
+Version? _reformatMin(List<PackageId> versions, VersionRange range) {
+  var min = range.min;
+  if (min == null) return null;
   if (!range.includeMin) return null;
-  if (!range.min.isFirstPreRelease) return null;
+  if (!min.isFirstPreRelease) return null;
 
-  var index = _lowerBound(versions, range.min);
+  var index = _lowerBound(versions, min);
   var next = index == versions.length ? null : versions[index].version;
 
   // If there's a real pre-release version of [range.min], use that as the min.
   // Otherwise, use the release version.
-  return next != null && equalsIgnoringPreRelease(range.min, next)
+  return next != null && equalsIgnoringPreRelease(min, next)
       ? next
-      : Version(range.min.major, range.min.minor, range.min.patch);
+      : Version(min.major, min.minor, min.patch);
 }
 
 /// Returns the new maximum version to use for [range] and whether that maximum
 /// is inclusive, or `null` if it doesn't need to be reformatted.
-Pair<Version, bool> _reformatMax(List<PackageId> versions, VersionRange range) {
-  if (range.max == null) return null;
+@visibleForTesting
+Pair<Version, bool>? reformatMax(List<PackageId> versions, VersionRange range) {
+  // This corresponds to the logic in the constructor of [VersionRange] with
+  // `alwaysIncludeMaxPreRelease = false` for discovering when a max-bound
+  // should not include prereleases.
+
+  var max = range.max;
+  var min = range.min;
+  if (max == null) return null;
   if (range.includeMax) return null;
-  if (range.max.isPreRelease) return null;
-  if (range.min != null &&
-      range.min.isPreRelease &&
-      equalsIgnoringPreRelease(range.min, range.max)) {
+  if (max.isPreRelease) return null;
+  if (max.build.isNotEmpty) return null;
+  if (min != null && min.isPreRelease && equalsIgnoringPreRelease(min, max)) {
     return null;
   }
 
-  var index = _lowerBound(versions, range.max);
+  var index = _lowerBound(versions, max);
   var previous = index == 0 ? null : versions[index - 1].version;
 
-  return previous != null && equalsIgnoringPreRelease(previous, range.max)
+  return previous != null && equalsIgnoringPreRelease(previous, max)
       ? Pair(previous, true)
-      : Pair(range.max.firstPreRelease, false);
+      : Pair(max.firstPreRelease, false);
 }
 
 /// Returns the first index in [ids] (which is sorted by version) whose version
diff --git a/lib/src/solver/report.dart b/lib/src/solver/report.dart
index 8bd3cc9..e3dd5dc 100644
--- a/lib/src/solver/report.dart
+++ b/lib/src/solver/report.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -74,7 +72,7 @@
     }).length;
 
     var suffix = '';
-    if (_root.dir != null) {
+    if (!_root.isInMemory) {
       final dir = path.normalize(_root.dir);
       if (dir != '.') {
         suffix = ' in $dir';
@@ -91,7 +89,7 @@
       }
     } else {
       if (numChanged == 0) {
-        if (_type == SolveType.GET) {
+        if (_type == SolveType.get) {
           log.message('Got dependencies$suffix!');
         } else {
           log.message('No dependencies changed$suffix.');
@@ -153,7 +151,11 @@
       if (id.source == null) continue;
       final status =
           await _cache.source(id.source).status(id, maxAge: Duration(days: 3));
-      if (status.isDiscontinued) numDiscontinued++;
+      if (status.isDiscontinued &&
+          (_root.dependencyType(id.name) == DependencyType.direct ||
+              _root.dependencyType(id.name) == DependencyType.dev)) {
+        numDiscontinued++;
+      }
     }
     if (numDiscontinued > 0) {
       if (numDiscontinued == 1) {
@@ -168,7 +170,7 @@
   /// instruction to run `pub outdated` if outdated packages are detected.
   void reportOutdated() {
     final outdatedPackagesCount = _result.packages.where((id) {
-      final versions = _result.availableVersions[id.name];
+      final versions = _result.availableVersions[id.name]!;
       // A version is counted:
       // - if there is a newer version which is not a pre-release and current
       // version is also not a pre-release or,
@@ -199,7 +201,7 @@
       {bool alwaysShow = false, bool highlightOverride = true}) async {
     var newId = _dependencies[name];
     var oldId = _previousLockFile.packages[name];
-    var id = newId ?? oldId;
+    var id = newId ?? oldId!;
 
     var isOverridden = _root.dependencyOverrides.containsKey(id.name);
 
@@ -240,11 +242,11 @@
       // Unchanged.
       icon = '  ';
     }
-    String message;
+    String? message;
     // See if there are any newer versions of the package that we were
     // unable to upgrade to.
-    if (newId != null && _type != SolveType.DOWNGRADE) {
-      var versions = _result.availableVersions[newId.name];
+    if (newId != null && _type != SolveType.downgrade) {
+      var versions = _result.availableVersions[newId.name]!;
 
       var newerStable = false;
       var newerUnstable = false;
@@ -270,7 +272,9 @@
         } else {
           message = '(retracted)';
         }
-      } else if (status.isDiscontinued) {
+      } else if (status.isDiscontinued &&
+          (_root.dependencyType(name) == DependencyType.direct ||
+              _root.dependencyType(name) == DependencyType.dev)) {
         if (status.discontinuedReplacedBy == null) {
           message = '(discontinued)';
         } else {
@@ -288,7 +292,7 @@
       }
     }
 
-    if (_type == SolveType.GET &&
+    if (_type == SolveType.get &&
         !(alwaysShow || changed || addedOrRemoved || message != null)) {
       return;
     }
@@ -301,7 +305,7 @@
     // If the package was upgraded, show what it was upgraded from.
     if (changed) {
       _output.write(' (was ');
-      _writeId(oldId);
+      _writeId(oldId!);
       _output.write(')');
     }
 
@@ -320,7 +324,7 @@
     _output.write(id.version);
 
     if (id.source != _sources.defaultSource) {
-      var description = id.source.formatDescription(id.description);
+      var description = id.source!.formatDescription(id.description);
       _output.write(' from ${id.source} $description');
     }
   }
diff --git a/lib/src/solver/result.dart b/lib/src/solver/result.dart
index ebcdfd7..bc01162 100644
--- a/lib/src/solver/result.dart
+++ b/lib/src/solver/result.dart
@@ -2,15 +2,19 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
+import '../http.dart';
+import '../io.dart';
 import '../lock_file.dart';
+import '../log.dart' as log;
 import '../package.dart';
 import '../package_name.dart';
+import '../pub_embeddable_command.dart';
 import '../pubspec.dart';
+import '../source/cached.dart';
+import '../source/hosted.dart';
 import '../source_registry.dart';
 import '../system_cache.dart';
 import 'report.dart';
@@ -44,6 +48,9 @@
   /// because it found an invalid solution.
   final int attemptedSolutions;
 
+  /// The wall clock time the resolution took.
+  final Duration resolutionTime;
+
   /// The [LockFile] representing the packages selected by this version
   /// resolution.
   LockFile get lockFile {
@@ -73,12 +80,22 @@
 
   final LockFile _previousLockFile;
 
+  /// Downloads all cached packages in [packages].
+  Future<void> downloadCachedPackages(SystemCache cache) async {
+    await Future.wait(packages.map((id) async {
+      if (id.source == null) return;
+      final source = cache.source(id.source);
+      if (source is! CachedSource) return;
+      return await withDependencyType(_root.dependencyType(id.name), () async {
+        await source.downloadToSystemCache(id);
+      });
+    }));
+  }
+
   /// Returns the names of all packages that were changed.
   ///
   /// This includes packages that were added or removed.
   Set<String> get changedPackages {
-    if (packages == null) return null;
-
     var changed = packages
         .where((id) => _previousLockFile.packages[id.name] != id)
         .map((id) => id.name)
@@ -89,8 +106,15 @@
         .toSet());
   }
 
-  SolveResult(this._sources, this._root, this._previousLockFile, this.packages,
-      this.pubspecs, this.availableVersions, this.attemptedSolutions);
+  SolveResult(
+      this._sources,
+      this._root,
+      this._previousLockFile,
+      this.packages,
+      this.pubspecs,
+      this.availableVersions,
+      this.attemptedSolutions,
+      this.resolutionTime);
 
   /// Displays a report of what changes were made to the lockfile.
   ///
@@ -112,12 +136,58 @@
     final report =
         SolveReport(type, _sources, _root, _previousLockFile, this, cache);
     report.summarize(dryRun: dryRun);
-    if (type == SolveType.UPGRADE) {
+    if (type == SolveType.upgrade) {
       await report.reportDiscontinued();
       report.reportOutdated();
     }
   }
 
+  /// Send analytics about the package resolution.
+  void sendAnalytics(PubAnalytics pubAnalytics) {
+    ArgumentError.checkNotNull(pubAnalytics);
+    final analytics = pubAnalytics.analytics;
+    if (analytics == null) return;
+
+    final dependenciesForAnalytics = <PackageId>[];
+    for (final package in packages) {
+      // Only send analytics for packages from pub.dev.
+      if (HostedSource.isFromPubDev(package) ||
+          (package.source is HostedSource && runningFromTest)) {
+        dependenciesForAnalytics.add(package);
+      }
+    }
+    // Randomize the dependencies, such that even if some analytics events don't
+    // get sent, the results will still be representative.
+    shuffle(dependenciesForAnalytics);
+    for (final package in dependenciesForAnalytics) {
+      final dependencyKind = const {
+        DependencyType.dev: 'dev',
+        DependencyType.direct: 'direct',
+        DependencyType.none: 'transitive'
+      }[_root.dependencyType(package.name)]!;
+      analytics.sendEvent(
+        'pub-get',
+        package.name,
+        label: package.version.canonicalizedVersion,
+        value: 1,
+        parameters: {
+          'ni': '1', // We consider a pub-get a non-interactive event.
+          pubAnalytics.dependencyKindCustomDimensionName: dependencyKind,
+        },
+      );
+      log.fine(
+          'Sending analytics hit for "pub-get" of ${package.name} version ${package.version} as dependency-kind $dependencyKind');
+    }
+
+    analytics.sendTiming(
+      'resolution',
+      resolutionTime.inMilliseconds,
+      category: 'pub-get',
+    );
+    log.fine(
+        'Sending analytics timing "pub-get" took ${resolutionTime.inMilliseconds} miliseconds');
+  }
+
   @override
   String toString() => 'Took $attemptedSolutions tries to resolve to\n'
       '- ${packages.join("\n- ")}';
diff --git a/lib/src/solver/set_relation.dart b/lib/src/solver/set_relation.dart
index 09a1a80..2d88eb9 100644
--- a/lib/src/solver/set_relation.dart
+++ b/lib/src/solver/set_relation.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 /// An enum of possible relationships between two sets.
 class SetRelation {
   /// The second set contains all elements of the first, as well as possibly
diff --git a/lib/src/solver/term.dart b/lib/src/solver/term.dart
index f5f4662..266fabc 100644
--- a/lib/src/solver/term.dart
+++ b/lib/src/solver/term.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub_semver/pub_semver.dart';
 
 import '../package_name.dart';
@@ -60,7 +58,9 @@
         if (otherConstraint.allowsAll(constraint)) return SetRelation.subset;
 
         // foo ^2.0.0 is disjoint with foo ^1.0.0
-        if (!constraint.allowsAny(otherConstraint)) return SetRelation.disjoint;
+        if (!constraint.allowsAny(otherConstraint)) {
+          return SetRelation.disjoint;
+        }
 
         // foo >=1.5.0 <3.0.0 overlaps foo ^1.0.0
         return SetRelation.overlapping;
@@ -69,7 +69,9 @@
         if (!_compatiblePackage(other.package)) return SetRelation.overlapping;
 
         // not foo ^1.0.0 is disjoint with foo ^1.5.0
-        if (constraint.allowsAll(otherConstraint)) return SetRelation.disjoint;
+        if (constraint.allowsAll(otherConstraint)) {
+          return SetRelation.disjoint;
+        }
 
         // not foo ^1.5.0 overlaps foo ^1.0.0
         // not foo ^2.0.0 is a superset of foo ^1.5.0
@@ -110,7 +112,7 @@
   ///
   /// Throws an [ArgumentError] if [other] doesn't refer to a package with the
   /// same name as [package].
-  Term intersect(Term other) {
+  Term? intersect(Term other) {
     if (package.name != other.package.name) {
       throw ArgumentError.value(
           other, 'other', 'should refer to package ${package.name}');
@@ -148,7 +150,7 @@
   ///
   /// Throws an [ArgumentError] if [other] doesn't refer to a package with the
   /// same name as [package].
-  Term difference(Term other) => intersect(other.inverse); // A ∖ B → A ∩ not B
+  Term? difference(Term other) => intersect(other.inverse); // A ∖ B → A ∩ not B
 
   /// Returns whether [other] is compatible with [package].
   bool _compatiblePackage(PackageRange other) =>
@@ -157,7 +159,7 @@
   /// Returns a new [Term] with the same package as [this] and with
   /// [constraint], unless that would produce a term that allows no packages,
   /// in which case this returns `null`.
-  Term _nonEmptyTerm(VersionConstraint constraint, bool isPositive) =>
+  Term? _nonEmptyTerm(VersionConstraint constraint, bool isPositive) =>
       constraint.isEmpty
           ? null
           : Term(package.withConstraint(constraint), isPositive);
diff --git a/lib/src/solver/type.dart b/lib/src/solver/type.dart
index f0f37af..28a3230 100644
--- a/lib/src/solver/type.dart
+++ b/lib/src/solver/type.dart
@@ -2,21 +2,19 @@
 // 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.
 
-// @dart=2.10
-
 /// An enum for types of version resolution.
 class SolveType {
   /// As few changes to the lockfile as possible to be consistent with the
   /// pubspec.
-  static const GET = SolveType._('get');
+  static const get = SolveType._('get');
 
   /// Upgrade all packages or specific packages to the highest versions
   /// possible, regardless of the lockfile.
-  static const UPGRADE = SolveType._('upgrade');
+  static const upgrade = SolveType._('upgrade');
 
   /// Downgrade all packages or specific packages to the lowest versions
   /// possible, regardless of the lockfile.
-  static const DOWNGRADE = SolveType._('downgrade');
+  static const downgrade = SolveType._('downgrade');
 
   final String _name;
 
diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart
index ee1541d..d4e3a75 100644
--- a/lib/src/solver/version_solver.dart
+++ b/lib/src/solver/version_solver.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:math' as math;
 
@@ -78,6 +76,8 @@
   /// The set of packages for which the lockfile should be ignored.
   final Set<String> _unlock;
 
+  final _stopwatch = Stopwatch();
+
   VersionSolver(this._type, this._systemCache, this._root, this._lockFile,
       Iterable<String> unlock)
       : _dependencyOverrides = _root.pubspec.dependencyOverrides,
@@ -86,14 +86,13 @@
   /// Finds a set of dependencies that match the root package's constraints, or
   /// throws an error if no such set is available.
   Future<SolveResult> solve() async {
-    var stopwatch = Stopwatch()..start();
-
+    _stopwatch.start();
     _addIncompatibility(Incompatibility(
         [Term(PackageRange.root(_root), false)], IncompatibilityCause.root));
 
     try {
       return await _systemCache.hosted.withPrefetching(() async {
-        var next = _root.name;
+        String? next = _root.name;
         while (next != null) {
           _propagate(next);
           next = await _choosePackageVersion();
@@ -103,7 +102,7 @@
       });
     } finally {
       // Gather some solving metrics.
-      log.solver('Version solving took ${stopwatch.elapsed} seconds.\n'
+      log.solver('Version solving took ${_stopwatch.elapsed} seconds.\n'
           'Tried ${_solution.attemptedSolutions} solutions.');
     }
   }
@@ -123,7 +122,7 @@
       // general incompatibilities as time goes on. If we look at those first,
       // we can derive stronger assignments sooner and more eagerly find
       // conflicts.
-      for (var incompatibility in _incompatibilities[package].reversed) {
+      for (var incompatibility in _incompatibilities[package]!.reversed) {
         var result = _propagateIncompatibility(incompatibility);
         if (result == #conflict) {
           // If [incompatibility] is satisfied by [_solution], we use
@@ -159,7 +158,7 @@
     // The first entry in `incompatibility.terms` that's not yet satisfied by
     // [_solution], if one exists. If we find more than one, [_solution] is
     // inconclusive for [incompatibility] and we can't deduce anything.
-    Term unsatisfied;
+    Term? unsatisfied;
 
     for (var i = 0; i < incompatibility.terms.length; i++) {
       var term = incompatibility.terms[i];
@@ -208,17 +207,17 @@
     while (!incompatibility.isFailure) {
       // The term in `incompatibility.terms` that was most recently satisfied by
       // [_solution].
-      Term mostRecentTerm;
+      Term? mostRecentTerm;
 
       // The earliest assignment in [_solution] such that [incompatibility] is
       // satisfied by [_solution] up to and including this assignment.
-      Assignment mostRecentSatisfier;
+      Assignment? mostRecentSatisfier;
 
       // The difference between [mostRecentSatisfier] and [mostRecentTerm];
       // that is, the versions that are allowed by [mostRecentSatisfier] and not
       // by [mostRecentTerm]. This is `null` if [mostRecentSatisfier] totally
       // satisfies [mostRecentTerm].
-      Term difference;
+      Term? difference;
 
       // The decision level of the earliest assignment in [_solution] *before*
       // [mostRecentSatisfier] such that [incompatibility] is satisfied by
@@ -251,7 +250,7 @@
           // If [mostRecentSatisfier] doesn't satisfy [mostRecentTerm] on its
           // own, then the next-most-recent satisfier may be the one that
           // satisfies the remainder.
-          difference = mostRecentSatisfier.difference(mostRecentTerm);
+          difference = mostRecentSatisfier.difference(mostRecentTerm!);
           if (difference != null) {
             previousSatisfierLevel = math.max(previousSatisfierLevel,
                 _solution.satisfier(difference.inverse).decisionLevel);
@@ -264,7 +263,7 @@
       // than a derivation), then [incompatibility] is the root cause. We then
       // backjump to [previousSatisfierLevel], where [incompatibility] is
       // guaranteed to allow [_propagate] to produce more assignments.
-      if (previousSatisfierLevel < mostRecentSatisfier.decisionLevel ||
+      if (previousSatisfierLevel < mostRecentSatisfier!.decisionLevel ||
           mostRecentSatisfier.cause == null) {
         _solution.backtrack(previousSatisfierLevel);
         if (newIncompatibility) _addIncompatibility(incompatibility);
@@ -280,7 +279,7 @@
       var newTerms = <Term>[
         for (var term in incompatibility.terms)
           if (term != mostRecentTerm) term,
-        for (var term in mostRecentSatisfier.cause.terms)
+        for (var term in mostRecentSatisfier.cause!.terms)
           if (term.package != mostRecentSatisfier.package) term,
       ];
 
@@ -299,7 +298,7 @@
       if (difference != null) newTerms.add(difference.inverse);
 
       incompatibility = Incompatibility(
-          newTerms, ConflictCause(incompatibility, mostRecentSatisfier.cause));
+          newTerms, ConflictCause(incompatibility, mostRecentSatisfier.cause!));
       newIncompatibility = true;
 
       var partially = difference == null ? '' : ' partially';
@@ -318,7 +317,7 @@
   /// Returns the name of the package whose incompatibilities should be
   /// propagated by [_propagate], or `null` indicating that version solving is
   /// complete and a solution has been found.
-  Future<String> _choosePackageVersion() async {
+  Future<String?> _choosePackageVersion() async {
     var unsatisfied = _solution.unsatisfied.toList();
     if (unsatisfied.isEmpty) return null;
 
@@ -334,14 +333,14 @@
 
     /// Prefer packages with as few remaining versions as possible, so that if a
     /// conflict is necessary it's forced quickly.
-    var package = await minByAsync(unsatisfied, (package) async {
+    var package = await minByAsync(unsatisfied, (PackageRange package) async {
       return await _packageLister(package).countVersions(package.constraint);
     });
     if (package == null) {
       return null; // when unsatisfied.isEmpty
     }
 
-    PackageId version;
+    PackageId? version;
     try {
       version = await _packageLister(package).bestVersion(package.constraint);
     } on PackageNotFoundException catch (error) {
@@ -370,7 +369,7 @@
 
     var conflict = false;
     for (var incompatibility
-        in await _packageLister(package).incompatibilitiesFor(version)) {
+        in await _packageLister(package).incompatibilitiesFor(version!)) {
       _addIncompatibility(incompatibility);
 
       // If an incompatibility is already satisfied, then selecting [version]
@@ -418,13 +417,15 @@
     }
 
     return SolveResult(
-        _systemCache.sources,
-        _root,
-        _lockFile,
-        decisions,
-        pubspecs,
-        await _getAvailableVersions(decisions),
-        _solution.attemptedSolutions);
+      _systemCache.sources,
+      _root,
+      _lockFile,
+      decisions,
+      pubspecs,
+      await _getAvailableVersions(decisions),
+      _solution.attemptedSolutions,
+      _stopwatch.elapsed,
+    );
   }
 
   /// Generates a map containing all of the known available versions for each
@@ -484,15 +485,15 @@
           _root.dependencyType(package.name),
           overridden,
           _getAllowedRetracted(ref.name),
-          downgrade: _type == SolveType.DOWNGRADE);
+          downgrade: _type == SolveType.downgrade);
     });
   }
 
   /// Gets the version of [package] currently locked in the lock file.
   ///
   /// Returns `null` if it isn't in the lockfile (or has been unlocked).
-  PackageId _getLocked(String package) {
-    if (_type == SolveType.GET) {
+  PackageId? _getLocked(String? package) {
+    if (_type == SolveType.get) {
       if (_unlock.contains(package)) {
         return null;
       }
@@ -502,9 +503,9 @@
     // When downgrading, we don't want to force the latest versions of
     // non-hosted packages, since they don't support multiple versions and thus
     // can't be downgraded.
-    if (_type == SolveType.DOWNGRADE) {
+    if (_type == SolveType.downgrade) {
       var locked = _lockFile.packages[package];
-      if (locked != null && !locked.source.hasMultipleVersions) return locked;
+      if (locked != null && !locked.source!.hasMultipleVersions) return locked;
     }
 
     if (_unlock.isEmpty || _unlock.contains(package)) return null;
@@ -512,16 +513,17 @@
   }
 
   /// Gets the version of [package] which can be allowed during version solving
-  /// even if that version is marked as retracted.
+  /// even if that version is marked as retracted. Returns `null` if no such
+  /// version exists.
   ///
   /// We only allow resolving to a retracted version if it is already in the
   /// `pubspec.lock` or pinned in `dependency_overrides`.
-  Version _getAllowedRetracted(String package) {
+  Version? _getAllowedRetracted(String? package) {
     if (_dependencyOverrides.containsKey(package)) {
-      var range = _dependencyOverrides[package];
+      var range = _dependencyOverrides[package]!;
       if (range.constraint is Version) {
         // We have a pinned dependency.
-        return range.constraint;
+        return range.constraint as Version?;
       }
     }
     return _lockFile.packages[package]?.version;
@@ -530,7 +532,7 @@
   /// Logs [message] in the context of the current selected packages.
   ///
   /// If [message] is omitted, just logs a description of leaf-most selection.
-  void _log([String message]) {
+  void _log([String message = '']) {
     // Indent for the previous selections.
     log.solver(prefixLines(message, prefix: '  ' * _solution.decisionLevel));
   }
diff --git a/lib/src/source.dart b/lib/src/source.dart
index 4fe2322..ecbc5eb 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -2,13 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
+import 'package:collection/collection.dart' show IterableNullableExtension;
 import 'package:pub_semver/pub_semver.dart';
 
 import 'exceptions.dart';
+import 'language_version.dart';
 import 'package_name.dart';
 import 'pubspec.dart';
 import 'system_cache.dart';
@@ -77,16 +77,27 @@
   /// should be interpreted. This will be called during parsing to validate that
   /// the given [description] is well-formed according to this source, and to
   /// give the source a chance to canonicalize the description.
+  /// For simple hosted dependencies like `foo:` or `foo: ^1.2.3`, the
+  /// [description] may also be `null`.
   ///
   /// [containingPath] is the path to the pubspec where this description
   /// appears. It may be `null` if the description is coming from some in-memory
   /// source (such as pulling down a pubspec from pub.dartlang.org).
   ///
+  /// [languageVersion] is the minimum Dart version parsed from the pubspec's
+  /// `environment` field. Source implementations may use this parameter to only
+  /// support specific syntax for some versions.
+  ///
   /// The description in the returned [PackageRef] need bear no resemblance to
   /// the original user-provided description.
   ///
   /// Throws a [FormatException] if the description is not valid.
-  PackageRef parseRef(String name, description, {String containingPath});
+  PackageRef parseRef(
+    String name,
+    description, {
+    String? containingPath,
+    required LanguageVersion languageVersion,
+  });
 
   /// Parses a [PackageId] from a name and a serialized description.
   ///
@@ -99,13 +110,13 @@
   ///
   /// Throws a [FormatException] if the description is not valid.
   PackageId parseId(String name, Version version, description,
-      {String containingPath});
+      {String? containingPath});
 
   /// When a [LockFile] is serialized, it uses this method to get the
   /// [description] in the right format.
   ///
   /// [containingPath] is the containing directory of the root package.
-  dynamic serializeDescription(String containingPath, description) {
+  dynamic serializeDescription(String? containingPath, description) {
     return description;
   }
 
@@ -166,7 +177,7 @@
   /// selected even if it is marked as retracted. Otherwise, all the returned
   /// IDs correspond to non-retracted versions.
   Future<List<PackageId>> getVersions(PackageRef ref,
-      {Duration maxAge, Version allowedRetractedVersion}) async {
+      {Duration? maxAge, Version? allowedRetractedVersion}) async {
     if (ref.isRoot) {
       throw ArgumentError('Cannot get versions for the root package.');
     }
@@ -183,7 +194,7 @@
       }
       return null;
     })))
-        .where((element) => element != null)
+        .whereNotNull()
         .toList();
 
     return versions;
@@ -201,7 +212,7 @@
   ///
   /// This method is effectively protected: subclasses must implement it, but
   /// external code should not call this. Instead, call [getVersions].
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge);
+  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration? maxAge);
 
   /// A cache of pubspecs described by [describe].
   final _pubspecs = <PackageId, Pubspec>{};
@@ -256,7 +267,7 @@
   ///
   /// If id is a relative path id, the directory will be relative from
   /// [relativeFrom]. Returns an absolute path if [relativeFrom] is not passed.
-  String getDirectory(PackageId id, {String relativeFrom});
+  String getDirectory(PackageId id, {String? relativeFrom});
 
   /// Returns metadata about a given package.
   ///
@@ -265,7 +276,7 @@
   ///
   /// In the case of offline sources, [maxAge] is not used, since information is
   /// per definiton cached.
-  Future<PackageStatus> status(PackageId id, {Duration maxAge}) async =>
+  Future<PackageStatus> status(PackageId id, {Duration? maxAge}) async =>
       // Default implementation has no metadata.
       PackageStatus();
 
@@ -282,10 +293,11 @@
   /// `null` if not [isDiscontinued]. Otherwise contains the
   /// replacement string provided by the host or `null` if there is no
   /// replacement.
-  final String discontinuedReplacedBy;
+  final String? discontinuedReplacedBy;
   final bool isDiscontinued;
   final bool isRetracted;
-  PackageStatus({isDiscontinued, this.discontinuedReplacedBy, isRetracted})
-      : isDiscontinued = isDiscontinued ?? false,
-        isRetracted = isRetracted ?? false;
+  PackageStatus(
+      {this.isDiscontinued = false,
+      this.discontinuedReplacedBy,
+      this.isRetracted = false});
 }
diff --git a/lib/src/source/cached.dart b/lib/src/source/cached.dart
index c0d9ef1..0c8574d 100644
--- a/lib/src/source/cached.dart
+++ b/lib/src/source/cached.dart
@@ -2,11 +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.
 
-// @dart=2.10
-
 import 'dart:async';
 
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import '../io.dart';
@@ -43,7 +40,7 @@
   }
 
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) =>
+  String getDirectory(PackageId id, {String? relativeFrom}) =>
       getDirectoryInCache(id);
 
   String getDirectoryInCache(PackageId id);
@@ -82,5 +79,5 @@
   /// cache (but that might itself have failed).
   final bool success;
   final PackageId package;
-  RepairResult(this.package, {@required this.success});
+  RepairResult(this.package, {required this.success});
 }
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 5f1def9..3be752c 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -2,17 +2,17 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
+import 'package:collection/collection.dart' show IterableNullableExtension;
 import 'package:path/path.dart' as p;
 import 'package:pool/pool.dart';
 import 'package:pub_semver/pub_semver.dart';
 
 import '../git.dart' as git;
 import '../io.dart';
+import '../language_version.dart';
 import '../log.dart' as log;
 import '../package.dart';
 import '../package_name.dart';
@@ -44,7 +44,12 @@
   }
 
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(
+    String name,
+    description, {
+    String? containingPath,
+    LanguageVersion? languageVersion,
+  }) {
     dynamic url;
     dynamic ref;
     dynamic path;
@@ -73,7 +78,7 @@
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (description is! Map) {
       throw FormatException("The description must be a map with a 'url' "
           'key.');
@@ -103,10 +108,10 @@
   /// For the descriptions where `relative` attribute is `true`, tries to make
   /// `url` relative to the specified [containingPath].
   @override
-  dynamic serializeDescription(String containingPath, description) {
+  dynamic serializeDescription(String? containingPath, description) {
     final copy = Map.from(description);
     copy.remove('relative');
-    if (description['relative'] == true) {
+    if (description['relative'] == true && containingPath != null) {
       copy['url'] = p.url.relative(description['url'],
           from: Uri.file(containingPath).toString());
     }
@@ -114,7 +119,7 @@
   }
 
   /// Throws a [FormatException] if [url] isn't a valid Git URL.
-  Map<String, Object> _validatedUrl(dynamic url, String containingDir) {
+  Map<String, Object> _validatedUrl(dynamic url, String? containingDir) {
     if (url is! String) {
       throw FormatException("The 'url' field of the description must be a "
           'string.');
@@ -248,7 +253,8 @@
   }
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     return await _pool.withResource(() async {
       await _ensureRepoCache(ref);
       var path = _repoCachePath(ref);
@@ -301,7 +307,7 @@
 
     // Git doesn't recognize backslashes in paths, even on Windows.
     if (Platform.isWindows) pubspecPath = pubspecPath.replaceAll('\\', '/');
-    List<String> lines;
+    late List<String> lines;
     try {
       lines = await git
           .run(['show', '$revision:$pubspecPath'], workingDir: repoPath);
@@ -361,8 +367,8 @@
 
   /// Returns the path to the revision-specific cache of [id].
   @override
-  String getDirectoryInCache(PackageId id) =>
-      p.join(_revisionCachePath(id), id.description['path']);
+  String getDirectoryInCache(PackageId? id) =>
+      p.join(_revisionCachePath(id!), id.description['path']);
 
   @override
   List<Package> getCachedPackages() {
@@ -401,7 +407,7 @@
             }
           });
         })
-        .where((package) => package != null)
+        .whereNotNull()
         .toList();
 
     // Note that there may be multiple packages with the same name and version
@@ -516,7 +522,7 @@
         ['rev-parse', '--is-inside-git-dir'],
         workingDir: dirPath,
       );
-      if (result?.join('\n') != 'true') {
+      if (result.join('\n') != 'true') {
         isValid = false;
       }
     } on git.GitException {
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 11f37b8..2cfc52a 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -2,16 +2,14 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
 
-import 'package:collection/collection.dart' show maxBy;
+import 'package:collection/collection.dart'
+    show maxBy, IterableNullableExtension;
 import 'package:http/http.dart' as http;
 import 'package:path/path.dart' as p;
-import 'package:pedantic/pedantic.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:stack_trace/stack_trace.dart';
 
@@ -19,6 +17,7 @@
 import '../exceptions.dart';
 import '../http.dart';
 import '../io.dart';
+import '../language_version.dart';
 import '../log.dart' as log;
 import '../package.dart';
 import '../package_name.dart';
@@ -97,6 +96,13 @@
           ? _OfflineHostedSource(this, systemCache)
           : BoundHostedSource(this, systemCache);
 
+  static String pubDevUrl = 'https://pub.dartlang.org';
+
+  static bool isFromPubDev(PackageId id) {
+    return id.source is HostedSource &&
+        (id.description as _HostedDescription).uri.toString() == pubDevUrl;
+  }
+
   /// Gets the default URL for the package server for hosted dependencies.
   Uri get defaultUrl {
     // Changing this to pub.dev raises the following concerns:
@@ -120,27 +126,27 @@
     }
   }
 
-  Uri _defaultUrl;
+  Uri? _defaultUrl;
 
   /// Returns a reference to a hosted package named [name].
   ///
   /// If [url] is passed, it's the URL of the pub server from which the package
   /// should be downloaded. [url] most be normalized and validated using
   /// [validateAndNormalizeHostedUrl].
-  PackageRef refFor(String name, {Uri url}) =>
-      PackageRef(name, this, _descriptionFor(name, url));
+  PackageRef refFor(String name, {Uri? url}) =>
+      PackageRef(name, this, _HostedDescription(name, url ?? defaultUrl));
 
   /// Returns an ID for a hosted package named [name] at [version].
   ///
   /// If [url] is passed, it's the URL of the pub server from which the package
   /// should be downloaded. [url] most be normalized and validated using
   /// [validateAndNormalizeHostedUrl].
-  PackageId idFor(String name, Version version, {Uri url}) =>
-      PackageId(name, this, version, _descriptionFor(name, url));
+  PackageId idFor(String name, Version version, {Uri? url}) => PackageId(
+      name, this, version, _HostedDescription(name, url ?? defaultUrl));
 
   /// Returns the description for a hosted package named [name] with the
   /// given package server [url].
-  dynamic _descriptionFor(String name, [Uri url]) {
+  dynamic _serializedDescriptionFor(String name, [Uri? url]) {
     if (url == null) {
       return name;
     }
@@ -153,54 +159,118 @@
   }
 
   @override
+  dynamic serializeDescription(String? containingPath, description) {
+    final desc = _asDescription(description);
+    return _serializedDescriptionFor(desc.packageName, desc.uri);
+  }
+
+  @override
   String formatDescription(description) =>
-      'on ${_parseDescription(description).last}';
+      'on ${_asDescription(description).uri}';
 
   @override
   bool descriptionsEqual(description1, description2) =>
-      _parseDescription(description1) == _parseDescription(description2);
+      _asDescription(description1) == _asDescription(description2);
 
   @override
-  int hashDescription(description) => _parseDescription(description).hashCode;
+  int hashDescription(description) => _asDescription(description).hashCode;
 
   /// Ensures that [description] is a valid hosted package description.
   ///
-  /// There are two valid formats. A plain string refers to a package with the
-  /// given name from the default host, while a map with keys "name" and "url"
-  /// refers to a package with the given name from the host at the given URL.
+  /// Simple hosted dependencies only consist of a plain string, which is
+  /// resolved against the default host. In this case, [description] will be
+  /// null.
+  ///
+  /// Hosted dependencies may also specify a custom host from which the package
+  /// is fetched. There are two syntactic forms of those dependencies:
+  ///
+  ///  1. With an url and an optional name in a map: `hosted: {url: <url>}`
+  ///  2. With a direct url: `hosted: <url>`
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
-    _parseDescription(description);
-    return PackageRef(name, this, description);
+  PackageRef parseRef(String name, description,
+      {String? containingPath, required LanguageVersion languageVersion}) {
+    return PackageRef(
+        name, this, _parseDescription(name, description, languageVersion));
   }
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
-    _parseDescription(description);
-    return PackageId(name, this, version, description);
+      {String? containingPath}) {
+    // Old pub versions only wrote `description: <pkg>` into the lock file.
+    if (description is String) {
+      if (description != name) {
+        throw FormatException('The description should be the same as the name');
+      }
+      return PackageId(
+          name, this, version, _HostedDescription(name, defaultUrl));
+    }
+
+    final serializedDescription = (description as Map).cast<String, String>();
+
+    return PackageId(
+      name,
+      this,
+      version,
+      _HostedDescription(serializedDescription['name']!,
+          Uri.parse(serializedDescription['url']!)),
+    );
   }
 
+  _HostedDescription _asDescription(desc) => desc as _HostedDescription;
+
   /// Parses the description for a package.
   ///
   /// If the package parses correctly, this returns a (name, url) pair. If not,
   /// this throws a descriptive FormatException.
-  Pair<String, Uri> _parseDescription(description) {
+  _HostedDescription _parseDescription(
+    String packageName,
+    description,
+    LanguageVersion languageVersion,
+  ) {
+    if (description == null) {
+      // Simple dependency without a `hosted` block, use the default server.
+      return _HostedDescription(packageName, defaultUrl);
+    }
+
+    final canUseShorthandSyntax = languageVersion.supportsShorterHostedSyntax;
+
     if (description is String) {
-      return Pair<String, Uri>(description, defaultUrl);
+      // Old versions of pub (pre Dart 2.15) interpret `hosted: foo` as
+      // `hosted: {name: foo, url: <default>}`.
+      // For later versions, we treat it as `hosted: {name: <inferred>,
+      // url: foo}` if a user opts in by raising their min SDK environment.
+      //
+      // Since the old behavior is very rarely used and we want to show a
+      // helpful error message if the new syntax is used without raising the SDK
+      // environment, we throw an error if something that looks like a URI is
+      // used as a package name.
+      if (canUseShorthandSyntax) {
+        return _HostedDescription(
+            packageName, validateAndNormalizeHostedUrl(description));
+      } else {
+        if (_looksLikePackageName.hasMatch(description)) {
+          // Valid use of `hosted: package` dependency with an old SDK
+          // environment.
+          return _HostedDescription(description, defaultUrl);
+        } else {
+          throw FormatException(
+            'Using `hosted: <url>` is only supported with a minimum SDK '
+            'constraint of ${LanguageVersion.firstVersionWithShorterHostedSyntax}.',
+          );
+        }
+      }
     }
 
     if (description is! Map) {
       throw FormatException('The description must be a package name or map.');
     }
 
-    if (!description.containsKey('name')) {
-      throw FormatException("The description map must contain a 'name' key.");
-    }
-
     var name = description['name'];
+    if (canUseShorthandSyntax) name ??= packageName;
+
     if (name is! String) {
-      throw FormatException("The 'name' key must have a string value.");
+      throw FormatException("The 'name' key must have a string value without "
+          'a minimum Dart SDK constraint of ${LanguageVersion.firstVersionWithShorterHostedSyntax}.0 or higher.');
     }
 
     var url = defaultUrl;
@@ -212,8 +282,11 @@
       url = validateAndNormalizeHostedUrl(u);
     }
 
-    return Pair<String, Uri>(name, url);
+    return _HostedDescription(name, url);
   }
+
+  static final RegExp _looksLikePackageName =
+      RegExp(r'^[a-zA-Z_]+[a-zA-Z0-9_]*$');
 }
 
 /// Information about a package version retrieved from /api/packages/$package
@@ -225,6 +298,28 @@
   _VersionInfo(this.pubspec, this.archiveUrl, this.status);
 }
 
+/// The [PackageName.description] for a [HostedSource], storing the package name
+/// and resolved URI of the package server.
+class _HostedDescription {
+  final String packageName;
+  final Uri uri;
+
+  _HostedDescription(this.packageName, this.uri) {
+    ArgumentError.checkNotNull(packageName, 'packageName');
+    ArgumentError.checkNotNull(uri, 'uri');
+  }
+
+  @override
+  int get hashCode => Object.hash(packageName, uri);
+
+  @override
+  bool operator ==(Object other) {
+    return other is _HostedDescription &&
+        other.packageName == packageName &&
+        other.uri == uri;
+  }
+}
+
 /// The [BoundSource] for [HostedSource].
 class BoundHostedSource extends CachedSource {
   @override
@@ -232,7 +327,8 @@
 
   @override
   final SystemCache systemCache;
-  RateLimitedScheduler<PackageRef, Map<PackageId, _VersionInfo>> _scheduler;
+  late RateLimitedScheduler<PackageRef, Map<PackageId, _VersionInfo>?>
+      _scheduler;
 
   BoundHostedSource(this.source, this.systemCache) {
     _scheduler = RateLimitedScheduler(
@@ -255,9 +351,9 @@
           var archiveUrl = map['archive_url'];
           if (archiveUrl is String) {
             final status = PackageStatus(
-                isDiscontinued: body['isDiscontinued'] as bool ?? false,
-                discontinuedReplacedBy: body['replacedBy'] as String,
-                isRetracted: map['retracted'] as bool ?? false);
+                isDiscontinued: body['isDiscontinued'] ?? false,
+                discontinuedReplacedBy: body['replacedBy'],
+                isRetracted: map['retracted'] ?? false);
             return MapEntry(
                 id, _VersionInfo(pubspec, Uri.parse(archiveUrl), status));
           }
@@ -269,15 +365,15 @@
     throw FormatException('versions must be a list');
   }
 
-  Future<Map<PackageId, _VersionInfo>> _fetchVersionsNoPrefetching(
+  Future<Map<PackageId, _VersionInfo>?> _fetchVersionsNoPrefetching(
       PackageRef ref) async {
     final serverUrl = _hostedUrl(ref.description);
     final url = _listVersionsUrl(ref.description);
     log.io('Get versions from $url.');
 
-    String bodyText;
-    Map body;
-    Map<PackageId, _VersionInfo> result;
+    late final String bodyText;
+    late final dynamic body;
+    late final Map<PackageId, _VersionInfo> result;
     try {
       // TODO(sigurdm): Implement cancellation of requests. This probably
       // requires resolution of: https://github.com/dart-lang/sdk/issues/22265.
@@ -286,39 +382,44 @@
         serverUrl,
         (client) => client.read(url, headers: pubApiHeaders),
       );
-      body = jsonDecode(bodyText);
+      final decoded = jsonDecode(bodyText);
+      if (decoded is! Map<String, dynamic>) {
+        throw FormatException('version listing must be a mapping');
+      }
+      body = decoded;
       result = _versionInfoFromPackageListing(body, ref, url);
-    } catch (error, stackTrace) {
-      var parsed = source._parseDescription(ref.description);
-      _throwFriendlyError(error, stackTrace, parsed.first, parsed.last);
+    } on Exception catch (error, stackTrace) {
+      final packageName = source._asDescription(ref.description).packageName;
+      _throwFriendlyError(error, stackTrace, packageName, serverUrl);
     }
 
     // Cache the response on disk.
     // Don't cache overly big responses.
-    if (body.length < 100 * 1024) {
+    if (bodyText.length < 100 * 1024) {
       await _cacheVersionListingResponse(body, ref);
     }
     return result;
   }
 
-  Future<Map<PackageId, _VersionInfo>> _fetchVersions(PackageRef ref) async {
+  Future<Map<PackageId, _VersionInfo>?> _fetchVersions(PackageRef ref) async {
     final preschedule =
-        Zone.current[_prefetchingKey] as void Function(PackageRef);
+        Zone.current[_prefetchingKey] as void Function(PackageRef)?;
 
     /// Prefetch the dependencies of the latest version, we are likely to need
     /// them later.
-    void prescheduleDependenciesOfLatest(Map<PackageId, _VersionInfo> listing) {
+    void prescheduleDependenciesOfLatest(
+        Map<PackageId, _VersionInfo>? listing) {
       if (listing == null) return;
       final latestVersion =
-          maxBy(listing.keys.map((id) => id.version), (e) => e);
+          maxBy(listing.keys.map((id) => id.version), (e) => e)!;
       final latestVersionId =
           PackageId(ref.name, source, latestVersion, ref.description);
       final dependencies =
-          listing[latestVersionId]?.pubspec?.dependencies?.values ?? [];
+          listing[latestVersionId]?.pubspec.dependencies.values ?? [];
       unawaited(withDependencyType(DependencyType.none, () async {
         for (final packageRange in dependencies) {
           if (packageRange.source is HostedSource) {
-            preschedule(packageRange.toRef());
+            preschedule!(packageRange.toRef());
           }
         }
       }));
@@ -341,6 +442,15 @@
     return result;
   }
 
+  /// An in-memory cache to store the cached version listing loaded from
+  /// [_versionListingCachePath].
+  ///
+  /// Invariant: Entries in this cache are the parsed version of the exact same
+  ///  information cached on disk. I.e. if the entry is present in this cache,
+  /// there will not be a newer version on disk.
+  final Map<PackageRef, Pair<DateTime, Map<PackageId, _VersionInfo>>>
+      _responseCache = {};
+
   /// If a cached version listing response for [ref] exists on disk and is less
   /// than [maxAge] old it is parsed and returned.
   ///
@@ -348,29 +458,38 @@
   ///
   /// If [maxAge] is not given, we will try to get the cached version no matter
   /// how old it is.
-  Future<Map<PackageId, _VersionInfo>> _cachedVersionListingResponse(
+  Future<Map<PackageId, _VersionInfo>?> _cachedVersionListingResponse(
       PackageRef ref,
-      {Duration maxAge}) async {
+      {Duration? maxAge}) async {
+    if (_responseCache.containsKey(ref)) {
+      final cacheAge = DateTime.now().difference(_responseCache[ref]!.first);
+      if (maxAge == null || maxAge > cacheAge) {
+        // The cached value is not too old.
+        return _responseCache[ref]!.last;
+      }
+    }
     final cachePath = _versionListingCachePath(ref);
-    final stat = await io.File(cachePath).stat();
+    final stat = io.File(cachePath).statSync();
     final now = DateTime.now();
     if (stat.type == io.FileSystemEntityType.file) {
       if (maxAge == null || now.difference(stat.modified) < maxAge) {
         try {
-          final cachedDoc = jsonDecode(await readTextFileAsync(cachePath));
+          final cachedDoc = jsonDecode(readTextFile(cachePath));
           final timestamp = cachedDoc['_fetchedAt'];
           if (timestamp is String) {
-            final cacheAge =
-                DateTime.now().difference(DateTime.parse(timestamp));
+            final parsedTimestamp = DateTime.parse(timestamp);
+            final cacheAge = DateTime.now().difference(parsedTimestamp);
             if (maxAge != null && cacheAge > maxAge) {
               // Too old according to internal timestamp - delete.
               tryDeleteEntry(cachePath);
             } else {
-              return _versionInfoFromPackageListing(
+              var res = _versionInfoFromPackageListing(
                 cachedDoc,
                 ref,
                 Uri.file(cachePath),
               );
+              _responseCache[ref] = Pair(parsedTimestamp, res);
+              return res;
             }
           }
         } on io.IOException {
@@ -389,7 +508,8 @@
   }
 
   /// Saves the (decoded) response from package-listing of [ref].
-  Future<void> _cacheVersionListingResponse(Map body, PackageRef ref) async {
+  Future<void> _cacheVersionListingResponse(
+      Map<String, dynamic> body, PackageRef ref) async {
     final path = _versionListingCachePath(ref);
     try {
       ensureDir(p.dirname(path));
@@ -402,6 +522,9 @@
           },
         ),
       );
+      // Delete the entry in the in-memory cache to maintain the invariant that
+      // cached information in memory is the same as that on the disk.
+      _responseCache.remove(ref);
     } on io.IOException catch (e) {
       // Not being able to write this cache is not fatal. Just move on...
       log.fine('Failed writing cache file. $e');
@@ -409,7 +532,7 @@
   }
 
   @override
-  Future<PackageStatus> status(PackageId id, {Duration maxAge}) async {
+  Future<PackageStatus> status(PackageId id, {Duration? maxAge}) async {
     final ref = id.toRef();
     // Did we already get info for this package?
     var versionListing = _scheduler.peek(ref);
@@ -427,7 +550,7 @@
           test: (error) => error is Exception,
         );
 
-    final listing = versionListing[id];
+    final listing = versionListing![id];
     // If we don't have the specific version we return the empty response, since
     // it is more or less harmless..
     //
@@ -441,8 +564,8 @@
 
   // The path where the response from the package-listing api is cached.
   String _versionListingCachePath(PackageRef ref) {
-    final parsed = source._parseDescription(ref.description);
-    final dir = _urlToDirectory(parsed.last);
+    final parsed = source._asDescription(ref.description);
+    final dir = _urlToDirectory(parsed.uri);
     // Use a dot-dir because older versions of pub won't choke on that
     // name when iterating the cache (it is not listed by [listDir]).
     return p.join(systemCacheRoot, dir, _versionListingDirectory,
@@ -454,7 +577,8 @@
   /// Downloads a list of all versions of a package that are available from the
   /// site.
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     var versionListing = _scheduler.peek(ref);
     if (maxAge != null) {
       // Do we have a cached version response on disk?
@@ -462,22 +586,22 @@
           await _cachedVersionListingResponse(ref, maxAge: maxAge);
     }
     versionListing ??= await _scheduler.schedule(ref);
-    return versionListing.keys.toList();
+    return versionListing!.keys.toList();
   }
 
   /// Parses [description] into its server and package name components, then
   /// converts that to a Uri for listing versions of the given package.
   Uri _listVersionsUrl(description) {
-    final parsed = source._parseDescription(description);
-    final hostedUrl = parsed.last;
-    final package = Uri.encodeComponent(parsed.first);
+    final parsed = source._asDescription(description);
+    final hostedUrl = parsed.uri;
+    final package = Uri.encodeComponent(parsed.packageName);
     return hostedUrl.resolve('api/packages/$package');
   }
 
   /// Parses [description] into server name component.
   Uri _hostedUrl(description) {
-    final parsed = source._parseDescription(description);
-    return parsed.last;
+    final parsed = source._asDescription(description);
+    return parsed.uri;
   }
 
   /// Retrieves the pubspec for a specific version of a package that is
@@ -486,7 +610,7 @@
   Future<Pubspec> describeUncached(PackageId id) async {
     final versions = await _scheduler.schedule(id.toRef());
     final url = _listVersionsUrl(id.description);
-    return versions[id]?.pubspec ??
+    return versions![id]?.pubspec ??
         (throw PackageNotFoundException('Could not find package $id at $url'));
   }
 
@@ -509,9 +633,9 @@
   /// package downloaded from that site.
   @override
   String getDirectoryInCache(PackageId id) {
-    var parsed = source._parseDescription(id.description);
-    var dir = _urlToDirectory(parsed.last);
-    return p.join(systemCacheRoot, dir, '${parsed.first}-${id.version}');
+    var parsed = source._asDescription(id.description);
+    var dir = _urlToDirectory(parsed.uri);
+    return p.join(systemCacheRoot, dir, '${parsed.packageName}-${id.version}');
   }
 
   /// Re-downloads all packages that have been previously downloaded into the
@@ -564,6 +688,7 @@
           packages.map((package) async {
             var id = source.idFor(package.name, package.version, url: url);
             try {
+              deleteEntry(package.dir);
               await _download(id, package.dir);
               return RepairResult(id, success: true);
             } catch (error, stackTrace) {
@@ -584,7 +709,7 @@
 
   /// Returns the best-guess package ID for [basename], which should be a
   /// subdirectory in a hosted cache.
-  PackageId _idForBasename(String basename, {Uri url}) {
+  PackageId _idForBasename(String basename, {Uri? url}) {
     var components = split1(basename, '-');
     var version = Version.none;
     if (components.length > 1) {
@@ -621,7 +746,7 @@
             return null;
           }
         })
-        .where((e) => e != null)
+        .whereNotNull()
         .toList();
   }
 
@@ -642,20 +767,17 @@
     // query-string as is the case with signed S3 URLs. And we wish to allow for
     // such URLs to be used.
     final versions = await _scheduler.schedule(id.toRef());
-    final versionInfo = versions[id];
+    final versionInfo = versions![id];
     final packageName = id.name;
     final version = id.version;
     if (versionInfo == null) {
       throw PackageNotFoundException(
           'Package $packageName has no version $version');
     }
-    final parsedDescription = source._parseDescription(id.description);
-    final server = parsedDescription.last;
+    final parsedDescription = source._asDescription(id.description);
+    final server = parsedDescription.uri;
 
     var url = versionInfo.archiveUrl;
-    // To support old servers that has no archive_url we fall back to the
-    // hard-coded path.
-    url ??= Uri.parse('$server/packages/$packageName/versions/$version.tar.gz');
     log.io('Get package from $url.');
     log.message('Downloading ${log.bold(id.name)} ${id.version}...');
 
@@ -675,51 +797,74 @@
       var tempDir = systemCache.createTempDir();
       await extractTarGz(readBinaryFileAsSream(archivePath), tempDir);
 
-      // Remove the existing directory if it exists. This will happen if
-      // we're forcing a download to repair the cache.
-      if (dirExists(destPath)) deleteEntry(destPath);
-
       // Now that the get has succeeded, move it to the real location in the
-      // cache. This ensures that we don't leave half-busted ghost
-      // directories in the user's pub cache if a get fails.
-      renameDir(tempDir, destPath);
+      // cache.
+      //
+      // If this fails with a "directory not empty" exception we assume that
+      // another pub process has installed the same package version while we
+      // downloaded.
+      tryRenameDir(tempDir, destPath);
     });
   }
 
-  /// When an error occurs trying to read something about [package] from [url],
+  /// When an error occurs trying to read something about [package] from [hostedUrl],
   /// this tries to translate into a more user friendly error message.
   ///
   /// Always throws an error, either the original one or a better one.
-  void _throwFriendlyError(
-    error,
+  Never _throwFriendlyError(
+    Exception error,
     StackTrace stackTrace,
     String package,
-    Uri url,
+    Uri hostedUrl,
   ) {
     if (error is PubHttpException) {
       if (error.response.statusCode == 404) {
         throw PackageNotFoundException(
-            'could not find package $package at $url',
+            'could not find package $package at $hostedUrl',
             innerError: error,
             innerTrace: stackTrace);
       }
 
       fail(
           '${error.response.statusCode} ${error.response.reasonPhrase} trying '
-          'to find package $package at $url.',
+          'to find package $package at $hostedUrl.',
           error,
           stackTrace);
     } else if (error is io.SocketException) {
-      fail('Got socket error trying to find package $package at $url.', error,
-          stackTrace);
+      fail('Got socket error trying to find package $package at $hostedUrl.',
+          error, stackTrace);
     } else if (error is io.TlsException) {
-      fail('Got TLS error trying to find package $package at $url.', error,
-          stackTrace);
+      fail('Got TLS error trying to find package $package at $hostedUrl.',
+          error, stackTrace);
+    } else if (error is AuthenticationException) {
+      String? hint;
+      var message = 'authentication failed';
+
+      assert(error.statusCode == 401 || error.statusCode == 403);
+      if (error.statusCode == 401) {
+        hint = '$hostedUrl package repository requested authentication!\n'
+            'You can provide credentials using:\n'
+            '    pub token add $hostedUrl';
+      }
+      if (error.statusCode == 403) {
+        hint = 'Insufficient permissions to the resource at the $hostedUrl '
+            'package repository.\nYou can modify credentials using:\n'
+            '    pub token add $hostedUrl';
+        message = 'authorization failed';
+      }
+
+      if (error.serverMessage?.isNotEmpty == true && hint != null) {
+        hint += '\n${error.serverMessage}';
+      }
+
+      throw PackageNotFoundException(message, hint: hint);
     } else if (error is FormatException) {
       throw PackageNotFoundException(
-          'Got badly formatted response trying to find package $package at $url',
-          innerError: error,
-          innerTrace: stackTrace);
+        'Got badly formatted response trying to find package $package at $hostedUrl',
+        innerError: error,
+        innerTrace: stackTrace,
+        hint: 'Check that "$hostedUrl" is a valid package repository.',
+      );
     } else {
       // Otherwise re-throw the original exception.
       throw error;
@@ -755,7 +900,7 @@
     return replace(
       url,
       RegExp(r'[<>:"\\/|?*%]'),
-      (match) => '%${match[0].codeUnitAt(0)}',
+      (match) => '%${match[0]!.codeUnitAt(0)}',
     );
   }
 
@@ -786,7 +931,7 @@
   }
 
   /// Returns the server URL for [description].
-  Uri _serverFor(description) => source._parseDescription(description).last;
+  Uri _serverFor(description) => source._asDescription(description).uri;
 
   /// Enables speculative prefetching of dependencies of packages queried with
   /// [getVersions].
@@ -812,9 +957,12 @@
 
   /// Gets the list of all versions of [ref] that are in the system cache.
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
-    var parsed = source._parseDescription(ref.description);
-    var server = parsed.last;
+  Future<List<PackageId>> doGetVersions(
+    PackageRef ref,
+    Duration? maxAge,
+  ) async {
+    var parsed = source._asDescription(ref.description);
+    var server = parsed.uri;
     log.io('Finding versions of ${ref.name} in '
         '$systemCacheRoot/${_urlToDirectory(server)}');
 
@@ -834,7 +982,9 @@
     // If there are no versions in the cache, report a clearer error.
     if (versions.isEmpty) {
       throw PackageNotFoundException(
-          'could not find package ${ref.name} in cache');
+        'could not find package ${ref.name} in cache',
+        hint: 'Try again without --offline!',
+      );
     }
 
     return versions;
@@ -850,18 +1000,19 @@
   @override
   Future<Pubspec> describeUncached(PackageId id) {
     throw PackageNotFoundException(
-        '${id.name} ${id.version} is not available in your system cache');
+      '${id.name} ${id.version} is not available in cache',
+      hint: 'Try again without --offline!',
+    );
   }
 
   @override
-  Future<PackageStatus> status(PackageId id, {Duration maxAge}) async {
+  Future<PackageStatus> status(PackageId id, {Duration? maxAge}) async {
     // Do we have a cached version response on disk?
     final versionListing = await _cachedVersionListingResponse(id.toRef());
 
     if (versionListing == null) {
       return PackageStatus();
     }
-    final listing = versionListing[id];
     // If we don't have the specific version we return the empty response.
     //
     // This should not happen. But in production we want to avoid a crash, since
@@ -869,7 +1020,6 @@
     //
     // TODO(sigurdm): Consider representing the non-existence of the
     // package-version in the return value.
-    assert(listing != null);
     return versionListing[id]?.status ?? PackageStatus();
   }
 }
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index b1dc26b..4679f98 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
@@ -11,6 +9,7 @@
 
 import '../exceptions.dart';
 import '../io.dart';
+import '../language_version.dart';
 import '../package_name.dart';
 import '../pubspec.dart';
 import '../source.dart';
@@ -64,7 +63,12 @@
   /// original path but resolved relative to the containing path. The
   /// "relative" key will be `true` if the original path was relative.
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(
+    String name,
+    description, {
+    String? containingPath,
+    LanguageVersion? languageVersion,
+  }) {
     if (description is! String) {
       throw FormatException('The description must be a path string.');
     }
@@ -90,7 +94,7 @@
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (description is! Map) {
       throw FormatException('The description must be a map.');
     }
@@ -126,9 +130,11 @@
   ///
   /// For the descriptions where `relative` attribute is `true`, tries to make
   /// `path` relative to the specified [containingPath].
+  ///
+  /// If [containingPath] is `null` they are serialized as absolute.
   @override
-  dynamic serializeDescription(String containingPath, description) {
-    if (description['relative']) {
+  dynamic serializeDescription(String? containingPath, description) {
+    if (description['relative'] == true && containingPath != null) {
       return {
         'path': relativePathWithPosixSeparators(
             p.relative(description['path'], from: containingPath)),
@@ -165,7 +171,8 @@
   BoundPathSource(this.source, this.systemCache);
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     // There's only one package ID for a given path. We just need to find the
     // version.
     var pubspec = _loadPubspec(ref);
@@ -183,10 +190,10 @@
   }
 
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) {
+  String getDirectory(PackageId id, {String? relativeFrom}) {
     return id.description['relative']
         ? p.relative(id.description['path'], from: relativeFrom)
-        : id.description['path'];
+        : id.description['path']!;
   }
 
   /// Ensures that [description] is a valid path description and returns a
diff --git a/lib/src/source/sdk.dart b/lib/src/source/sdk.dart
index aff6acc..1458d1b 100644
--- a/lib/src/source/sdk.dart
+++ b/lib/src/source/sdk.dart
@@ -2,13 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
 
 import '../exceptions.dart';
+import '../language_version.dart';
 import '../package_name.dart';
 import '../pubspec.dart';
 import '../sdk.dart';
@@ -35,7 +34,8 @@
 
   /// Parses an SDK dependency.
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description,
+      {String? containingPath, LanguageVersion? languageVersion}) {
     if (description is! String) {
       throw FormatException('The description must be an SDK name.');
     }
@@ -45,7 +45,7 @@
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (description is! String) {
       throw FormatException('The description must be an SDK name.');
     }
@@ -72,7 +72,8 @@
   BoundSdkSource(this.source, this.systemCache);
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     var pubspec = _loadPubspec(ref);
     var id = PackageId(ref.name, source, pubspec.version, ref.description);
     memoizePubspec(id, pubspec);
@@ -95,13 +96,15 @@
   /// Throws a [PackageNotFoundException] if [package]'s SDK is unavailable or
   /// doesn't contain the package.
   String _verifiedPackagePath(PackageName package) {
-    var identifier = package.description as String;
-    var sdk = sdks[identifier];
+    var identifier = package.description as String?;
+    var sdk = sdks[identifier!];
     if (sdk == null) {
       throw PackageNotFoundException('unknown SDK "$identifier"');
     } else if (!sdk.isAvailable) {
-      throw PackageNotFoundException('the ${sdk.name} SDK is not available',
-          missingSdk: sdk);
+      throw PackageNotFoundException(
+        'the ${sdk.name} SDK is not available',
+        hint: sdk.installMessage,
+      );
     }
 
     var path = sdk.packagePath(package.name);
@@ -112,7 +115,7 @@
   }
 
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) {
+  String getDirectory(PackageId id, {String? relativeFrom}) {
     try {
       return _verifiedPackagePath(id);
     } on PackageNotFoundException catch (error) {
diff --git a/lib/src/source/unknown.dart b/lib/src/source/unknown.dart
index d98d0e2..baa46ea 100644
--- a/lib/src/source/unknown.dart
+++ b/lib/src/source/unknown.dart
@@ -2,12 +2,11 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
 
+import '../language_version.dart';
 import '../package_name.dart';
 import '../pubspec.dart';
 import '../source.dart';
@@ -44,12 +43,17 @@
   int hashDescription(description) => description.hashCode;
 
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) =>
+  PackageRef parseRef(
+    String name,
+    description, {
+    String? containingPath,
+    LanguageVersion? languageVersion,
+  }) =>
       PackageRef(name, this, description);
 
   @override
   PackageId parseId(String name, Version version, description,
-          {String containingPath}) =>
+          {String? containingPath}) =>
       PackageId(name, this, version, description);
 }
 
@@ -63,7 +67,7 @@
   _BoundUnknownSource(this.source, this.systemCache);
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) =>
+  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration? maxAge) =>
       throw UnsupportedError(
           "Cannot get package versions from unknown source '${source.name}'.");
 
@@ -73,7 +77,7 @@
 
   /// Returns the directory where this package can be found locally.
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) =>
+  String getDirectory(PackageId id, {String? relativeFrom}) =>
       throw UnsupportedError(
           "Cannot find a package from an unknown source '${source.name}'.");
 }
diff --git a/lib/src/source_registry.dart b/lib/src/source_registry.dart
index 9c946eb..7e8aa84 100644
--- a/lib/src/source_registry.dart
+++ b/lib/src/source_registry.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'source.dart';
 import 'source/git.dart';
 import 'source/hosted.dart';
@@ -27,7 +25,7 @@
   ///
   /// This defaults to [hosted].
   Source get defaultSource => _default;
-  Source _default;
+  late Source _default;
 
   /// The registered sources, in name order.
   List<Source> get all {
@@ -60,7 +58,7 @@
       throw StateError('Default source $name is not in the registry');
     }
 
-    _default = _sources[name];
+    _default = _sources[name]!;
   }
 
   /// Registers a new source.
@@ -80,7 +78,7 @@
   ///
   /// Returns an [UnknownSource] if no source with that name has been
   /// registered. If [name] is null, returns the default source.
-  Source operator [](String name) {
+  Source? operator [](String? name) {
     if (name == null) return _default;
     if (_sources.containsKey(name)) return _sources[name];
     return UnknownSource(name);
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index 298e775..d6b5876 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -23,7 +21,6 @@
 import 'source/sdk.dart';
 import 'source/unknown.dart';
 import 'source_registry.dart';
-import 'utils.dart';
 
 /// The system-wide cache of downloaded packages.
 ///
@@ -38,19 +35,20 @@
 
   static String defaultDir = (() {
     if (Platform.environment.containsKey('PUB_CACHE')) {
-      return Platform.environment['PUB_CACHE'];
+      return Platform.environment['PUB_CACHE']!;
     } else if (Platform.isWindows) {
       // %LOCALAPPDATA% is preferred as the cache location over %APPDATA%, because the latter is synchronised between
       // devices when the user roams between them, whereas the former is not.
       // The default cache dir used to be in %APPDATA%, so to avoid breaking old installs,
       // we use the old dir in %APPDATA% if it exists. Else, we use the new default location
       // in %LOCALAPPDATA%.
-      var appData = Platform.environment['APPDATA'];
+      //  TODO(sigurdm): handle missing APPDATA.
+      var appData = Platform.environment['APPDATA']!;
       var appDataCacheDir = p.join(appData, 'Pub', 'Cache');
       if (dirExists(appDataCacheDir)) {
         return appDataCacheDir;
       }
-      var localAppData = Platform.environment['LOCALAPPDATA'];
+      var localAppData = Platform.environment['LOCALAPPDATA']!;
       return p.join(localAppData, 'Pub', 'Cache');
     } else {
       return '${Platform.environment['HOME']}/.pub-cache';
@@ -64,7 +62,7 @@
   final sources = SourceRegistry();
 
   /// The sources bound to this cache.
-  final _boundSources = <Source, BoundSource>{};
+  final _boundSources = <Source?, BoundSource>{};
 
   /// The built-in Git source bound to this cache.
   BoundGitSource get git => _boundSources[sources.git] as BoundGitSource;
@@ -89,7 +87,7 @@
   ///
   /// If [isOffline] is `true`, then the offline hosted source will be used.
   /// Defaults to `false`.
-  SystemCache({String rootDir, bool isOffline = false})
+  SystemCache({String? rootDir, bool isOffline = false})
       : rootDir = rootDir ?? SystemCache.defaultDir,
         tokenStore = TokenStore(dartConfigDir) {
     for (var source in sources.all) {
@@ -102,8 +100,8 @@
   }
 
   /// Returns the version of [source] bound to this cache.
-  BoundSource source(Source source) =>
-      _boundSources.putIfAbsent(source, () => source.bind(this));
+  BoundSource source(Source? source) =>
+      _boundSources.putIfAbsent(source, () => source!.bind(this));
 
   /// Loads the package identified by [id].
   ///
@@ -158,8 +156,8 @@
   /// later stable version we return a prerelease version if it exists.
   ///
   /// Returns `null`, if unable to find the package.
-  Future<PackageId> getLatest(
-    PackageName package, {
+  Future<PackageId?> getLatest(
+    PackageName? package, {
     bool allowPrereleases = false,
   }) async {
     if (package == null) {
@@ -172,21 +170,14 @@
       return null;
     }
 
-    final latest = maxAll(
-      available.map((id) => id.version),
-      allowPrereleases ? Comparable.compare : Version.prioritize,
-    );
-
+    available.sort(allowPrereleases
+        ? (x, y) => x.version.compareTo(y.version)
+        : (x, y) => Version.prioritize(x.version, y.version));
     if (package is PackageId &&
         package.version.isPreRelease &&
-        package.version > latest &&
-        !allowPrereleases) {
-      return getLatest(package, allowPrereleases: true);
+        package.version > available.last.version) {
+      available.sort((x, y) => x.version.compareTo(y.version));
     }
-
-    // There should be exactly one entry in [available] matching [latest]
-    assert(available.where((id) => id.version == latest).length == 1);
-
-    return available.firstWhere((id) => id.version == latest);
+    return available.last;
   }
 }
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index fc3984e..2a5768e 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -9,7 +9,6 @@
 import 'dart:math' as math;
 
 import 'package:crypto/crypto.dart' as crypto;
-import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:stack_trace/stack_trace.dart';
 
@@ -376,7 +375,7 @@
 
   // If we're using verbose logging, be more verbose but more accurate when
   // reporting timing information.
-  var msString = log.verbosity.isLevelVisible(log.Level.FINE)
+  var msString = log.verbosity.isLevelVisible(log.Level.fine)
       ? _padLeft(ms.toString(), 3, '0')
       : (ms ~/ 100).toString();
 
@@ -501,8 +500,7 @@
 }
 
 /// Throw a [ApplicationException] with [message].
-@alwaysThrows
-void fail(String message, [Object? innerError, StackTrace? innerTrace]) {
+Never fail(String message, [Object? innerError, StackTrace? innerTrace]) {
   if (innerError != null) {
     throw WrappedException(message, innerError, innerTrace);
   } else {
@@ -514,7 +512,7 @@
 /// failed because of invalid input data.
 ///
 /// This will report the error and cause pub to exit with [exit_codes.DATA].
-void dataError(String message) => throw DataException(message);
+Never dataError(String message) => throw DataException(message);
 
 /// Returns a UUID in v4 format as a `String`.
 ///
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index c6a7b4a..1b07560 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:meta/meta.dart';
@@ -103,7 +101,7 @@
         'Make sure your SDK constraint excludes old versions:\n'
         '\n'
         'environment:\n'
-        '  sdk: \"$newSdkConstraint\"');
+        '  sdk: "$newSdkConstraint"');
   }
 
   /// Returns whether [version1] and [version2] are pre-releases of the same version.
@@ -123,8 +121,10 @@
   /// package, in bytes. This is used to validate that it's not too big to
   /// upload to the server.
   static Future<void> runAll(
-      Entrypoint entrypoint, Future<int> packageSize, Uri serverUrl,
-      {List<String> hints, List<String> warnings, List<String> errors}) {
+      Entrypoint entrypoint, Future<int> packageSize, Uri? serverUrl,
+      {required List<String> hints,
+      required List<String> warnings,
+      required List<String> errors}) {
     var validators = [
       GitignoreValidator(entrypoint),
       PubspecValidator(entrypoint),
@@ -149,9 +149,7 @@
       PubspecTypoValidator(entrypoint),
       LeakDetectionValidator(entrypoint),
     ];
-    if (packageSize != null) {
-      validators.add(SizeValidator(entrypoint, packageSize));
-    }
+    validators.add(SizeValidator(entrypoint, packageSize));
 
     return Future.wait(validators.map((validator) => validator.validate()))
         .then((_) {
diff --git a/lib/src/validator/changelog.dart b/lib/src/validator/changelog.dart
index 2f93903..614eba2 100644
--- a/lib/src/validator/changelog.dart
+++ b/lib/src/validator/changelog.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -23,7 +21,7 @@
       final changelog = entrypoint.root.changelogPath;
 
       if (changelog == null) {
-        warnings.add('Please add a`CHANGELOG.md` to your package. '
+        warnings.add('Please add a `CHANGELOG.md` to your package. '
             'See https://dart.dev/tools/pub/publishing#important-files.');
         return;
       }
@@ -43,9 +41,6 @@
         warnings.add('$changelog contains invalid UTF-8.\n'
             'This will cause it to be displayed incorrectly on '
             'the Pub site (https://pub.dev).');
-      }
-
-      if (contents == null) {
         // Failed to decode contents, so there's nothing else to check.
         return;
       }
diff --git a/lib/src/validator/compiled_dartdoc.dart b/lib/src/validator/compiled_dartdoc.dart
index 670862c..3f3376a 100644
--- a/lib/src/validator/compiled_dartdoc.dart
+++ b/lib/src/validator/compiled_dartdoc.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
diff --git a/lib/src/validator/dependency.dart b/lib/src/validator/dependency.dart
index c9f221a..5d158d1 100644
--- a/lib/src/validator/dependency.dart
+++ b/lib/src/validator/dependency.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -223,8 +221,9 @@
       constraint = '^${(dep.constraint as VersionRange).min}';
     } else {
       constraint = '"${dep.constraint} '
-          '<${(dep.constraint as VersionRange).min.nextBreaking}"';
+          '<${(dep.constraint as VersionRange).min!.nextBreaking}"';
     }
+    // TODO: Handle the case where `dep.constraint.min` is null.
 
     warnings
         .add('Your dependency on "${dep.name}" should have an upper bound. For '
@@ -240,7 +239,7 @@
   void _warnAboutPrerelease(String dependencyName, VersionRange constraint) {
     final packageVersion = entrypoint.root.version;
     if (constraint.min != null &&
-        constraint.min.isPreRelease &&
+        constraint.min!.isPreRelease &&
         !packageVersion.isPreRelease) {
       warnings.add('Packages dependent on a pre-release of another package '
           'should themselves be published as a pre-release version. '
diff --git a/lib/src/validator/dependency_override.dart b/lib/src/validator/dependency_override.dart
index 8942be8..24e4e84 100644
--- a/lib/src/validator/dependency_override.dart
+++ b/lib/src/validator/dependency_override.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:collection/collection.dart';
diff --git a/lib/src/validator/deprecated_fields.dart b/lib/src/validator/deprecated_fields.dart
index 150d599..e1aee4f 100644
--- a/lib/src/validator/deprecated_fields.dart
+++ b/lib/src/validator/deprecated_fields.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../entrypoint.dart';
diff --git a/lib/src/validator/directory.dart b/lib/src/validator/directory.dart
index 9828f64..409e243 100644
--- a/lib/src/validator/directory.dart
+++ b/lib/src/validator/directory.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
diff --git a/lib/src/validator/executable.dart b/lib/src/validator/executable.dart
index e9881d2..2163e8c 100644
--- a/lib/src/validator/executable.dart
+++ b/lib/src/validator/executable.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/validator/flutter_constraint.dart b/lib/src/validator/flutter_constraint.dart
index 74c2a2e..d689a22 100644
--- a/lib/src/validator/flutter_constraint.dart
+++ b/lib/src/validator/flutter_constraint.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/validator/flutter_plugin_format.dart b/lib/src/validator/flutter_plugin_format.dart
index 87fd3df..6f00f06 100644
--- a/lib/src/validator/flutter_plugin_format.dart
+++ b/lib/src/validator/flutter_plugin_format.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index df01214..4fb49b1 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -31,18 +29,27 @@
         '--exclude-standard',
         '--recurse-submodules'
       ], workingDir: entrypoint.root.dir);
+      final root = git.repoRoot(entrypoint.root.dir) ?? entrypoint.root.dir;
+      var beneath = p.posix.joinAll(
+          p.split(p.normalize(p.relative(entrypoint.root.dir, from: root))));
+      if (beneath == './') {
+        beneath = '';
+      }
       String resolve(String path) {
         if (Platform.isWindows) {
-          return p.joinAll([entrypoint.root.dir, ...p.posix.split(path)]);
+          return p.joinAll([root, ...p.posix.split(path)]);
         }
-        return p.join(entrypoint.root.dir, path);
+        return p.join(root, path);
       }
 
       final unignoredByGitignore = Ignore.listFiles(
+        beneath: beneath,
         listDir: (dir) {
           var contents = Directory(resolve(dir)).listSync();
-          return contents.map((entity) => p.posix.joinAll(
-              p.split(p.relative(entity.path, from: entrypoint.root.dir))));
+          return contents
+              .where((e) => !(linkExists(e.path) && dirExists(e.path)))
+              .map((entity) => p.posix
+                  .joinAll(p.split(p.relative(entity.path, from: root))));
         },
         ignoreForDir: (dir) {
           final gitIgnore = resolve('$dir/.gitignore');
@@ -52,15 +59,19 @@
           return rules.isEmpty ? null : Ignore(rules);
         },
         isDir: (dir) => dirExists(resolve(dir)),
-      ).toSet();
-
+      ).map((file) {
+        final relative = p.relative(resolve(file), from: entrypoint.root.dir);
+        return Platform.isWindows
+            ? p.posix.joinAll(p.split(relative))
+            : relative;
+      }).toSet();
       final ignoredFilesCheckedIn = checkedIntoGit
           .where((file) => !unignoredByGitignore.contains(file))
           .toList();
 
       if (ignoredFilesCheckedIn.isNotEmpty) {
         warnings.add('''
-${ignoredFilesCheckedIn.length} checked in ${pluralize('file', ignoredFilesCheckedIn.length)} ${ignoredFilesCheckedIn.length == 1 ? 'is' : 'are'} ignored by a `.gitignore`.
+${ignoredFilesCheckedIn.length} checked-in ${pluralize('file', ignoredFilesCheckedIn.length)} ${ignoredFilesCheckedIn.length == 1 ? 'is' : 'are'} ignored by a `.gitignore`.
 Previous versions of Pub would include those in the published package.
 
 Consider adjusting your `.gitignore` files to not ignore those files, and if you do not wish to
diff --git a/lib/src/validator/language_version.dart b/lib/src/validator/language_version.dart
index 6d71b22..e52a92a 100644
--- a/lib/src/validator/language_version.dart
+++ b/lib/src/validator/language_version.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:analyzer/dart/ast/ast.dart';
diff --git a/lib/src/validator/leak_detection.dart b/lib/src/validator/leak_detection.dart
index 53bf31d..7a398d4 100644
--- a/lib/src/validator/leak_detection.dart
+++ b/lib/src/validator/leak_detection.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -75,7 +73,7 @@
       errors.addAll(leaks.take(2).map((leak) => leak.toString()));
 
       final files = leaks
-          .map((leak) => leak.span.sourceUrl.toFilePath(windows: false))
+          .map((leak) => leak.span.sourceUrl!.toFilePath(windows: false))
           .toSet()
           .toList(growable: false)
         ..sort();
@@ -155,8 +153,8 @@
   final List<String> testsWithNoLeaks;
 
   LeakPattern._({
-    @required this.kind,
-    @required String pattern,
+    required this.kind,
+    required String pattern,
     Iterable<Pattern> allowed = const <Pattern>[],
     Map<int, double> entropyThresholds = const <int, double>{},
     Iterable<String> testsWithLeaks = const <String>[],
@@ -177,17 +175,17 @@
   Iterable<LeakMatch> findPossibleLeaks(String file, String content) sync* {
     final source = SourceFile.fromString(content, url: file);
     for (final m in _pattern.allMatches(content)) {
-      if (_allowed.any((s) => m.group(0).contains(s))) {
+      if (_allowed.any((s) => m.group(0)!.contains(s))) {
         continue;
       }
       if (_entropyThresholds.entries
-          .any((entry) => _entropy(m.group(entry.key)) < entry.value)) {
+          .any((entry) => _entropy(m.group(entry.key)!) < entry.value)) {
         continue;
       }
 
       yield LeakMatch(
         this,
-        source.span(m.start, m.start + m.group(0).length),
+        source.span(m.start, m.start + m.group(0)!.length),
       );
     }
   }
@@ -554,7 +552,7 @@
       '$_pemRequireLineBreak$_pemWSP',
       '=(?:(?:[a-zA-Z0-9+/]$_pemWSP){4})',
       _pemEnd('PGP PRIVATE KEY BLOCK'),
-    ].join(''),
+    ].join(),
     testsWithLeaks: [
       '''
 -----BEGIN PGP PRIVATE KEY BLOCK-----
@@ -607,7 +605,7 @@
       _pemRequireLineBreak,
       // Allow arbitrary whitespace and escaped line breaks
       _pemWSP,
-    ].join('');
+    ].join();
 
 String _pemBase64Block() => [
       // Require base64 character in blocks of 4, allow arbirary whitespace
@@ -629,10 +627,10 @@
         // character, allow arbirary whitespace and escaped line breaks
         // in between.
         '(?:[a-zA-Z0-9+/]$_pemWSP){2}(?:=$_pemWSP){2}',
-      ].join(''),
+      ].join(),
       // End blocks
       '))?',
-    ].join('');
+    ].join();
 
 String _pemEnd(String label) => [
       // Require \n, \r, \\r, or \\n, backslash escaping is allowed if the key
@@ -641,10 +639,10 @@
       // Allow arbitrary whitespace and escaped line breaks
       _pemWSP,
       '-----END $label-----',
-    ].join('');
+    ].join();
 
 String _pemKeyFormat(String label) => [
       _pemBegin(label),
       _pemBase64Block(),
       _pemEnd(label),
-    ].join('');
+    ].join();
diff --git a/lib/src/validator/license.dart b/lib/src/validator/license.dart
index edca16d..1a2c68a 100644
--- a/lib/src/validator/license.dart
+++ b/lib/src/validator/license.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
diff --git a/lib/src/validator/name.dart b/lib/src/validator/name.dart
index d484ed8..07d945c 100644
--- a/lib/src/validator/name.dart
+++ b/lib/src/validator/name.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
@@ -74,7 +72,7 @@
       builder
         ..write(source.substring(lastMatchEnd, match.start + 1))
         ..write('_')
-        ..write(match.group(1).toLowerCase());
+        ..write(match.group(1)!.toLowerCase());
       lastMatchEnd = match.end;
     }
     builder.write(source.substring(lastMatchEnd));
diff --git a/lib/src/validator/null_safety_mixed_mode.dart b/lib/src/validator/null_safety_mixed_mode.dart
index 401043e..42f9f77 100644
--- a/lib/src/validator/null_safety_mixed_mode.dart
+++ b/lib/src/validator/null_safety_mixed_mode.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/validator/pubspec.dart b/lib/src/validator/pubspec.dart
index 20a7174..2a5e052 100644
--- a/lib/src/validator/pubspec.dart
+++ b/lib/src/validator/pubspec.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/validator/pubspec_field.dart b/lib/src/validator/pubspec_field.dart
index 482d696..8a5c23e 100644
--- a/lib/src/validator/pubspec_field.dart
+++ b/lib/src/validator/pubspec_field.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../entrypoint.dart';
diff --git a/lib/src/validator/pubspec_typo.dart b/lib/src/validator/pubspec_typo.dart
index 2e10ed6..9b04292 100644
--- a/lib/src/validator/pubspec_typo.dart
+++ b/lib/src/validator/pubspec_typo.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import '../entrypoint.dart';
 import '../levenshtein.dart';
 import '../validator.dart';
@@ -16,8 +14,6 @@
   Future validate() async {
     final fields = entrypoint.root.pubspec.fields;
 
-    if (fields == null) return;
-
     /// Limit the number of typo warnings so as not to drown out the other
     /// warnings
     var warningCount = 0;
@@ -73,4 +69,6 @@
   'publish_to',
   'false_secrets',
   'flutter',
+  'screenshots',
+  'platforms',
 ];
diff --git a/lib/src/validator/readme.dart b/lib/src/validator/readme.dart
index 14d6b5e..85de479 100644
--- a/lib/src/validator/readme.dart
+++ b/lib/src/validator/readme.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
diff --git a/lib/src/validator/relative_version_numbering.dart b/lib/src/validator/relative_version_numbering.dart
index 6609074..eb4246c 100644
--- a/lib/src/validator/relative_version_numbering.dart
+++ b/lib/src/validator/relative_version_numbering.dart
@@ -2,10 +2,10 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
+import 'package:collection/collection.dart' show IterableExtension;
+
 import '../entrypoint.dart';
 import '../exceptions.dart';
 import '../null_safety_analysis.dart';
@@ -18,7 +18,7 @@
   static const String semverUrl =
       'https://dart.dev/tools/pub/versioning#semantic-versions';
 
-  final Uri _server;
+  final Uri? _server;
 
   RelativeVersionNumberingValidator(Entrypoint entrypoint, this._server)
       : super(entrypoint);
@@ -35,9 +35,8 @@
       existingVersions = [];
     }
     existingVersions.sort((a, b) => a.version.compareTo(b.version));
-    final previousVersion = existingVersions.lastWhere(
-        (id) => id.version < entrypoint.root.version,
-        orElse: () => null);
+    final previousVersion = existingVersions
+        .lastWhereOrNull((id) => id.version < entrypoint.root.version);
     if (previousVersion == null) return;
 
     final previousPubspec =
diff --git a/lib/src/validator/sdk_constraint.dart b/lib/src/validator/sdk_constraint.dart
index 68de6bf..ff25e53 100644
--- a/lib/src/validator/sdk_constraint.dart
+++ b/lib/src/validator/sdk_constraint.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -52,7 +50,7 @@
             'Expand it manually instead:\n'
             '\n'
             'environment:\n'
-            '  sdk: \"$dartConstraintWithoutCaret\"');
+            '  sdk: "$dartConstraintWithoutCaret"');
       }
 
       if (dartConstraint.max == null) {
diff --git a/lib/src/validator/size.dart b/lib/src/validator/size.dart
index f873dfe..e989518 100644
--- a/lib/src/validator/size.dart
+++ b/lib/src/validator/size.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:math' as math;
 
@@ -12,7 +10,7 @@
 import '../validator.dart';
 
 /// The maximum size of the package to upload (100 MB).
-const _MAX_SIZE = 100 * 1024 * 1024;
+const _maxSize = 100 * 1024 * 1024;
 
 /// A validator that validates that a package isn't too big.
 class SizeValidator extends Validator {
@@ -23,7 +21,7 @@
   @override
   Future validate() {
     return packageSize.then((size) {
-      if (size <= _MAX_SIZE) return;
+      if (size <= _maxSize) return;
       var sizeInMb = (size / math.pow(2, 20)).toStringAsPrecision(4);
       // Current implementation of Package.listFiles skips hidden files
       var ignoreExists = fileExists(entrypoint.root.path('.gitignore'));
diff --git a/lib/src/validator/strict_dependencies.dart b/lib/src/validator/strict_dependencies.dart
index 33e1210..881c55c 100644
--- a/lib/src/validator/strict_dependencies.dart
+++ b/lib/src/validator/strict_dependencies.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:analyzer/dart/ast/ast.dart';
@@ -47,9 +45,9 @@
       }
 
       for (var directive in directives) {
-        Uri url;
+        Uri? url;
         try {
-          url = Uri.parse(directive.uri.stringValue);
+          url = Uri.parse(directive.uri.stringValue!);
         } on FormatException catch (_) {
           // Ignore a format exception. [url] will be null, and we'll emit an
           // "Invalid URL" warning below.
diff --git a/pubspec.yaml b/pubspec.yaml
index 23fc0c8..a7d9bb4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -6,10 +6,10 @@
 dependencies:
   # Note: Pub's test infrastructure assumes that any dependencies used in tests
   # will be hosted dependencies.
-  analyzer: ^1.5.0
+  analyzer: ^2.7.0
   args: ^2.1.0
   async: ^2.6.1
-  cli_util: ^0.3.0
+  cli_util: ^0.3.5
   collection: ^1.15.0
   crypto: ^3.0.1
   frontend_server_client: ^2.0.0
@@ -18,16 +18,17 @@
   meta: ^1.3.0
   oauth2: ^2.0.0
   path: ^1.8.0
-  pedantic: ^1.11.0
   pool: ^1.5.0
-  pub_semver: ^2.0.0
+  pub_semver: ^2.1.0
   shelf: ^1.1.1
   source_span: ^1.8.1
   stack_trace: ^1.10.0
+  usage: ^4.0.2
   yaml: ^3.1.0
   yaml_edit: ^2.0.0
 
 dev_dependencies:
+  lints: ^1.0.1
   shelf_test_handler: ^2.0.0
   test: ^1.17.3
   test_descriptor: ^2.0.0
diff --git a/test/add/common/add_test.dart b/test/add/common/add_test.dart
index 51d5b71..82e098b 100644
--- a/test/add/common/add_test.dart
+++ b/test/add/common/add_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io' show File;
 
 import 'package:path/path.dart' as p;
@@ -15,7 +13,7 @@
 
 void main() {
   test('URL encodes the package name', () async {
-    await serveNoPackages();
+    await servePackages();
 
     await d.appDir({}).create();
 
@@ -39,49 +37,48 @@
   });
 
   group('normally', () {
-    test('fails if extra arguments are passed', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.2');
-      });
-
-      await d.dir(appPath, [
-        d.pubspec({'name': 'myapp'})
-      ]).create();
-
-      await pubAdd(
-          args: ['foo', '^1.2.2'],
-          exitCode: exit_codes.USAGE,
-          error: contains('Takes only a single argument.'));
-
-      await d.dir(appPath, [
-        d.pubspec({
-          'name': 'myapp',
-        }),
-        d.nothing('.dart_tool/package_config.json'),
-        d.nothing('pubspec.lock'),
-        d.nothing('.packages'),
-      ]).validate();
-    });
-
     test('adds a package from a pub server', () async {
-      await servePackages((builder) => builder.serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
 
       await d.appDir({}).create();
 
       await pubAdd(args: ['foo:1.2.3']);
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
       await d.appDir({'foo': '1.2.3'}).validate();
     });
 
+    test('adds multiple package from a pub server', () async {
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
+      server.serve('bar', '1.1.0');
+      server.serve('baz', '2.5.3');
+
+      await d.appDir({}).create();
+
+      await pubAdd(args: ['foo:1.2.3', 'bar:1.1.0', 'baz:2.5.3']);
+
+      await d.cacheDir(
+          {'foo': '1.2.3', 'bar': '1.1.0', 'baz': '2.5.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+        d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+        d.packageConfigEntry(name: 'baz', version: '2.5.3'),
+      ]).validate();
+      await d
+          .appDir({'foo': '1.2.3', 'bar': '1.1.0', 'baz': '2.5.3'}).validate();
+    });
+
     test(
         'does not remove empty dev_dependencies while adding to normal dependencies',
         () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3');
-        builder.serve('foo', '1.2.2');
-      });
+      await servePackages()
+        ..serve('foo', '1.2.3')
+        ..serve('foo', '1.2.2');
 
       await d.dir(appPath, [
         d.file('pubspec.yaml', '''
@@ -98,7 +95,9 @@
       await pubAdd(args: ['foo:1.2.3']);
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
 
       await d.dir(appPath, [
         d.pubspec({
@@ -111,7 +110,8 @@
 
     test('dry run does not actually add the package or modify the pubspec',
         () async {
-      await servePackages((builder) => builder.serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
 
       await d.appDir({}).create();
 
@@ -133,7 +133,8 @@
     test(
         'adds a package from a pub server even when dependencies key does not exist',
         () async {
-      await servePackages((builder) => builder.serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
 
       await d.dir(appPath, [
         d.pubspec({'name': 'myapp'})
@@ -142,16 +143,17 @@
       await pubAdd(args: ['foo:1.2.3']);
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
       await d.appDir({'foo': '1.2.3'}).validate();
     });
 
     group('warns user to use pub upgrade if package exists', () {
       test('if package is added without a version constraint', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.appDir({'foo': '1.2.2'}).create();
 
@@ -166,10 +168,9 @@
       });
 
       test('if package is added with a specific version constraint', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.appDir({'foo': '1.2.2'}).create();
 
@@ -184,10 +185,9 @@
       });
 
       test('if package is added with a version constraint range', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.appDir({'foo': '1.2.2'}).create();
 
@@ -203,10 +203,9 @@
     });
 
     test('removes dev_dependency and add to normal dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3');
-        builder.serve('foo', '1.2.2');
-      });
+      await servePackages()
+        ..serve('foo', '1.2.3')
+        ..serve('foo', '1.2.2');
 
       await d.dir(appPath, [
         d.file('pubspec.yaml', '''
@@ -227,7 +226,9 @@
               'adding it to dependencies instead.'));
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
 
       await d.dir(appPath, [
         d.pubspec({
@@ -239,10 +240,9 @@
 
     group('dependency override', () {
       test('passes if package does not specify a range', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -255,7 +255,9 @@
         await pubAdd(args: ['foo']);
 
         await d.cacheDir({'foo': '1.2.2'}).validate();
-        await d.appPackagesFile({'foo': '1.2.2'}).validate();
+        await d.appPackageConfigFile([
+          d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+        ]).validate();
         await d.dir(appPath, [
           d.pubspec({
             'name': 'myapp',
@@ -266,9 +268,8 @@
       });
 
       test('passes if constraint matches git dependency override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.3');
 
         await d.git('foo.git',
             [d.libDir('foo'), d.libPubspec('foo', '1.2.3')]).create();
@@ -297,9 +298,8 @@
       });
 
       test('passes if constraint matches path dependency override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.2');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.2');
         await d.dir(
             'foo', [d.libDir('foo'), d.libPubspec('foo', '1.2.2')]).create();
 
@@ -327,9 +327,8 @@
       });
 
       test('fails with bad version constraint', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.3');
 
         await d.dir(appPath, [
           d.pubspec({'name': 'myapp', 'dependencies': {}})
@@ -350,10 +349,9 @@
       });
 
       test('fails if constraint does not match override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -383,9 +381,8 @@
       });
 
       test('fails if constraint matches git dependency override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.3');
 
         await d.git('foo.git',
             [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
@@ -423,9 +420,8 @@
 
       test('fails if constraint does not match path dependency override',
           () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.2');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.2');
         await d.dir(
             'foo', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
 
@@ -464,7 +460,8 @@
 
   group('--dev', () {
     test('--dev adds packages to dev_dependencies instead', () async {
-      await servePackages((builder) => builder.serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
 
       await d.dir(appPath, [
         d.pubspec({'name': 'myapp', 'dev_dependencies': {}})
@@ -472,7 +469,9 @@
 
       await pubAdd(args: ['--dev', 'foo:1.2.3']);
 
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
 
       await d.dir(appPath, [
         d.pubspec({
@@ -484,10 +483,9 @@
 
     group('warns user to use pub upgrade if package exists', () {
       test('if package is added without a version constraint', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -512,10 +510,9 @@
       });
 
       test('if package is added with a specific version constraint', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -540,10 +537,9 @@
       });
 
       test('if package is added with a version constraint range', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -570,10 +566,9 @@
 
     group('dependency override', () {
       test('passes if package does not specify a range', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -586,7 +581,9 @@
         await pubAdd(args: ['foo', '--dev']);
 
         await d.cacheDir({'foo': '1.2.2'}).validate();
-        await d.appPackagesFile({'foo': '1.2.2'}).validate();
+        await d.appPackageConfigFile([
+          d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+        ]).validate();
         await d.dir(appPath, [
           d.pubspec({
             'name': 'myapp',
@@ -597,10 +594,8 @@
       });
 
       test('passes if constraint is git dependency', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-        });
-
+        final server = await servePackages();
+        server.serve('foo', '1.2.3');
         await d.git('foo.git',
             [d.libDir('foo'), d.libPubspec('foo', '1.2.3')]).create();
 
@@ -628,9 +623,8 @@
       });
 
       test('passes if constraint matches path dependency override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.2');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.2');
         await d.dir(
             'foo', [d.libDir('foo'), d.libPubspec('foo', '1.2.2')]).create();
 
@@ -658,10 +652,9 @@
       });
 
       test('fails if constraint does not match override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-          builder.serve('foo', '1.2.2');
-        });
+        await servePackages()
+          ..serve('foo', '1.2.3')
+          ..serve('foo', '1.2.2');
 
         await d.dir(appPath, [
           d.pubspec({
@@ -691,9 +684,8 @@
       });
 
       test('fails if constraint matches git dependency override', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.3');
 
         await d.git('foo.git',
             [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
@@ -731,9 +723,9 @@
 
       test('fails if constraint does not match path dependency override',
           () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.2');
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.2');
+
         await d.dir(
             'foo', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
 
@@ -772,10 +764,9 @@
     test(
         'prints information saying that package is already a dependency if it '
         'already exists and exits with a usage exception', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3');
-        builder.serve('foo', '1.2.2');
-      });
+      await servePackages()
+        ..serve('foo', '1.2.3')
+        ..serve('foo', '1.2.2');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -807,9 +798,8 @@
 
   /// Differs from the previous test because this tests YAML in flow format.
   test('adds to empty ', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.file('pubspec.yaml', '''
@@ -827,10 +817,9 @@
   });
 
   test('preserves comments', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0');
-      builder.serve('foo', '1.0.0');
-    });
+    await servePackages()
+      ..serve('bar', '1.0.0')
+      ..serve('foo', '1.0.0');
 
     await d.dir(appPath, [
       d.file('pubspec.yaml', '''
diff --git a/test/add/common/invalid_options.dart b/test/add/common/invalid_options.dart
index 17262d2..f5bf23d 100644
--- a/test/add/common/invalid_options.dart
+++ b/test/add/common/invalid_options.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -41,12 +39,10 @@
   test('cannot use both --path and --host-<option> flags', () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveNoPackages();
-    globalPackageServer.serveErrors();
+    (await servePackages()).serveErrors();
 
-    final server = await PackageServer.start((builder) {
-      builder.serve('foo', '1.2.3');
-    });
+    final server = await startPackageServer();
+    server.serve('foo', '1.2.3');
 
     await d
         .dir('bar', [d.libDir('bar'), d.libPubspec('foo', '0.0.1')]).create();
@@ -78,12 +74,10 @@
   test('cannot use both --hosted-url and --git-<option> flags', () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveNoPackages();
-    globalPackageServer.serveErrors();
+    (await servePackages()).serveErrors();
 
-    final server = await PackageServer.start((builder) {
-      builder.serve('foo', '1.2.3');
-    });
+    final server = await startPackageServer();
+    server.serve('foo', '1.2.3');
 
     ensureGit();
 
diff --git a/test/add/common/version_constraint_test.dart b/test/add/common/version_constraint_test.dart
index 22df8bc..f058082 100644
--- a/test/add/common/version_constraint_test.dart
+++ b/test/add/common/version_constraint_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -12,86 +10,96 @@
 
 void main() {
   test('allows empty version constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '0.2.3');
-      builder.serve('foo', '1.0.1');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '2.0.0-dev');
-      builder.serve('foo', '1.3.4-dev');
-    });
+    await servePackages()
+      ..serve('foo', '0.2.3')
+      ..serve('foo', '1.0.1')
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '2.0.0-dev')
+      ..serve('foo', '1.3.4-dev');
 
     await d.appDir({}).create();
 
     await pubAdd(args: ['foo']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': '^1.2.3'}).validate();
   });
 
   test('allows specific version constraint', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({}).create();
 
     await pubAdd(args: ['foo:1.2.3']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': '1.2.3'}).validate();
   });
 
   test('allows specific pre-release version constraint', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3-dev'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3-dev');
 
     await d.appDir({}).create();
 
     await pubAdd(args: ['foo:1.2.3-dev']);
 
     await d.cacheDir({'foo': '1.2.3-dev'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3-dev'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3-dev'),
+    ]).validate();
     await d.appDir({'foo': '1.2.3-dev'}).validate();
   });
 
   test('allows the "any" version constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '0.2.3');
-      builder.serve('foo', '1.0.1');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '2.0.0-dev');
-      builder.serve('foo', '1.3.4-dev');
-    });
+    await servePackages()
+      ..serve('foo', '0.2.3')
+      ..serve('foo', '1.0.1')
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '2.0.0-dev')
+      ..serve('foo', '1.3.4-dev');
 
     await d.appDir({}).create();
 
     await pubAdd(args: ['foo:any']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': 'any'}).validate();
   });
 
   test('allows version constraint range', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({}).create();
 
     await pubAdd(args: ['foo:>1.2.0 <2.0.0']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': '>1.2.0 <2.0.0'}).validate();
   });
 
   test(
       'empty constraint allows it to choose the latest version not in conflict',
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '0.1.0');
-      builder.serve('foo', '1.2.3', deps: {'bar': '2.0.4'});
-      builder.serve('bar', '2.0.3');
-      builder.serve('bar', '2.0.4');
-    });
+    await servePackages()
+      ..serve('foo', '0.1.0')
+      ..serve('foo', '1.2.3', deps: {'bar': '2.0.4'})
+      ..serve('bar', '2.0.3')
+      ..serve('bar', '2.0.4');
 
     await d.appDir({'bar': '2.0.3'}).create();
 
@@ -100,12 +108,16 @@
     await d.appDir({'foo': '^0.1.0', 'bar': '2.0.3'}).validate();
 
     await d.cacheDir({'foo': '0.1.0', 'bar': '2.0.3'}).validate();
-    await d.appPackagesFile({'foo': '0.1.0', 'bar': '2.0.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '0.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.3'),
+    ]).validate();
   });
 
   group('does not update pubspec if no available version found', () {
     test('simple', () async {
-      await servePackages((builder) => builder.serve('foo', '1.0.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.0.3');
 
       await d.appDir({}).create();
 
@@ -126,11 +138,10 @@
     });
 
     test('transitive', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3', deps: {'bar': '2.0.4'});
-        builder.serve('bar', '2.0.3');
-        builder.serve('bar', '2.0.4');
-      });
+      await servePackages()
+        ..serve('foo', '1.2.3', deps: {'bar': '2.0.4'})
+        ..serve('bar', '2.0.3')
+        ..serve('bar', '2.0.4');
 
       await d.appDir({'bar': '2.0.3'}).create();
 
diff --git a/test/add/common/version_resolution_test.dart b/test/add/common/version_resolution_test.dart
index a6ad690..0f44d2b 100644
--- a/test/add/common/version_resolution_test.dart
+++ b/test/add/common/version_resolution_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -15,10 +13,10 @@
   test('unlocks transitive dependencies', () async {
     /// The server used to only have the foo v3.2.1 as the latest,
     /// so pub get will create a pubspec.lock to foo 3.2.1
-    await servePackages((builder) {
-      builder.serve('foo', '3.2.1');
-      builder.serve('bar', '1.0.0', deps: {'foo': '^3.2.1'});
-    });
+    final server = await servePackages();
+
+    server.serve('foo', '3.2.1');
+    server.serve('bar', '1.0.0', deps: {'foo': '^3.2.1'});
 
     await d.appDir({'bar': '1.0.0'}).create();
     await pubGet();
@@ -26,66 +24,69 @@
     /// foo's package creator releases a newer version of foo, and we
     /// want to test that this is what the user gets when they run
     /// pub add foo.
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '3.5.0');
-      builder.serve('foo', '3.1.0');
-      builder.serve('foo', '2.5.0');
-    });
+    server.serve('foo', '3.5.0');
+    server.serve('foo', '3.1.0');
+    server.serve('foo', '2.5.0');
 
     await pubAdd(args: ['foo']);
 
     await d.appDir({'foo': '^3.5.0', 'bar': '1.0.0'}).validate();
     await d.cacheDir({'foo': '3.5.0', 'bar': '1.0.0'}).validate();
-    await d.appPackagesFile({'foo': '3.5.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '3.5.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('chooses the appropriate version to not break other dependencies',
       () async {
     /// The server used to only have the foo v3.2.1 as the latest,
     /// so pub get will create a pubspec.lock to foo 3.2.1
-    await servePackages((builder) {
-      builder.serve('foo', '3.2.1');
-      builder.serve('bar', '1.0.0', deps: {'foo': '^3.2.1'});
-    });
+    final server = await servePackages();
+
+    server.serve('foo', '3.2.1');
+    server.serve('bar', '1.0.0', deps: {'foo': '^3.2.1'});
 
     await d.appDir({'bar': '1.0.0'}).create();
     await pubGet();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '4.0.0');
-      builder.serve('foo', '2.0.0');
-    });
+    server.serve('foo', '4.0.0');
+    server.serve('foo', '2.0.0');
 
     await pubAdd(args: ['foo']);
 
     await d.appDir({'foo': '^3.2.1', 'bar': '1.0.0'}).validate();
     await d.cacheDir({'foo': '3.2.1', 'bar': '1.0.0'}).validate();
-    await d.appPackagesFile({'foo': '3.2.1', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '3.2.1'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('may upgrade other packages if they allow a later version to be chosen',
       () async {
     /// The server used to only have the foo v3.2.1 as the latest,
     /// so pub get will create a pubspec.lock to foo 3.2.1
-    await servePackages((builder) {
-      builder.serve('foo', '3.2.1');
-      builder.serve('bar', '1.0.0', deps: {'foo': '^3.2.1'});
-    });
+    final server = await servePackages();
+
+    server.serve('foo', '3.2.1');
+    server.serve('bar', '1.0.0', deps: {'foo': '^3.2.1'});
 
     await d.appDir({'bar': '^1.0.0'}).create();
     await pubGet();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '5.0.0');
-      builder.serve('foo', '4.0.0');
-      builder.serve('foo', '2.0.0');
-      builder.serve('bar', '1.5.0', deps: {'foo': '^4.0.0'});
-    });
+    server.serve('foo', '5.0.0');
+    server.serve('foo', '4.0.0');
+    server.serve('foo', '2.0.0');
+    server.serve('bar', '1.5.0', deps: {'foo': '^4.0.0'});
 
     await pubAdd(args: ['foo']);
 
     await d.appDir({'foo': '^4.0.0', 'bar': '^1.0.0'}).validate();
     await d.cacheDir({'foo': '4.0.0', 'bar': '1.5.0'}).validate();
-    await d.appPackagesFile({'foo': '4.0.0', 'bar': '1.5.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '4.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.5.0'),
+    ]).validate();
   });
 }
diff --git a/test/add/git/git_test.dart b/test/add/git/git_test.dart
index b120e38..d976172 100644
--- a/test/add/git/git_test.dart
+++ b/test/add/git/git_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -158,9 +156,8 @@
   });
 
   test('can be overriden by dependency override', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.2.2');
 
     await d.git(
         'foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
@@ -176,7 +173,9 @@
     await pubAdd(args: ['foo', '--git-url', '../foo.git']);
 
     await d.cacheDir({'foo': '1.2.2'}).validate();
-    await d.appPackagesFile({'foo': '1.2.2'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+    ]).validate();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
@@ -187,4 +186,18 @@
       })
     ]).validate();
   });
+
+  test('fails if multiple packages passed for git source', () async {
+    ensureGit();
+
+    await d.git(
+        'foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
+
+    await d.appDir({}).create();
+
+    await pubAdd(
+        args: ['foo', 'bar', 'baz', '--git-url', '../foo.git'],
+        exitCode: exit_codes.USAGE,
+        error: contains('Can only add a single git package at a time.'));
+  });
 }
diff --git a/test/add/git/ref_test.dart b/test/add/git/ref_test.dart
index fadd80a..11dfc0b 100644
--- a/test/add/git/ref_test.dart
+++ b/test/add/git/ref_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/add/git/subdir_test.dart b/test/add/git/subdir_test.dart
index 3bf381a..8e8c39c 100644
--- a/test/add/git/subdir_test.dart
+++ b/test/add/git/subdir_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -32,10 +30,11 @@
         ])
       ])
     ]).validate();
-
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')),
+    ]).validate();
 
     await d.appDir({
       'sub': {
@@ -70,9 +69,11 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir')),
+    ]).validate();
 
     await d.appDir({
       'sub': {
diff --git a/test/add/hosted/non_default_pub_server_test.dart b/test/add/hosted/non_default_pub_server_test.dart
index 33b6753..60538e3 100644
--- a/test/add/hosted/non_default_pub_server_test.dart
+++ b/test/add/hosted/non_default_pub_server_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -14,13 +12,12 @@
   test('adds a package from a non-default pub server', () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveErrors();
+    (await servePackages()).serveErrors();
 
-    var server = await PackageServer.start((builder) {
-      builder.serve('foo', '0.2.5');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.3');
-    });
+    final server = await servePackages();
+    server.serve('foo', '0.2.5');
+    server.serve('foo', '1.1.0');
+    server.serve('foo', '1.2.3');
 
     await d.appDir({}).create();
 
@@ -29,7 +26,9 @@
     await pubAdd(args: ['foo:1.2.3', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': '1.2.3',
@@ -38,6 +37,49 @@
     }).validate();
   });
 
+  test('adds multiple packages from a non-default pub server', () async {
+    // Make the default server serve errors. Only the custom server should
+    // be accessed.
+    (await servePackages()).serveErrors();
+
+    final server = await servePackages();
+    server.serve('foo', '1.1.0');
+    server.serve('foo', '1.2.3');
+    server.serve('bar', '0.2.5');
+    server.serve('bar', '3.2.3');
+    server.serve('baz', '0.1.3');
+    server.serve('baz', '1.3.5');
+
+    await d.appDir({}).create();
+
+    final url = server.url;
+
+    await pubAdd(
+        args: ['foo:1.2.3', 'bar:3.2.3', 'baz:1.3.5', '--hosted-url', url]);
+
+    await d.cacheDir({'foo': '1.2.3', 'bar': '3.2.3', 'baz': '1.3.5'},
+        port: server.port).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      d.packageConfigEntry(name: 'bar', version: '3.2.3'),
+      d.packageConfigEntry(name: 'baz', version: '1.3.5'),
+    ]).validate();
+    await d.appDir({
+      'foo': {
+        'version': '1.2.3',
+        'hosted': {'name': 'foo', 'url': url}
+      },
+      'bar': {
+        'version': '3.2.3',
+        'hosted': {'name': 'bar', 'url': url}
+      },
+      'baz': {
+        'version': '1.3.5',
+        'hosted': {'name': 'baz', 'url': url}
+      }
+    }).validate();
+  });
+
   test('fails when adding from an invalid url', () async {
     ensureGit();
 
@@ -66,13 +108,12 @@
       () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveErrors();
+    (await servePackages()).serveErrors();
 
-    var server = await PackageServer.start((builder) {
-      builder.serve('foo', '0.2.5');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.3');
-    });
+    final server = await servePackages();
+    server.serve('foo', '0.2.5');
+    server.serve('foo', '1.1.0');
+    server.serve('foo', '1.2.3');
 
     await d.appDir({}).create();
 
@@ -81,7 +122,9 @@
     await pubAdd(args: ['foo', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': '^1.2.3',
@@ -94,13 +137,12 @@
       () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveErrors();
+    (await servePackages()).serveErrors();
 
-    var server = await PackageServer.start((builder) {
-      builder.serve('foo', '0.2.5');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.3');
-    });
+    final server = await servePackages();
+    server.serve('foo', '0.2.5');
+    server.serve('foo', '1.1.0');
+    server.serve('foo', '1.2.3');
 
     await d.appDir({}).create();
 
@@ -109,7 +151,9 @@
     await pubAdd(args: ['foo', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': '^1.2.3',
@@ -123,13 +167,11 @@
       'constraint', () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveErrors();
-
-    var server = await PackageServer.start((builder) {
-      builder.serve('foo', '0.2.5');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.3');
-    });
+    (await servePackages()).serveErrors();
+    final server = await servePackages();
+    server.serve('foo', '0.2.5');
+    server.serve('foo', '1.1.0');
+    server.serve('foo', '1.2.3');
 
     await d.appDir({}).create();
 
@@ -138,7 +180,9 @@
     await pubAdd(args: ['foo:any', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': 'any',
diff --git a/test/add/path/absolute_path_test.dart b/test/add/path/absolute_path_test.dart
index dc74638..5e63679 100644
--- a/test/add/path/absolute_path_test.dart
+++ b/test/add/path/absolute_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
@@ -22,7 +20,9 @@
 
     await pubAdd(args: ['foo', '--path', absolutePath]);
 
-    await d.appPackagesFile({'foo': absolutePath}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: absolutePath),
+    ]).validate();
 
     await d.appDir({
       'foo': {'path': absolutePath}
@@ -43,6 +43,28 @@
     }).validate();
   });
 
+  test('fails when adding multiple packages through local path', () async {
+    ensureGit();
+
+    await d.git(
+        'foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
+
+    await d.appDir({}).create();
+    final absolutePath = path.join(d.sandbox, 'foo');
+
+    await pubAdd(
+        args: ['foo:2.0.0', 'bar:0.1.3', 'baz:1.3.1', '--path', absolutePath],
+        error: contains('Can only add a single local package at a time.'),
+        exitCode: exit_codes.USAGE);
+
+    await d.appDir({}).validate();
+    await d.dir(appPath, [
+      d.nothing('.dart_tool/package_config.json'),
+      d.nothing('pubspec.lock'),
+      d.nothing('.packages'),
+    ]).validate();
+  });
+
   test('fails when adding with an invalid version constraint', () async {
     ensureGit();
 
@@ -90,9 +112,8 @@
   });
 
   test('can be overriden by dependency override', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.2.2');
     await d
         .dir('foo', [d.libDir('foo'), d.libPubspec('foo', '0.0.1')]).create();
 
@@ -108,7 +129,9 @@
     await pubAdd(args: ['foo', '--path', absolutePath]);
 
     await d.cacheDir({'foo': '1.2.2'}).validate();
-    await d.appPackagesFile({'foo': '1.2.2'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+    ]).validate();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
diff --git a/test/add/path/relative_path_test.dart b/test/add/path/relative_path_test.dart
index 9d443e8..e08ba3c 100644
--- a/test/add/path/relative_path_test.dart
+++ b/test/add/path/relative_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io' show Platform;
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -21,7 +19,9 @@
 
     await pubAdd(args: ['foo', '--path', '../foo']);
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
 
     await d.appDir({
       'foo': {'path': '../foo'}
@@ -40,7 +40,9 @@
       output: contains('Changed 1 dependency in myapp!'),
     );
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
 
     await d.appDir({
       'foo': {'path': '../foo'}
@@ -104,9 +106,8 @@
   });
 
   test('can be overriden by dependency override', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.2.2');
     await d
         .dir('foo', [d.libDir('foo'), d.libPubspec('foo', '0.0.1')]).create();
 
@@ -121,7 +122,9 @@
     await pubAdd(args: ['foo', '--path', '../foo']);
 
     await d.cacheDir({'foo': '1.2.2'}).validate();
-    await d.appPackagesFile({'foo': '1.2.2'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+    ]).validate();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
diff --git a/test/add/sdk/sdk_test.dart b/test/add/sdk/sdk_test.dart
index 1ded16e..ff7d442 100644
--- a/test/add/sdk/sdk_test.dart
+++ b/test/add/sdk/sdk_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
@@ -13,9 +11,8 @@
 
 void main() {
   setUp(() async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('bar', '1.0.0');
 
     await d.dir('flutter', [
       d.dir('packages', [
@@ -45,11 +42,12 @@
           'foo': {'sdk': 'flutter', 'version': '^0.0.1'}
         }
       }),
-      d.packagesFile({
-        'myapp': '.',
-        'foo': p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-        'bar': '1.0.0'
-      })
+    ]).validate();
+
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'foo', path: p.join(d.sandbox, 'flutter', 'packages', 'foo')),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
     ]).validate();
   });
 
@@ -68,11 +66,11 @@
           'foo': {'sdk': 'flutter', 'version': '0.0.1'}
         }
       }),
-      d.packagesFile({
-        'myapp': '.',
-        'foo': p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-        'bar': '1.0.0'
-      })
+    ]).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'foo', path: p.join(d.sandbox, 'flutter', 'packages', 'foo')),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
     ]).validate();
   });
 
@@ -82,11 +80,10 @@
         args: ['baz', '--sdk', 'flutter'],
         environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
 
-    await d.dir(appPath, [
-      d.packagesFile({
-        'myapp': '.',
-        'baz': p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz')
-      })
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'baz',
+          path: p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz'))
     ]).validate();
   });
 
diff --git a/test/cache/add/adds_latest_matching_version_test.dart b/test/cache/add/adds_latest_matching_version_test.dart
index d7e26c4..7bf9625 100644
--- a/test/cache/add/adds_latest_matching_version_test.dart
+++ b/test/cache/add/adds_latest_matching_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:test/test.dart';
@@ -15,12 +13,11 @@
   test(
       'adds the latest version of the package matching the '
       'version constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '2.0.0-dev');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.2')
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '2.0.0-dev')
+      ..serve('foo', '2.0.0');
 
     await runPub(
         args: ['cache', 'add', 'foo', '-v', '>=1.0.0 <2.0.0'],
diff --git a/test/cache/add/adds_latest_version_test.dart b/test/cache/add/adds_latest_version_test.dart
index 6a8b795..cf34857 100644
--- a/test/cache/add/adds_latest_version_test.dart
+++ b/test/cache/add/adds_latest_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('adds the latest stable version of the package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '1.2.4-dev');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.2')
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '1.2.4-dev');
 
     await runPub(
         args: ['cache', 'add', 'foo'], output: 'Downloading foo 1.2.3...');
diff --git a/test/cache/add/all_adds_all_matching_versions_test.dart b/test/cache/add/all_adds_all_matching_versions_test.dart
index bfffa7f..a05e8dc 100644
--- a/test/cache/add/all_adds_all_matching_versions_test.dart
+++ b/test/cache/add/all_adds_all_matching_versions_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,12 +9,11 @@
 
 void main() {
   test('"--all" adds all matching versions of the package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-      builder.serve('foo', '1.2.3-dev');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.2')
+      ..serve('foo', '1.2.3-dev')
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '2.0.0');
 
     await runPub(
         args: ['cache', 'add', 'foo', '-v', '>=1.0.0 <2.0.0', '--all'],
diff --git a/test/cache/add/all_with_some_versions_present_test.dart b/test/cache/add/all_with_some_versions_present_test.dart
index da4da98..7b5b36d 100644
--- a/test/cache/add/all_with_some_versions_present_test.dart
+++ b/test/cache/add/all_with_some_versions_present_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,12 +9,11 @@
 
 void main() {
   test('"--all" adds all non-installed versions of the package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.1');
-      builder.serve('foo', '1.2.2');
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.1')
+      ..serve('foo', '1.2.2')
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '2.0.0');
 
     // Install a couple of versions first.
     await runPub(
diff --git a/test/cache/add/already_cached_test.dart b/test/cache/add/already_cached_test.dart
index 9bf8df3..8c74da9 100644
--- a/test/cache/add/already_cached_test.dart
+++ b/test/cache/add/already_cached_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,9 +9,8 @@
 
 void main() {
   test('does nothing if the package is already cached', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     // Run once to put it in the cache.
     await runPub(
diff --git a/test/cache/add/bad_version_test.dart b/test/cache/add/bad_version_test.dart
index e0a57e0..4e93331 100644
--- a/test/cache/add/bad_version_test.dart
+++ b/test/cache/add/bad_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,16 +9,10 @@
 
 void main() {
   test('fails if the version constraint cannot be parsed', () {
-    return runPub(args: ['cache', 'add', 'foo', '-v', '1.0'], error: '''
-            Could not parse version "1.0". Unknown text at "1.0".
-            
-            Usage: pub cache add <package> [--version <constraint>] [--all]
-            -h, --help       Print this usage information.
-                --all        Install all matching versions.
-            -v, --version    Version constraint.
-
-            Run "pub help" to see global options.
-            See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
-            ''', exitCode: exit_codes.USAGE);
+    return runPub(
+      args: ['cache', 'add', 'foo', '-v', '1.0'],
+      error: contains('Could not parse version "1.0". Unknown text at "1.0".'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/cache/add/missing_package_arg_test.dart b/test/cache/add/missing_package_arg_test.dart
index 2274992..456342b 100644
--- a/test/cache/add/missing_package_arg_test.dart
+++ b/test/cache/add/missing_package_arg_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,16 +9,10 @@
 
 void main() {
   test('fails if no package was given', () {
-    return runPub(args: ['cache', 'add'], error: '''
-            No package to add given.
-            
-            Usage: pub cache add <package> [--version <constraint>] [--all]
-            -h, --help       Print this usage information.
-                --all        Install all matching versions.
-            -v, --version    Version constraint.
-
-            Run "pub help" to see global options.
-            See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
-            ''', exitCode: exit_codes.USAGE);
+    return runPub(
+      args: ['cache', 'add'],
+      error: contains('No package to add given.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/cache/add/no_matching_version_test.dart b/test/cache/add/no_matching_version_test.dart
index d395f29..a860294 100644
--- a/test/cache/add/no_matching_version_test.dart
+++ b/test/cache/add/no_matching_version_test.dart
@@ -2,18 +2,15 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../test_pub.dart';
 
 void main() {
   test('fails if no version matches the version constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.2');
-      builder.serve('foo', '1.2.3');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.2')
+      ..serve('foo', '1.2.3');
 
     await runPub(
         args: ['cache', 'add', 'foo', '-v', '>2.0.0'],
diff --git a/test/cache/add/package_not_found_test.dart b/test/cache/add/package_not_found_test.dart
index 4179319..caf45f4 100644
--- a/test/cache/add/package_not_found_test.dart
+++ b/test/cache/add/package_not_found_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,12 +9,13 @@
 
 void main() {
   test('fails if the package cound not be found on the source', () async {
-    await serveNoPackages();
+    await servePackages();
 
     await runPub(
         args: ['cache', 'add', 'foo'],
-        error: RegExp(r"Package doesn't exist \(could not find package foo at "
-            r'http://.*\)\.'),
+        error: RegExp(
+          r'Package not available \(could not find package foo at http://.*\)\.',
+        ),
         exitCode: exit_codes.UNAVAILABLE);
   });
 }
diff --git a/test/cache/add/unexpected_arguments_test.dart b/test/cache/add/unexpected_arguments_test.dart
index 66174db..1e4fa54 100644
--- a/test/cache/add/unexpected_arguments_test.dart
+++ b/test/cache/add/unexpected_arguments_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,16 +9,10 @@
 
 void main() {
   test('fails if there are extra arguments', () {
-    return runPub(args: ['cache', 'add', 'foo', 'bar', 'baz'], error: '''
-            Unexpected arguments "bar" and "baz".
-            
-            Usage: pub cache add <package> [--version <constraint>] [--all]
-            -h, --help       Print this usage information.
-                --all        Install all matching versions.
-            -v, --version    Version constraint.
-
-            Run "pub help" to see global options.
-            See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
-            ''', exitCode: exit_codes.USAGE);
+    return runPub(
+      args: ['cache', 'add', 'foo', 'bar', 'baz'],
+      error: contains('Unexpected arguments "bar" and "baz".'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/cache/clean_test.dart b/test/cache/clean_test.dart
index 1ba2ac7..7a79bc8 100644
--- a/test/cache/clean_test.dart
+++ b/test/cache/clean_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -19,9 +17,9 @@
   });
 
   test('running pub cache clean --force deletes cache', () async {
-    await servePackages((b) => b
+    await servePackages()
       ..serve('foo', '1.1.2')
-      ..serve('bar', '1.2.3'));
+      ..serve('bar', '1.2.3');
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
     await pubGet();
     final cache = path.join(d.sandbox, cachePath);
@@ -34,9 +32,9 @@
 
   test('running pub cache clean deletes cache only with confirmation',
       () async {
-    await servePackages((b) => b
+    await servePackages()
       ..serve('foo', '1.1.2')
-      ..serve('bar', '1.2.3'));
+      ..serve('bar', '1.2.3');
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
     await pubGet();
     final cache = path.join(d.sandbox, cachePath);
diff --git a/test/cache/list_test.dart b/test/cache/list_test.dart
index 9d8b9f3..a2c26a4 100644
--- a/test/cache/list_test.dart
+++ b/test/cache/list_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
diff --git a/test/cache/repair/empty_cache_test.dart b/test/cache/repair/empty_cache_test.dart
index 679e32a..13d68cc 100644
--- a/test/cache/repair/empty_cache_test.dart
+++ b/test/cache/repair/empty_cache_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../test_pub.dart';
diff --git a/test/cache/repair/git_test.dart b/test/cache/repair/git_test.dart
index abb0ab7..262343a 100644
--- a/test/cache/repair/git_test.dart
+++ b/test/cache/repair/git_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
diff --git a/test/cache/repair/handles_corrupted_binstub_test.dart b/test/cache/repair/handles_corrupted_binstub_test.dart
index b1c3556..0ca169f 100644
--- a/test/cache/repair/handles_corrupted_binstub_test.dart
+++ b/test/cache/repair/handles_corrupted_binstub_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('handles a corrupted binstub script', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/cache/repair/handles_corrupted_global_lockfile_test.dart b/test/cache/repair/handles_corrupted_global_lockfile_test.dart
index 3952871..9a9af05 100644
--- a/test/cache/repair/handles_corrupted_global_lockfile_test.dart
+++ b/test/cache/repair/handles_corrupted_global_lockfile_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/cache/repair/handles_failure_test.dart b/test/cache/repair/handles_failure_test.dart
index 8016d30..d5637ca 100644
--- a/test/cache/repair/handles_failure_test.dart
+++ b/test/cache/repair/handles_failure_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -13,15 +11,14 @@
 void main() {
   test('handles failure to reinstall some packages', () async {
     // Only serve two packages so repairing will have a failure.
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '1.2.5');
-    });
+    final server = await servePackages()
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '1.2.5');
 
     // Set up a cache with some packages.
     await d.dir(cachePath, [
       d.dir('hosted', [
-        d.dir('localhost%58${globalServer.port}', [
+        d.dir('localhost%58${server.port}', [
           d.dir('foo-1.2.3',
               [d.libPubspec('foo', '1.2.3'), d.file('broken.txt')]),
           d.dir('foo-1.2.4',
@@ -41,7 +38,7 @@
     expect(pub.stderr, emits(startsWith('Failed to repair foo 1.2.4. Error:')));
     expect(
         pub.stderr,
-        emits('Package doesn\'t exist '
+        emits('Package not available '
             '(Package foo has no version 1.2.4).'));
 
     expect(pub.stdout, emits('Reinstalled 2 packages.'));
diff --git a/test/cache/repair/handles_orphaned_binstub_test.dart b/test/cache/repair/handles_orphaned_binstub_test.dart
index 4954121..0754496 100644
--- a/test/cache/repair/handles_orphaned_binstub_test.dart
+++ b/test/cache/repair/handles_orphaned_binstub_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
-const _ORPHANED_BINSTUB = '''
+const _orphanedBinstub = '''
 #!/usr/bin/env sh
 # This file was created by pub v0.1.2-3.
 # Package: foo
@@ -22,7 +20,7 @@
 void main() {
   test('handles an orphaned binstub script', () async {
     await d.dir(cachePath, [
-      d.dir('bin', [d.file(binStubName('script'), _ORPHANED_BINSTUB)])
+      d.dir('bin', [d.file(binStubName('script'), _orphanedBinstub)])
     ]).create();
 
     await runPub(
diff --git a/test/cache/repair/hosted.dart b/test/cache/repair/hosted.dart
index 23d839b..ec3786b 100644
--- a/test/cache/repair/hosted.dart
+++ b/test/cache/repair/hosted.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -13,14 +11,13 @@
 import '../../test_pub.dart';
 
 void main() {
-  setUp(() {
-    return servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '1.2.4');
-      builder.serve('foo', '1.2.5');
-      builder.serve('bar', '1.2.3');
-      builder.serve('bar', '1.2.4');
-    });
+  setUp(() async {
+    await servePackages()
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '1.2.4')
+      ..serve('foo', '1.2.5')
+      ..serve('bar', '1.2.3')
+      ..serve('bar', '1.2.4');
   });
 
   test('reinstalls previously cached hosted packages', () async {
diff --git a/test/cache/repair/recompiles_snapshots_test.dart b/test/cache/repair/recompiles_snapshots_test.dart
index 698e148..f3c3d6c 100644
--- a/test/cache/repair/recompiles_snapshots_test.dart
+++ b/test/cache/repair/recompiles_snapshots_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('recompiles activated executable snapshots', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/cache/repair/updates_binstubs_test.dart b/test/cache/repair/updates_binstubs_test.dart
index bfb6400..e8cbfb4 100644
--- a/test/cache/repair/updates_binstubs_test.dart
+++ b/test/cache/repair/updates_binstubs_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
-const _OUTDATED_BINSTUB = '''
+const _outdatedBinstub = '''
 #!/usr/bin/env sh
 # This file was created by pub v0.1.2-3.
 # Package: foo
@@ -21,19 +19,17 @@
 
 void main() {
   test('updates an outdated binstub script', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'foo-script': 'script'}
-      }, contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'foo-script': 'script'}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
     await d.dir(cachePath, [
-      d.dir('bin', [d.file(binStubName('foo-script'), _OUTDATED_BINSTUB)])
+      d.dir('bin', [d.file(binStubName('foo-script'), _outdatedBinstub)])
     ]).create();
 
     // Repair them.
diff --git a/test/dependency_override_test.dart b/test/dependency_override_test.dart
index 3cac645..59f59e4 100644
--- a/test/dependency_override_test.dart
+++ b/test/dependency_override_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 
 import 'package:test/test.dart';
@@ -14,11 +12,10 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test('chooses best version matching override constraint', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('foo', '3.0.0');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('foo', '3.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -30,13 +27,14 @@
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '2.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      ]).validate();
     });
 
     test('treats override as implicit dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -47,18 +45,19 @@
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      ]).validate();
     });
 
     test('ignores other constraints on overridden package', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('foo', '3.0.0');
-        builder.serve('bar', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('foo', '3.0.0')
+        ..serve('bar', '1.0.0', pubspec: {
           'dependencies': {'foo': '5.0.0-nonexistent'}
         });
-      });
 
       await d.dir(appPath, [
         d.pubspec({
@@ -70,14 +69,16 @@
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '2.0.0', 'bar': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      ]).validate();
     });
 
     test('ignores SDK constraints', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
-          'environment': {'sdk': '5.6.7-fblthp'}
-        });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0', pubspec: {
+        'environment': {'sdk': '5.6.7-fblthp'}
       });
 
       await d.dir(appPath, [
@@ -88,15 +89,15 @@
       ]).create();
 
       await pubCommand(command);
-
-      await d.appPackagesFile({'foo': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      ]).validate();
     });
 
     test('warns about overridden dependencies', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('bar', '1.0.0');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('bar', '1.0.0');
 
       await d
           .dir('baz', [d.libDir('baz'), d.libPubspec('baz', '0.0.1')]).create();
diff --git a/test/dependency_services/dependency_services_test.dart b/test/dependency_services/dependency_services_test.dart
index d341df4..0ab5825 100644
--- a/test/dependency_services/dependency_services_test.dart
+++ b/test/dependency_services/dependency_services_test.dart
@@ -1,39 +1,77 @@
 // Copyright (c) 2020, 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.
-
-// @dart=2.10
-
 import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
+import 'package:test_process/test_process.dart';
 
 import '../descriptor.dart' as d;
 import '../golden_file.dart';
 import '../test_pub.dart';
 
-Future<void> pipeline(String name, List<_PackageVersion> upgrades) async {
+late String snapshot;
+const _commandRunner = 'bin/dependency_services.dart';
+
+String _filter(String input) {
+  return input
+      .replaceAll(p.toUri(d.sandbox).toString(), r'file://$SANDBOX')
+      .replaceAll(d.sandbox, r'$SANDBOX')
+      .replaceAll(Platform.pathSeparator, '/')
+      .replaceAll(Platform.operatingSystem, r'$OS')
+      .replaceAll(globalServer.port.toString(), r'$PORT');
+}
+
+/// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and appends the output to [buffer].
+Future<void> runDependencyServicesToBuffer(
+  List<String> args,
+  StringBuffer buffer, {
+  String? workingDirectory,
+  Map<String, String>? environment,
+  dynamic exitCode = 0,
+  String? stdin,
+}) async {
+  final process = await TestProcess.start(
+    Platform.resolvedExecutable,
+    ['--enable-asserts', snapshot, ...args],
+    environment: {
+      ...getPubTestEnvironment(),
+      ...?environment,
+    },
+    workingDirectory: workingDirectory ?? p.join(d.sandbox, appPath),
+  );
+  if (stdin != null) {
+    process.stdin.write(stdin);
+    await process.stdin.flush();
+    await process.stdin.close();
+  }
+  await process.shouldExit(exitCode);
+
+  buffer.writeln([
+    '\$ $_commandRunner ${args.join(' ')}',
+    ...await process.stdout.rest.map(_filter).toList(),
+    ...await process.stderr.rest.map((e) => '[E] ${_filter(e)}').toList(),
+  ].join('\n'));
+  buffer.write('\n');
+}
+
+Future<void> pipeline(
+  String name,
+  List<_PackageVersion> upgrades,
+  GoldenTestContext context,
+) async {
   final buffer = StringBuffer();
-  await runPubIntoBuffer([
-    '__experimental-dependency-services',
-    'list',
-  ], buffer);
-  await runPubIntoBuffer([
-    '__experimental-dependency-services',
-    'report',
-  ], buffer);
+  await runDependencyServicesToBuffer(['list'], buffer);
+  await runDependencyServicesToBuffer(['report'], buffer);
 
   final input = json.encode({
-    'changes': upgrades,
+    'dependencyChanges': upgrades,
   });
 
-  await runPubIntoBuffer([
-    '__experimental-dependency-services',
-    'apply',
-  ], buffer, stdin: input);
+  await runDependencyServicesToBuffer(['apply'], buffer, stdin: input);
   void catIntoBuffer(String path) {
     buffer.writeln('$path:');
     buffer.writeln(File(p.join(d.sandbox, path)).readAsStringSync());
@@ -41,19 +79,24 @@
 
   catIntoBuffer(p.join(appPath, 'pubspec.yaml'));
   catIntoBuffer(p.join(appPath, 'pubspec.lock'));
-  expectMatchesGoldenFile(
-    // TODO: Consider if expectMatchesGoldenFile should replace localhost:<port>
-    buffer.toString().replaceAll(RegExp('localhost:\d+'), 'localhost:<port>'),
-    'test/dependency_services/goldens/dependency_report_$name.txt',
+  context.expectNextSection(
+    buffer.toString().replaceAll(RegExp(r'localhost:\d+'), 'localhost:<port>'),
   );
 }
 
 Future<void> main() async {
-  test('Removing transitive', () async {
-    await servePackages((builder) => builder
+  setUpAll(() async {
+    final tempDir = Directory.systemTemp.createTempSync();
+    snapshot = p.join(tempDir.path, 'dependency_services.dart.snapshot');
+    final r = Process.runSync(
+        Platform.resolvedExecutable, ['--snapshot=$snapshot', _commandRunner]);
+    expect(r.exitCode, 0, reason: r.stderr);
+  });
+  testWithGolden('Removing transitive', (context) async {
+    (await servePackages())
       ..serve('foo', '1.2.3', deps: {'transitive': '^1.0.0'})
       ..serve('foo', '2.2.3')
-      ..serve('transitive', '1.0.0'));
+      ..serve('transitive', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -64,17 +107,21 @@
       })
     ]).create();
     await pubGet();
-    await pipeline('removing_transitive', [
-      _PackageVersion('foo', Version.parse('2.2.3')),
-      _PackageVersion('transitive', null)
-    ]);
+    await pipeline(
+      'removing_transitive',
+      [
+        _PackageVersion('foo', Version.parse('2.2.3')),
+        _PackageVersion('transitive', null)
+      ],
+      context,
+    );
   });
 
-  test('Adding transitive', () async {
-    await servePackages((builder) => builder
+  testWithGolden('Adding transitive', (context) async {
+    (await servePackages())
       ..serve('foo', '1.2.3')
       ..serve('foo', '2.2.3', deps: {'transitive': '^1.0.0'})
-      ..serve('transitive', '1.0.0'));
+      ..serve('transitive', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -85,19 +132,23 @@
       })
     ]).create();
     await pubGet();
-    await pipeline('adding_transitive', [
-      _PackageVersion('foo', Version.parse('2.2.3')),
-      _PackageVersion('transitive', Version.parse('1.0.0'))
-    ]);
+    await pipeline(
+      'adding_transitive',
+      [
+        _PackageVersion('foo', Version.parse('2.2.3')),
+        _PackageVersion('transitive', Version.parse('1.0.0'))
+      ],
+      context,
+    );
   });
 }
 
 class _PackageVersion {
   String name;
-  Version version;
+  Version? version;
   _PackageVersion(this.name, this.version);
 
-  Map<String, Object> toJson() => {
+  Map<String, Object?> toJson() => {
         'name': name,
         'version': version?.toString(),
       };
diff --git a/test/deps/executables_test.dart b/test/deps/executables_test.dart
index f568511..ea864fd 100644
--- a/test/deps/executables_test.dart
+++ b/test/deps/executables_test.dart
@@ -2,13 +2,6 @@
 // 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.
 
-// @dart=2.10
-
-import 'package:pub/src/ascii_tree.dart' as tree;
-import 'package:pub/src/io.dart';
-import 'package:test/test.dart';
-
-import '../ascii_tree_test.dart';
 import '../descriptor.dart' as d;
 import '../golden_file.dart';
 import '../test_pub.dart';
@@ -16,31 +9,29 @@
 const _validMain = 'main() {}';
 const _invalidMain = 'main() {';
 
-Future<void> variations(String name) async {
-  final buffer = StringBuffer();
-  buffer.writeln(stripColors(
-      tree.fromFiles(listDir(d.sandbox, recursive: true), baseDir: d.sandbox)));
+extension on GoldenTestContext {
+  Future<void> runExecutablesTest() async {
+    await pubGet();
 
-  await pubGet();
-  await runPubIntoBuffer(['deps', '--executables'], buffer);
-  await runPubIntoBuffer(['deps', '--executables', '--dev'], buffer);
-  // The json ouput also lists the exectuables.
-  await runPubIntoBuffer(['deps', '--json'], buffer);
-  // The easiest way to update the golden files is to delete them and rerun the
-  // test.
-  expectMatchesGoldenFile(buffer.toString(), 'test/deps/goldens/$name.txt');
+    await tree();
+
+    await run(['deps', '--executables']);
+    await run(['deps', '--executables', '--dev']);
+    await run(['deps', '--json']);
+  }
 }
 
 void main() {
-  test('skips non-Dart executables', () async {
+  testWithGolden('skips non-Dart executables', (ctx) async {
     await d.dir(appPath, [
       d.appPubspec(),
       d.dir('bin', [d.file('foo.py'), d.file('bar.sh')])
     ]).create();
-    await variations('non_dart_executables');
+
+    await ctx.runExecutablesTest();
   });
 
-  test('lists Dart executables, even without entrypoints', () async {
+  testWithGolden('lists Dart executables, without entrypoints', (ctx) async {
     await d.dir(appPath, [
       d.appPubspec(),
       d.dir(
@@ -48,10 +39,11 @@
         [d.file('foo.dart', _validMain), d.file('bar.dart', _invalidMain)],
       )
     ]).create();
-    await variations('dart_executables');
+
+    await ctx.runExecutablesTest();
   });
 
-  test('skips executables in sub directories', () async {
+  testWithGolden('skips executables in sub directories', (ctx) async {
     await d.dir(appPath, [
       d.appPubspec(),
       d.dir('bin', [
@@ -59,10 +51,11 @@
         d.dir('sub', [d.file('bar.dart', _validMain)])
       ])
     ]).create();
-    await variations('nothing_in_sub_drectories');
+
+    await ctx.runExecutablesTest();
   });
 
-  test('lists executables from a dependency', () async {
+  testWithGolden('lists executables from a dependency', (ctx) async {
     await d.dir('foo', [
       d.libPubspec('foo', '1.0.0'),
       d.dir('bin', [d.file('bar.dart', _validMain)])
@@ -74,10 +67,11 @@
       })
     ]).create();
 
-    await variations('from_dependency');
+    await ctx.runExecutablesTest();
   });
 
-  test('lists executables only from immediate dependencies', () async {
+  testWithGolden('lists executables only from immediate dependencies',
+      (ctx) async {
     await d.dir(appPath, [
       d.appPubspec({
         'foo': {'path': '../foo'}
@@ -96,10 +90,10 @@
       d.dir('bin', [d.file('qux.dart', _validMain)])
     ]).create();
 
-    await variations('only_immediate');
+    await ctx.runExecutablesTest();
   });
 
-  test('applies formatting before printing executables', () async {
+  testWithGolden('applies formatting before printing executables', (ctx) async {
     await d.dir(appPath, [
       d.appPubspec({
         'foo': {'path': '../foo'},
@@ -119,10 +113,10 @@
       d.dir('bin', [d.file('qux.dart', _validMain)])
     ]).create();
 
-    await variations('formatting');
+    await ctx.runExecutablesTest();
   });
 
-  test('dev dependencies', () async {
+  testWithGolden('dev dependencies', (ctx) async {
     await d.dir('foo', [
       d.libPubspec('foo', '1.0.0'),
       d.dir('bin', [d.file('bar.dart', _validMain)])
@@ -136,10 +130,11 @@
         }
       })
     ]).create();
-    await variations('dev_dependencies');
+
+    await ctx.runExecutablesTest();
   });
 
-  test('overriden dependencies executables', () async {
+  testWithGolden('overriden dependencies executables', (ctx) async {
     await d.dir('foo-1.0', [
       d.libPubspec('foo', '1.0.0'),
       d.dir('bin', [d.file('bar.dart', _validMain)])
@@ -162,6 +157,7 @@
         }
       })
     ]).create();
-    await variations('overrides');
+
+    await ctx.runExecutablesTest();
   });
 }
diff --git a/test/deps/goldens/dart_executables.txt b/test/deps/goldens/dart_executables.txt
deleted file mode 100644
index 9284ceb..0000000
--- a/test/deps/goldens/dart_executables.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-'-- myapp
-    |-- bin
-    |   |-- bar.dart
-    |   '-- foo.dart
-    '-- pubspec.yaml
-
-$ pub deps --executables
-myapp: bar, foo
-
-$ pub deps --executables --dev
-myapp: bar, foo
-
-$ pub deps --json
-{
-  "root": "myapp",
-  "packages": [
-    {
-      "name": "myapp",
-      "version": "0.0.0",
-      "kind": "root",
-      "source": "root",
-      "dependencies": []
-    }
-  ],
-  "sdks": [
-    {
-      "name": "Dart",
-      "version": "0.1.2+3"
-    }
-  ],
-  "executables": [
-    ":bar",
-    ":foo"
-  ]
-}
-
diff --git a/test/deps/goldens/from_dependency.txt b/test/deps/goldens/from_dependency.txt
deleted file mode 100644
index 30836e5..0000000
--- a/test/deps/goldens/from_dependency.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-|-- foo
-|   |-- bin
-|   |   '-- bar.dart
-|   '-- pubspec.yaml
-'-- myapp
-    '-- pubspec.yaml
-
-$ pub deps --executables
-foo:bar
-
-$ pub deps --executables --dev
-foo:bar
-
-$ pub deps --json
-{
-  "root": "myapp",
-  "packages": [
-    {
-      "name": "myapp",
-      "version": "0.0.0",
-      "kind": "root",
-      "source": "root",
-      "dependencies": [
-        "foo"
-      ]
-    },
-    {
-      "name": "foo",
-      "version": "1.0.0",
-      "kind": "direct",
-      "source": "path",
-      "dependencies": []
-    }
-  ],
-  "sdks": [
-    {
-      "name": "Dart",
-      "version": "0.1.2+3"
-    }
-  ],
-  "executables": [
-    "foo:bar"
-  ]
-}
-
diff --git a/test/deps/goldens/non_dart_executables.txt b/test/deps/goldens/non_dart_executables.txt
deleted file mode 100644
index 646e21e..0000000
--- a/test/deps/goldens/non_dart_executables.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-'-- myapp
-    |-- bin
-    |   |-- bar.sh
-    |   '-- foo.py
-    '-- pubspec.yaml
-
-$ pub deps --executables
-
-$ pub deps --executables --dev
-
-$ pub deps --json
-{
-  "root": "myapp",
-  "packages": [
-    {
-      "name": "myapp",
-      "version": "0.0.0",
-      "kind": "root",
-      "source": "root",
-      "dependencies": []
-    }
-  ],
-  "sdks": [
-    {
-      "name": "Dart",
-      "version": "0.1.2+3"
-    }
-  ],
-  "executables": []
-}
-
diff --git a/test/deps/goldens/nothing_in_sub_drectories.txt b/test/deps/goldens/nothing_in_sub_drectories.txt
deleted file mode 100644
index db3c77c..0000000
--- a/test/deps/goldens/nothing_in_sub_drectories.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-'-- myapp
-    |-- bin
-    |   |-- foo.dart
-    |   '-- sub
-    |       '-- bar.dart
-    '-- pubspec.yaml
-
-$ pub deps --executables
-myapp:foo
-
-$ pub deps --executables --dev
-myapp:foo
-
-$ pub deps --json
-{
-  "root": "myapp",
-  "packages": [
-    {
-      "name": "myapp",
-      "version": "0.0.0",
-      "kind": "root",
-      "source": "root",
-      "dependencies": []
-    }
-  ],
-  "sdks": [
-    {
-      "name": "Dart",
-      "version": "0.1.2+3"
-    }
-  ],
-  "executables": [
-    ":foo"
-  ]
-}
-
diff --git a/test/deps_test.dart b/test/deps_test.dart
index 2d59ef8..7336f13 100644
--- a/test/deps_test.dart
+++ b/test/deps_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
@@ -12,21 +10,19 @@
 
 void main() {
   setUp(() async {
-    await servePackages((builder) {
-      builder.serve('normal', '1.2.3',
-          deps: {'transitive': 'any', 'circular_a': 'any'});
-      builder.serve('transitive', '1.2.3', deps: {'shared': 'any'});
-      builder.serve('shared', '1.2.3', deps: {'other': 'any'});
-      builder.serve('dev_only', '1.2.3');
-      builder.serve('unittest', '1.2.3',
-          deps: {'shared': 'any', 'dev_only': 'any'});
-      builder.serve('other', '1.0.0', deps: {'myapp': 'any'});
-      builder.serve('overridden', '1.0.0');
-      builder.serve('overridden', '2.0.0');
-      builder.serve('override_only', '1.2.3');
-      builder.serve('circular_a', '1.2.3', deps: {'circular_b': 'any'});
-      builder.serve('circular_b', '1.2.3', deps: {'circular_a': 'any'});
-    });
+    await servePackages()
+      ..serve('normal', '1.2.3',
+          deps: {'transitive': 'any', 'circular_a': 'any'})
+      ..serve('transitive', '1.2.3', deps: {'shared': 'any'})
+      ..serve('shared', '1.2.3', deps: {'other': 'any'})
+      ..serve('dev_only', '1.2.3')
+      ..serve('unittest', '1.2.3', deps: {'shared': 'any', 'dev_only': 'any'})
+      ..serve('other', '1.0.0', deps: {'myapp': 'any'})
+      ..serve('overridden', '1.0.0')
+      ..serve('overridden', '2.0.0')
+      ..serve('override_only', '1.2.3')
+      ..serve('circular_a', '1.2.3', deps: {'circular_b': 'any'})
+      ..serve('circular_b', '1.2.3', deps: {'circular_a': 'any'});
 
     await d.dir('from_path',
         [d.libDir('from_path'), d.libPubspec('from_path', '1.2.3')]).create();
diff --git a/test/descriptor.dart b/test/descriptor.dart
index 3eee5d1..ee50851 100644
--- a/test/descriptor.dart
+++ b/test/descriptor.dart
@@ -2,12 +2,9 @@
 // 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.
 
-// @dart=2.10
-
 /// Pub-specific test descriptors.
 import 'dart:convert';
 
-import 'package:meta/meta.dart';
 import 'package:oauth2/oauth2.dart' as oauth2;
 import 'package:path/path.dart' as p;
 import 'package:pub/src/language_version.dart';
@@ -27,11 +24,11 @@
 export 'descriptor/tar.dart';
 
 /// Creates a new [GitRepoDescriptor] with [name] and [contents].
-GitRepoDescriptor git(String name, [Iterable<Descriptor> contents]) =>
+GitRepoDescriptor git(String name, [List<Descriptor>? contents]) =>
     GitRepoDescriptor(name, contents ?? <Descriptor>[]);
 
 /// Creates a new [TarFileDescriptor] with [name] and [contents].
-TarFileDescriptor tar(String name, [Iterable<Descriptor> contents]) =>
+TarFileDescriptor tar(String name, [List<Descriptor>? contents]) =>
     TarFileDescriptor(name, contents ?? <Descriptor>[]);
 
 /// Describes a package that passes all validation.
@@ -67,14 +64,14 @@
 ///
 /// [contents] may contain [Future]s that resolve to serializable objects,
 /// which may in turn contain [Future]s recursively.
-Descriptor pubspec(Map<String, Object> contents) => YamlDescriptor(
+Descriptor pubspec(Map<String, Object?> contents) => YamlDescriptor(
       'pubspec.yaml',
       yaml({
         ...contents,
         // TODO: Copy-pasting this into all call-sites, or use d.libPubspec
         'environment': {
           'sdk': '>=0.1.2 <1.0.0',
-          ...contents['environment'] as Map ?? {},
+          ...(contents['environment'] ?? {}) as Map,
         },
       }),
     );
@@ -84,8 +81,8 @@
 
 /// Describes a file named `pubspec.yaml` for an application package with the
 /// given [dependencies].
-Descriptor appPubspec([Map dependencies]) {
-  var map = <String, dynamic>{
+Descriptor appPubspec([Map? dependencies]) {
+  var map = <String, Object>{
     'name': 'myapp',
     'environment': {
       'sdk': '>=0.1.2 <1.0.0',
@@ -100,7 +97,7 @@
 /// constraint on that version, otherwise it adds an SDK constraint allowing
 /// the current SDK version.
 Descriptor libPubspec(String name, String version,
-    {Map deps, Map devDeps, String sdk}) {
+    {Map? deps, Map? devDeps, String? sdk}) {
   var map = packageMap(name, version, deps, devDeps);
   if (sdk != null) {
     map['environment'] = {'sdk': sdk};
@@ -112,7 +109,7 @@
 
 /// Describes a directory named `lib` containing a single dart file named
 /// `<name>.dart` that contains a line of Dart code.
-Descriptor libDir(String name, [String code]) {
+Descriptor libDir(String name, [String? code]) {
   // Default to printing the name if no other code was given.
   code ??= name;
   return dir('lib', [file('$name.dart', 'main() => "$code";')]);
@@ -130,8 +127,8 @@
 /// If [repoName] is not given it is assumed to be equal to [packageName].
 Descriptor gitPackageRevisionCacheDir(
   String packageName, {
-  int modifier,
-  String repoName,
+  int? modifier,
+  String? repoName,
 }) {
   repoName = repoName ?? packageName;
   var value = packageName;
@@ -159,7 +156,7 @@
 /// validated since they will often lack the dependencies section that the
 /// real pubspec being compared against has. You usually only need to pass
 /// `true` for this if you plan to call [create] on the resulting descriptor.
-Descriptor cacheDir(Map packages, {int port, bool includePubspecs = false}) {
+Descriptor cacheDir(Map packages, {int? port, bool includePubspecs = false}) {
   var contents = <Descriptor>[];
   packages.forEach((name, versions) {
     if (versions is! List) versions = [versions];
@@ -180,7 +177,7 @@
 ///
 /// If [port] is passed, it's used as the port number of the local hosted server
 /// that this cache represents. It defaults to [globalServer.port].
-Descriptor hostedCache(Iterable<Descriptor> contents, {int port}) {
+Descriptor hostedCache(Iterable<Descriptor> contents, {int? port}) {
   return dir(cachePath, [
     dir('hosted', [dir('localhost%58${port ?? globalServer.port}', contents)])
   ]);
@@ -190,7 +187,7 @@
 /// credentials. The URL "/token" on [server] will be used as the token
 /// endpoint for refreshing the access token.
 Descriptor credentialsFile(PackageServer server, String accessToken,
-    {String refreshToken, DateTime expiration}) {
+    {String? refreshToken, DateTime? expiration}) {
   return dir(
     configPath,
     [
@@ -208,7 +205,7 @@
 }
 
 Descriptor legacyCredentialsFile(PackageServer server, String accessToken,
-    {String refreshToken, DateTime expiration}) {
+    {String? refreshToken, DateTime? expiration}) {
   return dir(
     cachePath,
     [
@@ -228,8 +225,8 @@
 String _credentialsFileContent(
   PackageServer server,
   String accessToken, {
-  String refreshToken,
-  DateTime expiration,
+  String? refreshToken,
+  DateTime? expiration,
 }) =>
     oauth2.Credentials(
       accessToken,
@@ -245,14 +242,12 @@
 /// Describes the file in the system cache that contains credentials for
 /// third party hosted pub servers.
 Descriptor tokensFile([Map<String, dynamic> contents = const {}]) {
-  return dir(configPath, [
-    file('pub-tokens.json', contents != null ? jsonEncode(contents) : null)
-  ]);
+  return dir(configPath, [file('pub-tokens.json', jsonEncode(contents))]);
 }
 
 /// Describes the application directory, containing only a pubspec specifying
 /// the given [dependencies].
-DirectoryDescriptor appDir([Map dependencies]) =>
+DirectoryDescriptor appDir([Map? dependencies]) =>
     dir(appPath, [appPubspec(dependencies)]);
 
 /// Describes a `.packages` file.
@@ -266,7 +261,7 @@
 /// entries (one per key in [dependencies]), each with a path that contains
 /// either the version string (for a reference to the pub cache) or a
 /// path to a path dependency, relative to the application directory.
-Descriptor packagesFile([Map<String, String> dependencies]) =>
+Descriptor packagesFile(Map<String, String> dependencies) =>
     PackagesFileDescriptor(dependencies);
 
 /// Describes a `.dart_tools/package_config.json` file.
@@ -282,13 +277,31 @@
 }) =>
     PackageConfigFileDescriptor(packages, generatorVersion);
 
+Descriptor appPackageConfigFile(
+  List<PackageConfigEntry> packages, {
+  String generatorVersion = '0.1.2+3',
+}) =>
+    dir(
+      appPath,
+      [
+        packageConfigFile(
+          [
+            packageConfigEntry(name: 'myapp', path: '.'),
+            ...packages,
+          ],
+          generatorVersion: generatorVersion,
+        ),
+      ],
+    );
+
 /// Create a [PackageConfigEntry] which assumes package with [name] is either
 /// a cached package with given [version] or a path dependency at given [path].
 PackageConfigEntry packageConfigEntry({
-  @required String name,
-  String version,
-  String path,
-  String languageVersion,
+  required String name,
+  String? version,
+  String? path,
+  String? languageVersion,
+  PackageServer? server,
 }) {
   if (version != null && path != null) {
     throw ArgumentError.value(
@@ -300,7 +313,7 @@
   }
   Uri rootUri;
   if (version != null) {
-    rootUri = p.toUri(globalPackageServer.pathInCache(name, version));
+    rootUri = p.toUri((server ?? globalServer).pathInCache(name, version));
   } else {
     rootUri = p.toUri(p.join('..', path));
   }
diff --git a/test/descriptor/git.dart b/test/descriptor/git.dart
index 56cd59f..edfc7bd 100644
--- a/test/descriptor/git.dart
+++ b/test/descriptor/git.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
@@ -17,7 +15,7 @@
 
   /// Creates the Git repository and commits the contents.
   @override
-  Future create([String parent]) async {
+  Future create([String? parent]) async {
     await super.create(parent);
     await _runGitCommands(parent, [
       ['init'],
@@ -35,7 +33,7 @@
   /// the previous structure to the Git repo.
   ///
   /// [parent] defaults to [sandbox].
-  Future commit([String parent]) async {
+  Future commit([String? parent]) async {
     await super.create(parent);
     await _runGitCommands(parent, [
       ['add', '.'],
@@ -47,7 +45,7 @@
   /// referred to by [ref].
   ///
   /// [parent] defaults to [sandbox].
-  Future<String> revParse(String ref, [String parent]) async {
+  Future<String> revParse(String ref, [String? parent]) async {
     var output = await _runGit(['rev-parse', ref], parent);
     return output[0];
   }
@@ -55,9 +53,9 @@
   /// Runs a Git command in this repository.
   ///
   /// [parent] defaults to [sandbox].
-  Future runGit(List<String> args, [String parent]) => _runGit(args, parent);
+  Future runGit(List<String> args, [String? parent]) => _runGit(args, parent);
 
-  Future<List<String>> _runGit(List<String> args, String parent) {
+  Future<List<String>> _runGit(List<String> args, String? parent) {
     // Explicitly specify the committer information. Git needs this to commit
     // and we don't want to rely on the buildbots having this already set up.
     var environment = {
@@ -72,7 +70,7 @@
         environment: environment);
   }
 
-  Future _runGitCommands(String parent, List<List<String>> commands) async {
+  Future _runGitCommands(String? parent, List<List<String>> commands) async {
     for (var command in commands) {
       await _runGit(command, parent);
     }
diff --git a/test/descriptor/packages.dart b/test/descriptor/packages.dart
index 2a23e40..b670322 100644
--- a/test/descriptor/packages.dart
+++ b/test/descriptor/packages.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async' show Future;
 import 'dart:convert' show JsonEncoder, json, utf8;
 import 'dart:io' show File;
@@ -25,7 +23,7 @@
 /// Describes a `.packages` file and its contents.
 class PackagesFileDescriptor extends Descriptor {
   /// A map from package names to either version strings or path to the package.
-  final Map<String, String> _dependencies;
+  final Map<String, String>? _dependencies;
 
   /// Describes a `.packages` file with the given dependencies.
   ///
@@ -34,11 +32,12 @@
   PackagesFileDescriptor([this._dependencies]) : super('.packages');
 
   @override
-  Future create([String parent]) {
+  Future create([String? parent]) {
     var contents = const <int>[];
-    if (_dependencies != null) {
+    var dependencies = _dependencies;
+    if (dependencies != null) {
       var mapping = <String, Uri>{};
-      _dependencies.forEach((package, version) {
+      dependencies.forEach((package, version) {
         String packagePath;
         if (_isSemver(version)) {
           // It's a cache reference.
@@ -58,7 +57,7 @@
   }
 
   @override
-  Future validate([String parent]) async {
+  Future validate([String? parent]) async {
     var fullPath = p.join(parent ?? sandbox, name);
     if (!await File(fullPath).exists()) {
       fail("File not found: '$fullPath'.");
@@ -68,14 +67,16 @@
 
     var map = packages_file.parse(bytes, Uri.parse(_base));
 
-    for (var package in _dependencies.keys) {
+    var dependencies = _dependencies!;
+
+    for (var package in dependencies.keys) {
       if (!map.containsKey(package)) {
         fail('.packages does not contain $package entry');
       }
 
-      var description = _dependencies[package];
+      var description = dependencies[package]!;
       if (_isSemver(description)) {
-        if (!map[package].path.contains(description)) {
+        if (!map[package]!.path.contains(description)) {
           fail('.packages of $package has incorrect version. '
               'Expected $description, found location: ${map[package]}.');
         }
@@ -90,9 +91,9 @@
       }
     }
 
-    if (map.length != _dependencies.length) {
+    if (map.length != dependencies.length) {
       for (var key in map.keys) {
-        if (!_dependencies.containsKey(key)) {
+        if (!dependencies.containsKey(key)) {
           fail('.packages file contains unexpected entry: $key');
         }
       }
@@ -128,7 +129,7 @@
       : super('.dart_tool/package_config.json');
 
   @override
-  Future<void> create([String parent]) async {
+  Future<void> create([String? parent]) async {
     final packageConfigFile = File(p.join(parent ?? sandbox, name));
     await packageConfigFile.parent.create();
     await packageConfigFile.writeAsString(
@@ -137,13 +138,13 @@
   }
 
   @override
-  Future<void> validate([String parent]) async {
+  Future<void> validate([String? parent]) async {
     final packageConfigFile = p.join(parent ?? sandbox, name);
     if (!await File(packageConfigFile).exists()) {
       fail("File not found: '$packageConfigFile'.");
     }
 
-    Map<String, Object> rawJson = json.decode(
+    Map<String, dynamic> rawJson = json.decode(
       await File(packageConfigFile).readAsString(),
     );
     PackageConfig config;
@@ -155,10 +156,20 @@
 
     // Compare packages as sets to ignore ordering.
     expect(
-      config.packages.map((e) => e.toJson()).toSet(),
-      equals(_packages.map((e) => e.toJson()).toSet()),
-      reason:
-          '"packages" property in "$packageConfigFile" does not expected values',
+      config.packages,
+      _packages
+          .map(
+            (p) => isA<PackageConfigEntry>()
+                .having((p0) => p0.name, 'name', p.name)
+                .having(
+                    (p0) => p0.languageVersion,
+                    'languageVersion',
+                    // If the expected entry has no language-version we don't check it.
+                    p.languageVersion ?? anything)
+                .having((p0) => p0.rootUri, 'rootUri', p.rootUri)
+                .having((p0) => p0.packageUri, 'packageUri', p.packageUri),
+          )
+          .toSet(),
     );
 
     final expected = PackageConfig.fromJson(_config.toJson());
diff --git a/test/descriptor/tar.dart b/test/descriptor/tar.dart
index 1c4f89f..2a3488f 100644
--- a/test/descriptor/tar.dart
+++ b/test/descriptor/tar.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -23,7 +21,7 @@
   /// Creates the files and directories within this tar file, then archives
   /// them, compresses them, and saves the result to [parentDir].
   @override
-  Future create([String parent]) {
+  Future create([String? parent]) {
     return withTempDir((tempDir) async {
       await Future.wait(contents.map((entry) => entry.create(tempDir)));
 
@@ -41,7 +39,7 @@
   /// Validates that the `.tar.gz` file at [path] contains the expected
   /// contents.
   @override
-  Future validate([String parent]) {
+  Future validate([String? parent]) {
     throw UnimplementedError('TODO(nweiz): implement this');
   }
 
diff --git a/test/descriptor/yaml.dart b/test/descriptor/yaml.dart
index 086da84..6840041 100644
--- a/test/descriptor/yaml.dart
+++ b/test/descriptor/yaml.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async' show Future;
 import 'dart:convert' show utf8;
 import 'dart:io';
@@ -29,7 +27,7 @@
       Stream.fromIterable([utf8.encode(_contents)]);
 
   @override
-  Future validate([String parent]) async {
+  Future validate([String? parent]) async {
     var fullPath = p.join(parent ?? sandbox, name);
     if (!await File(fullPath).exists()) {
       fail("File not found: '$fullPath'.");
diff --git a/test/descriptor_server.dart b/test/descriptor_server.dart
deleted file mode 100644
index a023518..0000000
--- a/test/descriptor_server.dart
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2016, 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.
-
-// @dart=2.10
-
-import 'dart:async';
-
-import 'package:path/path.dart' as p;
-import 'package:shelf/shelf.dart' as shelf;
-import 'package:shelf/shelf_io.dart' as shelf_io;
-import 'package:test/test.dart' hide fail;
-
-import 'descriptor.dart' as d;
-
-/// The global [DescriptorServer] that's used by default.
-///
-/// `null` if there's no global server in use. This can be set to replace the
-/// existing global server.
-DescriptorServer get globalServer => _globalServer;
-set globalServer(DescriptorServer value) {
-  if (_globalServer == null) {
-    addTearDown(() {
-      _globalServer = null;
-    });
-  } else {
-    expect(_globalServer.close(), completes);
-  }
-
-  _globalServer = value;
-}
-
-DescriptorServer _globalServer;
-
-/// Creates a global [DescriptorServer] to serve [contents] as static files.
-///
-/// This server will exist only for the duration of the pub run. It's accessible
-/// via [server]. Subsequent calls to [serve] replace the previous server.
-Future serve([List<d.Descriptor> contents]) async {
-  globalServer = (await DescriptorServer.start())..contents.addAll(contents);
-}
-
-class DescriptorServer {
-  /// The underlying server.
-  final shelf.Server _server;
-
-  /// A future that will complete to the port used for the server.
-  int get port => _server.url.port;
-
-  /// The list of paths that have been requested from this server.
-  final requestedPaths = <String>[];
-
-  /// The base directory descriptor of the directories served by [this].
-  final d.DirectoryDescriptor _baseDir;
-
-  /// The descriptors served by this server.
-  ///
-  /// This can safely be modified between requests.
-  List<d.Descriptor> get contents => _baseDir.contents;
-
-  /// Handlers for requests not easily described as files.
-  final Map<Pattern, shelf.Handler> extraHandlers = {};
-
-  /// Creates an HTTP server to serve [contents] as static files.
-  ///
-  /// This server exists only for the duration of the pub run. Subsequent calls
-  /// to [serve] replace the previous server.
-  static Future<DescriptorServer> start() async =>
-      DescriptorServer._(await shelf_io.IOServer.bind('localhost', 0));
-
-  /// Creates a server that reports an error if a request is ever received.
-  static Future<DescriptorServer> errors() async =>
-      DescriptorServer._(await shelf_io.IOServer.bind('localhost', 0));
-
-  DescriptorServer._(this._server) : _baseDir = d.dir('serve-dir', []) {
-    _server.mount((request) async {
-      final pathWithInitialSlash = '/${request.url.path}';
-      final key = extraHandlers.keys.firstWhere((pattern) {
-        final match = pattern.matchAsPrefix(pathWithInitialSlash);
-        return match != null && match.end == pathWithInitialSlash.length;
-      }, orElse: () => null);
-      if (key != null) return extraHandlers[key](request);
-
-      var path = p.posix.fromUri(request.url.path);
-      requestedPaths.add(path);
-
-      try {
-        var stream = await _validateStream(_baseDir.load(path));
-        return shelf.Response.ok(stream);
-      } catch (_) {
-        return shelf.Response.notFound('File "$path" not found.');
-      }
-    });
-    addTearDown(_server.close);
-  }
-
-  /// Closes this server.
-  Future close() => _server.close();
-}
-
-/// Ensures that [stream] can emit at least one value successfully (or close
-/// without any values).
-///
-/// For example, reading asynchronously from a non-existent file will return a
-/// stream that fails on the first chunk. In order to handle that more
-/// gracefully, you may want to check that the stream looks like it's working
-/// before you pipe the stream to something else.
-///
-/// This lets you do that. It returns a [Future] that completes to a [Stream]
-/// emitting the same values and errors as [stream], but only if at least one
-/// value can be read successfully. If an error occurs before any values are
-/// emitted, the returned Future completes to that error.
-Future<Stream<T>> _validateStream<T>(Stream<T> stream) {
-  var completer = Completer<Stream<T>>();
-  var controller = StreamController<T>(sync: true);
-
-  StreamSubscription subscription;
-  subscription = stream.listen((value) {
-    // We got a value, so the stream is valid.
-    if (!completer.isCompleted) completer.complete(controller.stream);
-    controller.add(value);
-  }, onError: (error, [StackTrace stackTrace]) {
-    // If the error came after values, it's OK.
-    if (completer.isCompleted) {
-      controller.addError(error, stackTrace);
-      return;
-    }
-
-    // Otherwise, the error came first and the stream is invalid.
-    completer.completeError(error, stackTrace);
-
-    // We won't be returning the stream at all in this case, so unsubscribe
-    // and swallow the error.
-    subscription.cancel();
-  }, onDone: () {
-    // It closed with no errors, so the stream is valid.
-    if (!completer.isCompleted) completer.complete(controller.stream);
-    controller.close();
-  });
-
-  return completer.future;
-}
diff --git a/test/dev_dependency_test.dart b/test/dev_dependency_test.dart
index 471f53c..b192444 100644
--- a/test/dev_dependency_test.dart
+++ b/test/dev_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import 'descriptor.dart' as d;
@@ -29,7 +27,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo', 'bar': '../bar'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+    ]).validate();
   });
 
   test("includes dev dependency's transitive dependencies", () async {
@@ -54,7 +55,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo', 'bar': '../bar'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+    ]).validate();
   });
 
   test("ignores transitive dependency's dev dependencies", () async {
@@ -80,6 +84,8 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
   });
 }
diff --git a/test/directory_option_test.dart b/test/directory_option_test.dart
index c993200..65193bf 100644
--- a/test/directory_option_test.dart
+++ b/test/directory_option_test.dart
@@ -2,33 +2,33 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:path/path.dart' as p;
 import 'package:shelf/shelf.dart' as shelf;
-import 'package:test/test.dart';
 
 import 'descriptor.dart';
 import 'golden_file.dart';
 import 'test_pub.dart';
 
 Future<void> main() async {
-  test('commands taking a --directory/-C parameter work', () async {
-    await servePackages((b) => b
+  testWithGolden('commands taking a --directory/-C parameter work',
+      (ctx) async {
+    await servePackages()
       ..serve('foo', '1.0.0')
       ..serve('foo', '0.1.2')
-      ..serve('bar', '1.2.3'));
-    await credentialsFile(globalPackageServer, 'access token').create();
-    globalPackageServer
-        .extraHandlers[RegExp('/api/packages/test_pkg/uploaders')] = (request) {
-      return shelf.Response.ok(
-          jsonEncode({
-            'success': {'message': 'Good job!'}
-          }),
-          headers: {'content-type': 'application/json'});
-    };
+      ..serve('bar', '1.2.3');
+    await credentialsFile(globalServer, 'access token').create();
+    globalServer.handle(
+      RegExp('/api/packages/test_pkg/uploaders'),
+      (request) {
+        return shelf.Response.ok(
+            jsonEncode({
+              'success': {'message': 'Good job!'}
+            }),
+            headers: {'content-type': 'application/json'});
+      },
+    );
 
     await validPackage.create();
     await dir(appPath, [
@@ -56,42 +56,34 @@
         })
       ]),
     ]).create();
-    final buffer = StringBuffer();
-    Future<void> run(List<String> args) async {
-      await runPubIntoBuffer(
-        args,
-        buffer,
+
+    final cases = [
+      // Try --directory after command.
+      ['add', '--directory=$appPath', 'foo'],
+      // Try the top-level version also.
+      ['-C', appPath, 'add', 'bar'],
+      // When both top-level and after command, the one after command takes
+      // precedence.
+      ['-C', p.join(appPath, 'example'), 'get', '--directory=$appPath', 'bar'],
+      ['remove', 'bar', '-C', appPath],
+      ['get', 'bar', '-C', appPath],
+      ['get', 'bar', '-C', '$appPath/example'],
+      ['get', 'bar', '-C', '$appPath/example2'],
+      ['get', 'bar', '-C', '$appPath/broken_dir'],
+      ['downgrade', '-C', appPath],
+      ['upgrade', 'bar', '-C', appPath],
+      ['run', '-C', appPath, 'bin/app.dart'],
+      ['publish', '-C', appPath, '--dry-run'],
+      ['uploader', '-C', appPath, 'add', 'sigurdm@google.com'],
+      ['deps', '-C', appPath],
+    ];
+
+    for (var i = 0; i < cases.length; i++) {
+      await ctx.run(
+        cases[i],
         workingDirectory: sandbox,
         environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'},
       );
     }
-
-    await run(['add', '--directory=$appPath', 'foo']);
-    // Try the top-level version also.
-    await run(['-C', appPath, 'add', 'bar']);
-    // When both top-level and after command, the one after command takes
-    // precedence.
-    await run([
-      '-C',
-      p.join(appPath, 'example'),
-      'get',
-      '--directory=$appPath',
-      'bar',
-    ]);
-    await run(['remove', 'bar', '-C', appPath]);
-    await run(['get', 'bar', '-C', appPath]);
-    await run(['get', 'bar', '-C', '$appPath/example']);
-    await run(['get', 'bar', '-C', '$appPath/example2']);
-    await run(['get', 'bar', '-C', '$appPath/broken_dir']);
-    await run(['downgrade', '-C', appPath]);
-    await run(['upgrade', 'bar', '-C', appPath]);
-    await run(['run', '-C', appPath, 'bin/app.dart']);
-    await run(['publish', '-C', appPath, '--dry-run']);
-    await run(['uploader', '-C', appPath, 'add', 'sigurdm@google.com']);
-    await run(['deps', '-C', appPath]);
-    // TODO(sigurdm): we should also test `list-package-dirs` - it is a bit
-    // hard on windows due to quoted back-slashes on windows.
-    expectMatchesGoldenFile(
-        buffer.toString(), 'test/goldens/directory_option.txt');
   });
 }
diff --git a/test/downgrade/does_not_show_other_versions_test.dart b/test/downgrade/does_not_show_other_versions_test.dart
index 485debb..884bbaf 100644
--- a/test/downgrade/does_not_show_other_versions_test.dart
+++ b/test/downgrade/does_not_show_other_versions_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('does not show how many other versions are available', () async {
-    await servePackages((builder) {
-      builder.serve('downgraded', '1.0.0');
-      builder.serve('downgraded', '2.0.0');
-      builder.serve('downgraded', '3.0.0-dev');
-    });
+    await servePackages()
+      ..serve('downgraded', '1.0.0')
+      ..serve('downgraded', '2.0.0')
+      ..serve('downgraded', '3.0.0-dev');
 
     await d.appDir({'downgraded': '3.0.0-dev'}).create();
 
diff --git a/test/downgrade/doesnt_change_git_dependencies_test.dart b/test/downgrade/doesnt_change_git_dependencies_test.dart
index 41f0a51..1450af5 100644
--- a/test/downgrade/doesnt_change_git_dependencies_test.dart
+++ b/test/downgrade/doesnt_change_git_dependencies_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/downgrade/dry_run_does_not_apply_changes_test.dart b/test/downgrade/dry_run_does_not_apply_changes_test.dart
index 765cb35..38fbebe 100644
--- a/test/downgrade/dry_run_does_not_apply_changes_test.dart
+++ b/test/downgrade/dry_run_does_not_apply_changes_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -13,10 +11,9 @@
 
 void main() {
   test('--dry-run shows report but does not apply changes', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0');
 
     // Create the first lockfile.
     await d.appDir({'foo': '2.0.0'}).create();
diff --git a/test/downgrade/unlock_if_necessary_test.dart b/test/downgrade/unlock_if_necessary_test.dart
index b8aa83e..c8b55af 100644
--- a/test/downgrade/unlock_if_necessary_test.dart
+++ b/test/downgrade/unlock_if_necessary_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -13,24 +11,27 @@
   test(
       "downgrades one locked hosted package's dependencies if it's "
       'necessary', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.0.0', deps: {'foo_dep': 'any'});
-      builder.serve('foo_dep', '2.0.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '2.0.0', deps: {'foo_dep': 'any'});
+    server.serve('foo_dep', '2.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '2.0.0', 'foo_dep': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '2.0.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '1.0.0', deps: {'foo_dep': '<2.0.0'});
-      builder.serve('foo_dep', '1.0.0');
-    });
+    server.serve('foo', '1.0.0', deps: {'foo_dep': '<2.0.0'});
+    server.serve('foo_dep', '1.0.0');
 
     await pubDowngrade(args: ['foo']);
 
-    await d.appPackagesFile({'foo': '1.0.0', 'foo_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/downgrade/unlock_single_package_test.dart b/test/downgrade/unlock_single_package_test.dart
index 73c3467..1188e41 100644
--- a/test/downgrade/unlock_single_package_test.dart
+++ b/test/downgrade/unlock_single_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -11,51 +9,60 @@
 
 void main() {
   test('can unlock a single package only in downgrade', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.1.0', deps: {'bar': '>1.0.0'});
-      builder.serve('bar', '2.1.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '2.1.0', deps: {'bar': '>1.0.0'});
+    server.serve('bar', '2.1.0');
 
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
     await pubGet();
-    await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.1.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
-      builder.serve('bar', '1.0.0');
-    });
+    server.serve('foo', '1.0.0', deps: {'bar': 'any'});
+    server.serve('bar', '1.0.0');
 
     await pubDowngrade(args: ['bar']);
-    await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.1.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '2.0.0', deps: {'bar': 'any'});
-      builder.serve('bar', '2.0.0');
-    });
+    server.serve('foo', '2.0.0', deps: {'bar': 'any'});
+    server.serve('bar', '2.0.0');
 
     await pubDowngrade(args: ['bar']);
-    await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+    ]).validate();
 
     await pubDowngrade();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('will not downgrade below constraint #2629', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '2.1.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '2.1.0');
 
     await d.appDir({'foo': '^2.0.0'}).create();
 
     await pubGet();
-
-    await d.appPackagesFile({'foo': '2.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+    ]).validate();
 
     await pubDowngrade(args: ['foo']);
 
-    await d.appPackagesFile({'foo': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/embedding/embedding_test.dart b/test/embedding/embedding_test.dart
index 257bf4b..a9795ea 100644
--- a/test/embedding/embedding_test.dart
+++ b/test/embedding/embedding_test.dart
@@ -2,102 +2,329 @@
 // 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.
 
-// @dart=2.10
-
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 import 'package:test_process/test_process.dart';
+
 import '../descriptor.dart' as d;
 import '../golden_file.dart';
 import '../test_pub.dart';
 
-const _command_runner = 'tool/test-bin/pub_command_runner.dart';
+const _commandRunner = 'tool/test-bin/pub_command_runner.dart';
 
-String snapshot;
+late String snapshot;
+
+final logFile = p.join(d.sandbox, cachePath, 'log', 'pub_log.txt');
 
 /// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and appends the output to [buffer].
-Future<void> runEmbedding(List<String> args, StringBuffer buffer,
-    {String workingDirextory,
-    Map<String, String> environment,
-    dynamic exitCode = 0}) async {
+Future<void> runEmbeddingToBuffer(
+  List<String> args,
+  StringBuffer buffer, {
+  String? workingDirectory,
+  Map<String, String>? environment,
+  dynamic exitCode = 0,
+}) async {
   final process = await TestProcess.start(
     Platform.resolvedExecutable,
-    [snapshot, ...args],
-    environment: environment,
-    workingDirectory: workingDirextory,
+    ['--enable-asserts', snapshot, ...args],
+    environment: {
+      ...getPubTestEnvironment(),
+      ...?environment,
+    },
+    workingDirectory: workingDirectory,
   );
   await process.shouldExit(exitCode);
 
   buffer.writeln([
-    '\$ $_command_runner ${args.join(' ')}',
-    ...await process.stdout.rest.toList(),
+    '\$ $_commandRunner ${args.join(' ')}',
+    ...await process.stdout.rest.map(_filter).toList(),
+    ...await process.stderr.rest.map((e) => '[E] ${_filter(e)}').toList(),
   ].join('\n'));
-  final stdErr = await process.stderr.rest.toList();
-  if (stdErr.isNotEmpty) {
-    buffer.writeln(stdErr.map((e) => '[E] $e').join('\n'));
-  }
   buffer.write('\n');
 }
 
+extension on GoldenTestContext {
+  /// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and compare to
+  /// next section in golden file.
+  Future<void> runEmbedding(
+    List<String> args, {
+    String? workingDirectory,
+    Map<String, String>? environment,
+    dynamic exitCode = 0,
+  }) async {
+    final buffer = StringBuffer();
+    await runEmbeddingToBuffer(
+      args,
+      buffer,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      exitCode: exitCode,
+    );
+
+    expectNextSection(buffer.toString());
+  }
+}
+
 Future<void> main() async {
   setUpAll(() async {
     final tempDir = Directory.systemTemp.createTempSync();
     snapshot = path.join(tempDir.path, 'command_runner.dart.snapshot');
     final r = Process.runSync(
-        Platform.resolvedExecutable, ['--snapshot=$snapshot', _command_runner]);
+        Platform.resolvedExecutable, ['--snapshot=$snapshot', _commandRunner]);
     expect(r.exitCode, 0, reason: r.stderr);
   });
 
   tearDownAll(() {
     File(snapshot).parent.deleteSync(recursive: true);
   });
-  test('help text', () async {
-    final buffer = StringBuffer();
-    await runEmbedding([''], buffer, exitCode: 64);
-    await runEmbedding(['--help'], buffer);
-    await runEmbedding(['pub'], buffer, exitCode: 64);
-    await runEmbedding(['pub', '--help'], buffer);
-    await runEmbedding(['pub', 'get', '--help'], buffer);
-    await runEmbedding(['pub', 'global'], buffer, exitCode: 64);
-    expectMatchesGoldenFile(
-        buffer.toString(), 'test/embedding/goldens/helptext.txt');
-  });
 
-  test('run works, though hidden', () async {
-    final buffer = StringBuffer();
+  testWithGolden('run works, though hidden', (ctx) async {
+    await servePackages();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
         'environment': {
-          'sdk': '>=2.0.0 <3.0.0',
+          'sdk': '0.1.2+3',
         },
       }),
       d.dir('bin', [
         d.file('main.dart', '''
 import 'dart:io';
 main() { 
-  print("Hi");
+  print('Hi');
   exit(123);
 }
 ''')
       ]),
     ]).create();
-    await runEmbedding(
+    await ctx.runEmbedding(
       ['pub', 'get'],
-      buffer,
-      workingDirextory: d.path(appPath),
+      workingDirectory: d.path(appPath),
     );
-    await runEmbedding(
+    await ctx.runEmbedding(
       ['pub', 'run', 'bin/main.dart'],
-      buffer,
       exitCode: 123,
-      workingDirextory: d.path(appPath),
-    );
-    expectMatchesGoldenFile(
-      buffer.toString(),
-      'test/embedding/goldens/run.txt',
+      workingDirectory: d.path(appPath),
     );
   });
+
+  testWithGolden(
+      'logfile is written with --verbose and on unexpected exceptions',
+      (context) async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    await d.appDir({'foo': 'any'}).create();
+
+    // TODO(sigurdm) This logs the entire verbose trace to a golden file.
+    //
+    // This is fragile, and can break for all sorts of small reasons. We think
+    // this might be worth while having to have at least minimal testing of the
+    // verbose stack trace.
+    //
+    // But if you, future contributor, think this test is annoying: feel free to
+    // remove it, or rewrite it to filter out the stack-trace itself, only
+    // testing for creation of the file.
+    //
+    //  It is a fragile test, and we acknowledge that it's usefulness can be
+    //  debated...
+    await context.runEmbedding(
+      ['pub', '--verbose', 'get'],
+      workingDirectory: d.path(appPath),
+    );
+    context.expectNextSection(
+      _filter(
+        File(logFile).readAsStringSync(),
+      ),
+    );
+    await d.dir('empty').create();
+    await context.runEmbedding(
+      ['pub', 'fail'],
+      workingDirectory: d.path('empty'),
+      exitCode: 1,
+    );
+    context.expectNextSection(
+      _filter(
+        File(logFile).readAsStringSync(),
+      ),
+    );
+  });
+
+  test('analytics', () async {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': 'any'})
+      ..serve('bar', '1.0.0');
+    await d.dir('dep', [
+      d.pubspec({
+        'name': 'dep',
+        'environment': {'sdk': '>=0.0.0 <3.0.0'}
+      })
+    ]).create();
+    final app = d.dir(appPath, [
+      d.appPubspec({
+        'foo': '1.0.0',
+        // The path dependency should not go to analytics.
+        'dep': {'path': '../dep'}
+      })
+    ]);
+    await app.create();
+
+    final buffer = StringBuffer();
+
+    await runEmbeddingToBuffer(
+      ['pub', 'get'],
+      buffer,
+      workingDirectory: app.io.path,
+      environment: {...getPubTestEnvironment(), '_PUB_LOG_ANALYTICS': 'true'},
+    );
+    final analytics = buffer
+        .toString()
+        .split('\n')
+        .where((line) => line.startsWith('[E] [analytics]: '))
+        .map((line) => json.decode(line.substring('[E] [analytics]: '.length)));
+    expect(analytics, {
+      {
+        'hitType': 'event',
+        'message': {
+          'category': 'pub-get',
+          'action': 'foo',
+          'label': '1.0.0',
+          'value': 1,
+          'cd1': 'direct',
+          'ni': '1',
+        }
+      },
+      {
+        'hitType': 'event',
+        'message': {
+          'category': 'pub-get',
+          'action': 'bar',
+          'label': '1.0.0',
+          'value': 1,
+          'cd1': 'transitive',
+          'ni': '1',
+        }
+      },
+      {
+        'hitType': 'timing',
+        'message': {
+          'variableName': 'resolution',
+          'time': isA<int>(),
+          'category': 'pub-get',
+          'label': null
+        }
+      },
+    });
+    // Don't write the logs to file on a normal run.
+    expect(File(logFile).existsSync(), isFalse);
+  });
+
+  test('`embedding --verbose pub` is verbose', () async {
+    await servePackages();
+    final buffer = StringBuffer();
+    await runEmbeddingToBuffer(['--verbose', 'pub', 'logout'], buffer);
+    expect(buffer.toString(), contains('FINE: Pub 0.1.2+3'));
+  });
+}
+
+String _filter(String input) {
+  return input
+      .replaceAll(p.toUri(d.sandbox).toString(), r'file://$SANDBOX')
+      .replaceAll(d.sandbox, r'$SANDBOX')
+      .replaceAll(Platform.pathSeparator, '/')
+      .replaceAll(Platform.operatingSystem, r'$OS')
+      .replaceAll(globalServer.port.toString(), r'$PORT')
+      .replaceAll(
+        RegExp(r'^Created:(.*)$', multiLine: true),
+        r'Created: $TIME',
+      )
+      .replaceAll(
+        RegExp(r'Generated by pub on (.*)$', multiLine: true),
+        r'Generated by pub on $TIME',
+      )
+      .replaceAll(
+        RegExp(r'X-Pub-Session-ID(.*)$', multiLine: true),
+        r'X-Pub-Session-ID: $ID',
+      )
+      .replaceAll(
+        RegExp(r'took (.*)$', multiLine: true),
+        r'took: $TIME',
+      )
+      .replaceAll(
+        RegExp(r'date: (.*)$', multiLine: true),
+        r'date: $TIME',
+      )
+      .replaceAll(
+        RegExp(r'Creating (.*) from stream\.$', multiLine: true),
+        r'Creating $FILE from stream',
+      )
+      .replaceAll(
+        RegExp(r'Created (.*) from stream\.$', multiLine: true),
+        r'Created $FILE from stream',
+      )
+      .replaceAll(
+        RegExp(r'Renaming directory $SANDBOX/cache/_temp/(.*?) to',
+            multiLine: true),
+        r'Renaming directory $SANDBOX/cache/_temp/',
+      )
+      .replaceAll(
+        RegExp(r'Extracting .tar.gz stream to (.*?)$', multiLine: true),
+        r'Extracting .tar.gz stream to $DIR',
+      )
+      .replaceAll(
+        RegExp(r'Extracted .tar.gz to (.*?)$', multiLine: true),
+        r'Extracted .tar.gz to $DIR',
+      )
+      .replaceAll(
+        RegExp(r'Reading binary file (.*?)$', multiLine: true),
+        r'Reading binary file $FILE.',
+      )
+      .replaceAll(
+        RegExp(r'Deleting directory (.*)$', multiLine: true),
+        r'Deleting directory $DIR',
+      )
+      .replaceAll(
+        RegExp(r'Deleting directory (.*)$', multiLine: true),
+        r'Deleting directory $DIR',
+      )
+      .replaceAll(
+        RegExp(r'Resolving dependencies finished (.*)$', multiLine: true),
+        r'Resolving dependencies finished ($TIME)',
+      )
+      .replaceAll(
+        RegExp(r'Created temp directory (.*)$', multiLine: true),
+        r'Created temp directory $DIR',
+      )
+      .replaceAll(
+        RegExp(r'Renaming directory (.*)$', multiLine: true),
+        r'Renaming directory $A to $B',
+      )
+      .replaceAll(
+        RegExp(r'"_fetchedAt":"(.*)"}$', multiLine: true),
+        r'"_fetchedAt": "$TIME"}',
+      )
+      .replaceAll(
+        RegExp(r'"generated": "(.*)",$', multiLine: true),
+        r'"generated": "$TIME",',
+      )
+      .replaceAll(
+        RegExp(r'( |^)(/|[A-Z]:)(.*)/tool/test-bin/pub_command_runner.dart',
+            multiLine: true),
+        r' tool/test-bin/pub_command_runner.dart',
+      )
+      .replaceAll(
+        RegExp(r'[ ]{4,}', multiLine: true),
+        r'   ',
+      )
+      .replaceAll(
+        RegExp(r' [\d]+:[\d]+ ', multiLine: true),
+        r' $LINE:$COL ',
+      )
+      .replaceAll(
+        RegExp(r'Writing \d+ characters', multiLine: true),
+        r'Writing $N characters',
+      );
 }
diff --git a/test/embedding/get_executable_for_command_test.dart b/test/embedding/get_executable_for_command_test.dart
index e174f3a..0edfa72 100644
--- a/test/embedding/get_executable_for_command_test.dart
+++ b/test/embedding/get_executable_for_command_test.dart
@@ -2,22 +2,31 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' show separator;
 import 'package:path/path.dart' as p;
 import 'package:pub/pub.dart';
+import 'package:pub/src/log.dart' as log;
+
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-Future<void> testGetExecutable(String command, String root,
-    {allowSnapshot = true, result, errorMessage}) async {
+Future<void> testGetExecutable(
+  String command,
+  String root, {
+  allowSnapshot = true,
+  executable,
+  packageConfig,
+  errorMessage,
+  CommandResolutionIssue? issue,
+}) async {
   final _cachePath = getPubTestEnvironment()['PUB_CACHE'];
-  if (result == null) {
+  final oldVerbosity = log.verbosity;
+  log.verbosity = log.Verbosity.none;
+  if (executable == null) {
     expect(
       () => getExecutableForCommand(
         command,
@@ -27,18 +36,25 @@
       ),
       throwsA(
         isA<CommandResolutionFailedException>()
-            .having((e) => e.message, 'message', errorMessage),
+            .having((e) => e.message, 'message', errorMessage)
+            .having((e) => e.issue, 'issue', issue),
       ),
     );
   } else {
-    final path = await getExecutableForCommand(
+    final e = await getExecutableForCommand(
       command,
       root: root,
       pubCacheDir: _cachePath,
       allowSnapshot: allowSnapshot,
     );
-    expect(path, result);
-    expect(File(p.join(root, path)).existsSync(), true);
+    expect(
+      e,
+      isA<DartExecutableWithPackageConfig>()
+          .having((e) => e.executable, 'executable', executable)
+          .having((e) => e.packageConfig, 'packageConfig', packageConfig),
+    );
+    expect(File(p.join(root, e.executable)).existsSync(), true);
+    log.verbosity = oldVerbosity;
   }
 }
 
@@ -50,13 +66,13 @@
     final dir = d.path('foo');
 
     await testGetExecutable('bar/bar.dart', dir,
-        result: p.join('bar', 'bar.dart'));
+        executable: p.join('bar', 'bar.dart'));
 
     await testGetExecutable(p.join('bar', 'bar.dart'), dir,
-        result: p.join('bar', 'bar.dart'));
+        executable: p.join('bar', 'bar.dart'));
 
     await testGetExecutable('${p.toUri(dir)}/bar/bar.dart', dir,
-        result: p.join('bar', 'bar.dart'));
+        executable: p.join('bar', 'bar.dart'));
   });
 
   test('Looks for file when no pubspec.yaml', () async {
@@ -66,9 +82,11 @@
     final dir = d.path('foo');
 
     await testGetExecutable('bar/m.dart', dir,
-        errorMessage: contains('Could not find file `bar/m.dart`'));
+        errorMessage: contains('Could not find file `bar/m.dart`'),
+        issue: CommandResolutionIssue.fileNotFound);
     await testGetExecutable(p.join('bar', 'm.dart'), dir,
-        errorMessage: contains('Could not find file `bar${separator}m.dart`'));
+        errorMessage: contains('Could not find file `bar${separator}m.dart`'),
+        issue: CommandResolutionIssue.fileNotFound);
   });
 
   test('Error message when pubspec is broken', () async {
@@ -95,13 +113,15 @@
             contains(
                 'Error on line 1, column 9 of ${d.sandbox}${p.separator}foo${p.separator}pubspec.yaml: "name" field must be a valid Dart identifier.'),
             contains(
-                '{"name":"broken name","environment":{"sdk":">=0.1.2 <1.0.0"}}')));
+                '{"name":"broken name","environment":{"sdk":">=0.1.2 <1.0.0"}}')),
+        issue: CommandResolutionIssue.pubGetFailed);
   });
 
   test('Does `pub get` if there is a pubspec.yaml', () async {
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
+        'environment': {'sdk': '>=$_currentVersion <3.0.0'},
         'dependencies': {'foo': '^1.0.0'}
       }),
       d.dir('bin', [
@@ -109,23 +129,64 @@
       ])
     ]).create();
 
-    await serveNoPackages();
+    await servePackages();
     // The solver uses word-wrapping in its error message, so we use \s to
     // accomodate.
-    await testGetExecutable('bar/m.dart', d.path(appPath),
-        errorMessage: matches(r'version\s+solving\s+failed'));
+    await testGetExecutable(
+      'bar/m.dart',
+      d.path(appPath),
+      errorMessage: matches(r'version\s+solving\s+failed'),
+      issue: CommandResolutionIssue.pubGetFailed,
+    );
+  });
+
+  test('Reports parse failure', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=$_currentVersion <3.0.0'},
+      }),
+    ]).create();
+    await testGetExecutable(
+      '::',
+      d.path(appPath),
+      errorMessage: contains(r'cannot contain multiple ":"'),
+      issue: CommandResolutionIssue.parseError,
+    );
+  });
+
+  test('Reports compilation failure', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=$_currentVersion <3.0.0'},
+      }),
+      d.dir('bin', [
+        d.file('foo.dart', 'main() {'),
+      ])
+    ]).create();
+
+    await servePackages();
+    // The solver uses word-wrapping in its error message, so we use \s to
+    // accomodate.
+    await testGetExecutable(
+      ':foo',
+      d.path(appPath),
+      errorMessage: matches(r'foo.dart:1:8:'),
+      issue: CommandResolutionIssue.compilationFailed,
+    );
   });
 
   test('Finds files', () async {
-    await servePackages((b) => b
-      ..serve('foo', '1.0.0', pubspec: {
-        'environment': {'sdk': '>=$_currentVersion <3.0.0'}
-      }, contents: [
-        d.dir('bin', [
-          d.file('foo.dart', 'main() {print(42);}'),
-          d.file('tool.dart', 'main() {print(42);}')
-        ])
-      ]));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'environment': {'sdk': '>=$_currentVersion <3.0.0'}
+    }, contents: [
+      d.dir('bin', [
+        d.file('foo.dart', 'main() {print(42);}'),
+        d.file('tool.dart', 'main() {print(42);}')
+      ])
+    ]);
 
     await d.dir(appPath, [
       d.pubspec({
@@ -148,46 +209,81 @@
     ]).create();
     final dir = d.path(appPath);
 
-    await testGetExecutable('myapp', dir,
-        result: p.join('.dart_tool', 'pub', 'bin', 'myapp',
-            'myapp.dart-$_currentVersion.snapshot'));
-    await testGetExecutable('myapp:myapp', dir,
-        result: p.join('.dart_tool', 'pub', 'bin', 'myapp',
-            'myapp.dart-$_currentVersion.snapshot'));
-    await testGetExecutable(':myapp', dir,
-        result: p.join('.dart_tool', 'pub', 'bin', 'myapp',
-            'myapp.dart-$_currentVersion.snapshot'));
-    await testGetExecutable(':tool', dir,
-        result: p.join('.dart_tool', 'pub', 'bin', 'myapp',
-            'tool.dart-$_currentVersion.snapshot'));
-    await testGetExecutable('foo', dir,
-        allowSnapshot: false,
-        result: endsWith('foo-1.0.0${separator}bin${separator}foo.dart'));
-    await testGetExecutable('foo', dir,
-        result:
-            '.dart_tool${separator}pub${separator}bin${separator}foo${separator}foo.dart-$_currentVersion.snapshot');
-    await testGetExecutable('foo:tool', dir,
-        allowSnapshot: false,
-        result: endsWith('foo-1.0.0${separator}bin${separator}tool.dart'));
-    await testGetExecutable('foo:tool', dir,
-        result:
-            '.dart_tool${separator}pub${separator}bin${separator}foo${separator}tool.dart-$_currentVersion.snapshot');
+    await testGetExecutable(
+      'myapp',
+      dir,
+      executable: p.join('.dart_tool', 'pub', 'bin', 'myapp',
+          'myapp.dart-$_currentVersion.snapshot'),
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      'myapp:myapp',
+      dir,
+      executable: p.join('.dart_tool', 'pub', 'bin', 'myapp',
+          'myapp.dart-$_currentVersion.snapshot'),
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      ':myapp',
+      dir,
+      executable: p.join('.dart_tool', 'pub', 'bin', 'myapp',
+          'myapp.dart-$_currentVersion.snapshot'),
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      ':tool',
+      dir,
+      executable: p.join('.dart_tool', 'pub', 'bin', 'myapp',
+          'tool.dart-$_currentVersion.snapshot'),
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      'foo',
+      dir,
+      allowSnapshot: false,
+      executable: endsWith('foo-1.0.0${separator}bin${separator}foo.dart'),
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      'foo',
+      dir,
+      executable:
+          '.dart_tool${separator}pub${separator}bin${separator}foo${separator}foo.dart-$_currentVersion.snapshot',
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      'foo:tool',
+      dir,
+      allowSnapshot: false,
+      executable: endsWith('foo-1.0.0${separator}bin${separator}tool.dart'),
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
+    await testGetExecutable(
+      'foo:tool',
+      dir,
+      executable:
+          '.dart_tool${separator}pub${separator}bin${separator}foo${separator}tool.dart-$_currentVersion.snapshot',
+      packageConfig: p.join('.dart_tool', 'package_config.json'),
+    );
     await testGetExecutable(
       'unknown:tool',
       dir,
       errorMessage: 'Could not find package `unknown` or file `unknown:tool`',
+      issue: CommandResolutionIssue.packageNotFound,
     );
     await testGetExecutable(
       'foo:unknown',
       dir,
       errorMessage:
           'Could not find `bin${separator}unknown.dart` in package `foo`.',
+      issue: CommandResolutionIssue.noBinaryFound,
     );
     await testGetExecutable(
       'unknownTool',
       dir,
       errorMessage:
           'Could not find package `unknownTool` or file `unknownTool`',
+      issue: CommandResolutionIssue.packageNotFound,
     );
   });
 }
diff --git a/test/embedding/goldens/helptext.txt b/test/embedding/goldens/helptext.txt
deleted file mode 100644
index b5c206f..0000000
--- a/test/embedding/goldens/helptext.txt
+++ /dev/null
@@ -1,113 +0,0 @@
-$ tool/test-bin/pub_command_runner.dart 
-[E] Could not find a command named "".
-[E] 
-[E] Usage: pub_command_runner <command> [arguments]
-[E] 
-[E] Global options:
-[E] -h, --help    Print this usage information.
-[E] 
-[E] Available commands:
-[E]   pub   Work with packages.
-[E] 
-[E] Run "pub_command_runner help <command>" for more information about a command.
-
-$ tool/test-bin/pub_command_runner.dart --help
-Tests the embeddable pub command.
-
-Usage: pub_command_runner <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  pub   Work with packages.
-
-Run "pub_command_runner help <command>" for more information about a command.
-
-$ tool/test-bin/pub_command_runner.dart pub
-[E] Missing subcommand for "pub_command_runner pub".
-[E] 
-[E] Usage: pub_command_runner pub [arguments...]
-[E] -h, --help               Print this usage information.
-[E]     --[no-]trace         Print debugging information when an error occurs.
-[E] -v, --verbose            Shortcut for "--verbosity=all".
-[E] -C, --directory=<dir>    Run the subcommand in the directory<dir>.
-[E]                          (defaults to ".")
-[E] 
-[E] Available subcommands:
-[E]   add         Add a dependency to pubspec.yaml.
-[E]   cache       Work with the system cache.
-[E]   deps        Print package dependencies.
-[E]   downgrade   Downgrade the current package's dependencies to oldest versions.
-[E]   get         Get the current package's dependencies.
-[E]   global      Work with global packages.
-[E]   login       Log into pub.dev.
-[E]   logout      Log out of pub.dev.
-[E]   outdated    Analyze your dependencies to find which ones can be upgraded.
-[E]   publish     Publish the current package to pub.dartlang.org.
-[E]   remove      Removes a dependency from the current package.
-[E]   token       Manage authentication tokens for hosted pub repositories.
-[E]   upgrade     Upgrade the current package's dependencies to latest versions.
-[E]   uploader    Manage uploaders for a package on pub.dartlang.org.
-[E] 
-[E] Run "pub_command_runner help" to see global options.
-[E] See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
-
-$ tool/test-bin/pub_command_runner.dart pub --help
-Work with packages.
-
-Usage: pub_command_runner pub [arguments...]
--h, --help               Print this usage information.
-    --[no-]trace         Print debugging information when an error occurs.
--v, --verbose            Shortcut for "--verbosity=all".
--C, --directory=<dir>    Run the subcommand in the directory<dir>.
-                         (defaults to ".")
-
-Available subcommands:
-  add         Add a dependency to pubspec.yaml.
-  cache       Work with the system cache.
-  deps        Print package dependencies.
-  downgrade   Downgrade the current package's dependencies to oldest versions.
-  get         Get the current package's dependencies.
-  global      Work with global packages.
-  login       Log into pub.dev.
-  logout      Log out of pub.dev.
-  outdated    Analyze your dependencies to find which ones can be upgraded.
-  publish     Publish the current package to pub.dartlang.org.
-  remove      Removes a dependency from the current package.
-  token       Manage authentication tokens for hosted pub repositories.
-  upgrade     Upgrade the current package's dependencies to latest versions.
-  uploader    Manage uploaders for a package on pub.dartlang.org.
-
-Run "pub_command_runner help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
-
-$ tool/test-bin/pub_command_runner.dart pub get --help
-Get the current package's dependencies.
-
-Usage: pub_command_runner pub get <subcommand> [arguments...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
--C, --directory=<dir>    Run this in the directory<dir>.
-
-Run "pub_command_runner help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-get for detailed documentation.
-
-$ tool/test-bin/pub_command_runner.dart pub global
-[E] Missing subcommand for "pub_command_runner pub global".
-[E] 
-[E] Usage: pub_command_runner pub global [arguments...]
-[E] -h, --help    Print this usage information.
-[E] 
-[E] Available subcommands:
-[E]   activate     Make a package's executables globally available.
-[E]   deactivate   Remove a previously activated package.
-[E]   list         List globally activated packages.
-[E]   run          Run an executable from a globally activated package.
-[E] 
-[E] Run "pub_command_runner help" to see global options.
-[E] See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
-
diff --git a/test/embedding/goldens/run.txt b/test/embedding/goldens/run.txt
deleted file mode 100644
index 6f0f46a..0000000
--- a/test/embedding/goldens/run.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-$ tool/test-bin/pub_command_runner.dart pub get
-Resolving dependencies...
-Got dependencies!
-
-$ tool/test-bin/pub_command_runner.dart pub run bin/main.dart
-Hi
-
diff --git a/test/get/dry_run_does_not_apply_changes_test.dart b/test/get/dry_run_does_not_apply_changes_test.dart
index 1879f51..3e44bf9 100644
--- a/test/get/dry_run_does_not_apply_changes_test.dart
+++ b/test/get/dry_run_does_not_apply_changes_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -11,9 +9,8 @@
 
 void main() {
   test('--dry-run shows but does not apply changes', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await d.appDir({'foo': '1.0.0'}).create();
 
diff --git a/test/get/flutter_constraint_upper_bound_ignored_test.dart b/test/get/flutter_constraint_upper_bound_ignored_test.dart
index 9fc833c..7fd2edc 100644
--- a/test/get/flutter_constraint_upper_bound_ignored_test.dart
+++ b/test/get/flutter_constraint_upper_bound_ignored_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/get/gets_in_example_folder_test.dart b/test/get/gets_in_example_folder_test.dart
index d7f6c57..04f9d47 100644
--- a/test/get/gets_in_example_folder_test.dart
+++ b/test/get/gets_in_example_folder_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.11
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
diff --git a/test/get/git/check_out_and_upgrade_test.dart b/test/get/git/check_out_and_upgrade_test.dart
index 846859b..42044af 100644
--- a/test/get/git/check_out_and_upgrade_test.dart
+++ b/test/get/git/check_out_and_upgrade_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/check_out_branch_test.dart b/test/get/git/check_out_branch_test.dart
index 0b58abf..104c8b9 100644
--- a/test/get/git/check_out_branch_test.dart
+++ b/test/get/git/check_out_branch_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/check_out_revision_test.dart b/test/get/git/check_out_revision_test.dart
index eed9f01..2428d7c 100644
--- a/test/get/git/check_out_revision_test.dart
+++ b/test/get/git/check_out_revision_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/check_out_test.dart b/test/get/git/check_out_test.dart
index 194e980..f3caaa2 100644
--- a/test/get/git/check_out_test.dart
+++ b/test/get/git/check_out_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
diff --git a/test/get/git/check_out_transitive_test.dart b/test/get/git/check_out_transitive_test.dart
index b7e71fa..0587c58 100644
--- a/test/get/git/check_out_transitive_test.dart
+++ b/test/get/git/check_out_transitive_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
diff --git a/test/get/git/check_out_twice_test.dart b/test/get/git/check_out_twice_test.dart
index f9cebd1..b6de388 100644
--- a/test/get/git/check_out_twice_test.dart
+++ b/test/get/git/check_out_twice_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart b/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
index c5fc4aa..dae234d 100644
--- a/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
+++ b/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
diff --git a/test/get/git/check_out_with_trailing_slash_test.dart b/test/get/git/check_out_with_trailing_slash_test.dart
index 16dc07c..5838986 100644
--- a/test/get/git/check_out_with_trailing_slash_test.dart
+++ b/test/get/git/check_out_with_trailing_slash_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/clean_invalid_git_repo_cache_test.dart b/test/get/git/clean_invalid_git_repo_cache_test.dart
index 6ea650b..ab386ab 100644
--- a/test/get/git/clean_invalid_git_repo_cache_test.dart
+++ b/test/get/git/clean_invalid_git_repo_cache_test.dart
@@ -2,7 +2,6 @@
 // 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.
 
-// @dart=2.10
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
@@ -15,11 +14,10 @@
 void _invalidateGitCache(String repo) {
   final cacheDir =
       path.join(d.sandbox, path.joinAll([cachePath, 'git', 'cache']));
-  final Directory fooCacheDir =
-      Directory(cacheDir).listSync().firstWhere((entity) {
+  final fooCacheDir = Directory(cacheDir).listSync().firstWhere((entity) {
     return entity is Directory &&
         entity.path.split(Platform.pathSeparator).last.startsWith(repo);
-  });
+  }) as Directory;
 
   fooCacheDir.deleteSync(recursive: true);
   fooCacheDir.createSync();
diff --git a/test/get/git/dependency_name_match_pubspec_test.dart b/test/get/git/dependency_name_match_pubspec_test.dart
index 2accd62..1545fca 100644
--- a/test/get/git/dependency_name_match_pubspec_test.dart
+++ b/test/get/git/dependency_name_match_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/get/git/different_repo_name_test.dart b/test/get/git/different_repo_name_test.dart
index 65e37eb..1d8f9c4 100644
--- a/test/get/git/different_repo_name_test.dart
+++ b/test/get/git/different_repo_name_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/doesnt_fetch_if_nothing_changes_test.dart b/test/get/git/doesnt_fetch_if_nothing_changes_test.dart
index ec214d4..3d9f040 100644
--- a/test/get/git/doesnt_fetch_if_nothing_changes_test.dart
+++ b/test/get/git/doesnt_fetch_if_nothing_changes_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
diff --git a/test/get/git/git_not_installed_test.dart b/test/get/git/git_not_installed_test.dart
index 13a5601..f00d795 100644
--- a/test/get/git/git_not_installed_test.dart
+++ b/test/get/git/git_not_installed_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 @TestOn('linux')
 import 'dart:io';
 
diff --git a/test/get/git/lock_version_test.dart b/test/get/git/lock_version_test.dart
index 1e51d12..40b2708 100644
--- a/test/get/git/lock_version_test.dart
+++ b/test/get/git/lock_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
diff --git a/test/get/git/locked_revision_without_repo_test.dart b/test/get/git/locked_revision_without_repo_test.dart
index 49c1a11..c00c730 100644
--- a/test/get/git/locked_revision_without_repo_test.dart
+++ b/test/get/git/locked_revision_without_repo_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
diff --git a/test/get/git/path_test.dart b/test/get/git/path_test.dart
index 8bbd9e7..a323be7 100644
--- a/test/get/git/path_test.dart
+++ b/test/get/git/path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:pub/src/lock_file.dart';
@@ -39,9 +37,11 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')),
+    ]).validate();
   });
 
   test('depends on a package in a deep subdirectory', () async {
@@ -73,14 +73,17 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path:
+              pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')),
+    ]).validate();
 
     final lockFile = LockFile.load(
         p.join(d.sandbox, appPath, 'pubspec.lock'), SourceRegistry());
 
-    expect(lockFile.packages['sub'].description['path'], 'sub/dir%25',
+    expect(lockFile.packages['sub']!.description['path'], 'sub/dir%25',
         reason: 'use uris to specify the path relative to the repo');
   });
 
@@ -117,14 +120,17 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path:
+              pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')),
+    ]).validate();
 
     final lockFile = LockFile.load(
         p.join(d.sandbox, appPath, 'pubspec.lock'), SourceRegistry());
 
-    expect(lockFile.packages['sub'].description['path'], 'sub/dir%25',
+    expect(lockFile.packages['sub']!.description['path'], 'sub/dir%25',
         reason: 'use uris to specify the path relative to the repo');
   });
 
@@ -160,10 +166,14 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub1': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir1'),
-      'sub2': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir2')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub1',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir1')),
+      d.packageConfigEntry(
+          name: 'sub2',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir2')),
+    ]).validate();
   });
 
   test('depends on packages in the same subdirectory at different revisions',
@@ -208,9 +218,11 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub1': pathInCache('git/foo-$oldRevision/subdir'),
-      'sub2': pathInCache('git/foo-$newRevision/subdir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub1', path: pathInCache('git/foo-$oldRevision/subdir')),
+      d.packageConfigEntry(
+          name: 'sub2', path: pathInCache('git/foo-$newRevision/subdir')),
+    ]).validate();
   });
 }
diff --git a/test/get/git/require_pubspec_name_test.dart b/test/get/git/require_pubspec_name_test.dart
index e0815b2..adc31e4 100644
--- a/test/get/git/require_pubspec_name_test.dart
+++ b/test/get/git/require_pubspec_name_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/get/git/require_pubspec_test.dart b/test/get/git/require_pubspec_test.dart
index fb1dd56..0f75c75 100644
--- a/test/get/git/require_pubspec_test.dart
+++ b/test/get/git/require_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/stay_locked_if_compatible_test.dart b/test/get/git/stay_locked_if_compatible_test.dart
index 615a11b..d7f6eb9 100644
--- a/test/get/git/stay_locked_if_compatible_test.dart
+++ b/test/get/git/stay_locked_if_compatible_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/git/unlock_if_incompatible_test.dart b/test/get/git/unlock_if_incompatible_test.dart
index 1a97136..61b60e6 100644
--- a/test/get/git/unlock_if_incompatible_test.dart
+++ b/test/get/git/unlock_if_incompatible_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/get/hosted/avoid_network_requests_test.dart b/test/get/hosted/avoid_network_requests_test.dart
index 4a0cd90..c62fa7c 100644
--- a/test/get/hosted/avoid_network_requests_test.dart
+++ b/test/get/hosted/avoid_network_requests_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,14 +9,13 @@
 
 void main() {
   test('only requests versions that are needed during solving', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.0');
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '1.1.0');
-      builder.serve('bar', '1.2.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '1.1.0')
+      ..serve('foo', '1.2.0')
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '1.1.0')
+      ..serve('bar', '1.2.0');
 
     await d.appDir({'foo': 'any'}).create();
 
@@ -34,8 +31,10 @@
 
     // Run the solver again.
     await pubGet();
-
-    await d.appPackagesFile({'foo': '1.2.0', 'bar': '1.2.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.2.0'),
+    ]).validate();
 
     // The get should not have done any network requests since the lock file is
     // up to date.
diff --git a/test/get/hosted/cached_pubspec_test.dart b/test/get/hosted/cached_pubspec_test.dart
index c4711c8..fa9decc 100644
--- a/test/get/hosted/cached_pubspec_test.dart
+++ b/test/get/hosted/cached_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,7 +9,8 @@
 
 void main() {
   test('does not request a pubspec for a cached package', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({'foo': '1.2.3'}).create();
 
@@ -20,16 +19,18 @@
 
     // Clear the cache. We don't care about anything that was served during
     // the initial get.
-    globalServer.requestedPaths.clear();
+    server.requestedPaths.clear();
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
 
     // Run the solver again now that it's cached.
     await pubGet();
 
     // The get should not have requested the pubspec since it's local already.
-    expect(globalServer.requestedPaths,
+    expect(server.requestedPaths,
         isNot(contains('packages/foo/versions/1.2.3.yaml')));
   });
 }
diff --git a/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart b/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart
index c635399..44fe075 100644
--- a/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart
+++ b/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,24 +11,29 @@
   test(
       "doesn't upgrade dependencies whose constraints have been "
       'removed', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'shared_dep': 'any'});
-      builder.serve('bar', '1.0.0', deps: {'shared_dep': '<2.0.0'});
-      builder.serve('shared_dep', '1.0.0');
-      builder.serve('shared_dep', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'shared_dep': 'any'})
+      ..serve('bar', '1.0.0', deps: {'shared_dep': '<2.0.0'})
+      ..serve('shared_dep', '1.0.0')
+      ..serve('shared_dep', '2.0.0');
 
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '1.0.0', 'bar': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+    ]).validate();
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/does_no_network_requests_when_possible_test.dart b/test/get/hosted/does_no_network_requests_when_possible_test.dart
index bca2c4d..4324776 100644
--- a/test/get/hosted/does_no_network_requests_when_possible_test.dart
+++ b/test/get/hosted/does_no_network_requests_when_possible_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('does not request versions if the lockfile is up to date', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.0');
-    });
+    final server = await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '1.1.0')
+      ..serve('foo', '1.2.0');
 
     await d.appDir({'foo': 'any'}).create();
 
@@ -24,16 +21,18 @@
 
     // Clear the cache. We don't care about anything that was served during
     // the initial get.
-    globalServer.requestedPaths.clear();
+    server.requestedPaths.clear();
 
     // Run the solver again now that it's cached.
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.0'}).validate();
-    await d.appPackagesFile({'foo': '1.2.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.0'),
+    ]).validate();
 
     // The get should not have done any network requests since the lock file is
     // up to date.
-    expect(globalServer.requestedPaths, isEmpty);
+    expect(server.requestedPaths, isEmpty);
   });
 }
diff --git a/test/get/hosted/explain_bad_hosted_url_test.dart b/test/get/hosted/explain_bad_hosted_url_test.dart
index 6de6860..feebf67 100644
--- a/test/get/hosted/explain_bad_hosted_url_test.dart
+++ b/test/get/hosted/explain_bad_hosted_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -34,11 +32,12 @@
   });
 
   test('Allows PUB_HOSTED_URL to end with a slash', () async {
-    await servePackages((b) => b.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet(
-      environment: {'PUB_HOSTED_URL': '${globalPackageServer.url}/'},
+      environment: {'PUB_HOSTED_URL': '${globalServer.url}/'},
     );
   });
 }
diff --git a/test/get/hosted/get_stress_test.dart b/test/get/hosted/get_stress_test.dart
index 8a05104..026c0c1 100644
--- a/test/get/hosted/get_stress_test.dart
+++ b/test/get/hosted/get_stress_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,12 +9,11 @@
 
 void main() {
   test('gets more than 16 packages from a pub server', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-      for (var i = 0; i < 20; i++) {
-        builder.serve('pkg$i', '1.$i.0');
-      }
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+    for (var i = 0; i < 20; i++) {
+      server.serve('pkg$i', '1.$i.0');
+    }
 
     await d.appDir({
       'foo': '1.2.3',
@@ -29,10 +26,10 @@
       'foo': '1.2.3',
       for (var i = 0; i < 20; i++) 'pkg$i': '1.$i.0',
     }).validate();
-
-    await d.appPackagesFile({
-      'foo': '1.2.3',
-      for (var i = 0; i < 20; i++) 'pkg$i': '1.$i.0',
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      for (var i = 0; i < 20; i++)
+        d.packageConfigEntry(name: 'pkg$i', version: '1.$i.0')
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/get_test.dart b/test/get/hosted/get_test.dart
index 13f9ad8..f0270a3 100644
--- a/test/get/hosted/get_test.dart
+++ b/test/get/hosted/get_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
@@ -15,18 +13,21 @@
 
 void main() {
   test('gets a package from a pub server', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({'foo': '1.2.3'}).create();
 
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
   });
 
   test('URL encodes the package name', () async {
-    await serveNoPackages();
+    await servePackages();
 
     await d.appDir({'bad name!': '1.2.3'}).create();
 
@@ -43,11 +44,10 @@
   test('gets a package from a non-default pub server', () async {
     // Make the default server serve errors. Only the custom server should
     // be accessed.
-    await serveErrors();
+    (await servePackages()).serveErrors();
 
-    var server = await PackageServer.start((builder) {
-      builder.serve('foo', '1.2.3');
-    });
+    var server = await startPackageServer();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({
       'foo': {
@@ -59,18 +59,21 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+    ]).validate();
   });
 
   group('categorizes dependency types in the lockfile', () {
-    setUp(() => servePackages((builder) {
-          builder.serve('foo', '1.2.3', deps: {'bar': 'any'});
-          builder.serve('bar', '1.2.3');
-          builder.serve('baz', '1.2.3', deps: {'qux': 'any'});
-          builder.serve('qux', '1.2.3');
-          builder.serve('zip', '1.2.3', deps: {'zap': 'any'});
-          builder.serve('zap', '1.2.3');
-        }));
+    setUp(() async {
+      await servePackages()
+        ..serve('foo', '1.2.3', deps: {'bar': 'any'})
+        ..serve('bar', '1.2.3')
+        ..serve('baz', '1.2.3', deps: {'qux': 'any'})
+        ..serve('qux', '1.2.3')
+        ..serve('zip', '1.2.3', deps: {'zap': 'any'})
+        ..serve('zap', '1.2.3');
+    });
 
     test('for main, dev, and overridden dependencies', () async {
       await d.dir(appPath, [
diff --git a/test/get/hosted/get_transitive_test.dart b/test/get/hosted/get_transitive_test.dart
index 4640c44..0ae18ac 100644
--- a/test/get/hosted/get_transitive_test.dart
+++ b/test/get/hosted/get_transitive_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,18 +9,20 @@
 
 void main() {
   test('gets packages transitively from a pub server', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3', deps: {'bar': '2.0.4'});
-      builder.serve('bar', '2.0.3');
-      builder.serve('bar', '2.0.4');
-      builder.serve('bar', '2.0.5');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.3', deps: {'bar': '2.0.4'})
+      ..serve('bar', '2.0.3')
+      ..serve('bar', '2.0.4')
+      ..serve('bar', '2.0.5');
 
     await d.appDir({'foo': '1.2.3'}).create();
 
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3', 'bar': '2.0.4'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3', 'bar': '2.0.4'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.4'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart b/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart
index 14f1d98..585cfba 100644
--- a/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart
+++ b/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -14,12 +12,11 @@
   test(
       'gets a dependency with broken dev dependencies from a pub '
       'server', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3', pubspec: {
-        'dev_dependencies': {
-          'busted': {'not a real source': null}
-        }
-      });
+    final server = await servePackages();
+    server.serve('foo', '1.2.3', pubspec: {
+      'dev_dependencies': {
+        'busted': {'not a real source': null}
+      }
     });
 
     await d.appDir({'foo': '1.2.3'}).create();
@@ -27,6 +24,8 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/resolve_constraints_test.dart b/test/get/hosted/resolve_constraints_test.dart
index cf3d434..466902e 100644
--- a/test/get/hosted/resolve_constraints_test.dart
+++ b/test/get/hosted/resolve_constraints_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,13 +9,12 @@
 
 void main() {
   test('resolves version constraints from a pub server', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3', deps: {'baz': '>=2.0.0'});
-      builder.serve('bar', '2.3.4', deps: {'baz': '<3.0.0'});
-      builder.serve('baz', '2.0.3');
-      builder.serve('baz', '2.0.4');
-      builder.serve('baz', '3.0.1');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.3', deps: {'baz': '>=2.0.0'})
+      ..serve('bar', '2.3.4', deps: {'baz': '<3.0.0'})
+      ..serve('baz', '2.0.3')
+      ..serve('baz', '2.0.4')
+      ..serve('baz', '3.0.1');
 
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
@@ -25,8 +22,10 @@
 
     await d
         .cacheDir({'foo': '1.2.3', 'bar': '2.3.4', 'baz': '2.0.4'}).validate();
-
-    await d.appPackagesFile(
-        {'foo': '1.2.3', 'bar': '2.3.4', 'baz': '2.0.4'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      d.packageConfigEntry(name: 'bar', version: '2.3.4'),
+      d.packageConfigEntry(name: 'baz', version: '2.0.4'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/resolve_with_retracted_package_versions_test.dart b/test/get/hosted/resolve_with_retracted_package_versions_test.dart
index af714cf..d9b33a4 100644
--- a/test/get/hosted/resolve_with_retracted_package_versions_test.dart
+++ b/test/get/hosted/resolve_with_retracted_package_versions_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 
 import 'package:pub/src/io.dart';
@@ -15,28 +13,29 @@
 
 void main() {
   test('Do not consider retracted packages', () async {
-    await servePackages((builder) => builder
+    final server = await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': '^1.0.0'})
       ..serve('bar', '1.0.0')
-      ..serve('bar', '1.1.0'));
+      ..serve('bar', '1.1.0');
     await d.appDir({'foo': '1.0.0'}).create();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.1.0'));
+    server.retractPackageVersion('bar', '1.1.0');
     await pubGet();
 
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('Error when the only available package version is retracted', () async {
-    await servePackages((builder) => builder
+    final server = await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': '^1.0.0'})
-      ..serve('bar', '1.0.0'));
+      ..serve('bar', '1.0.0');
     await d.appDir({'foo': '1.0.0'}).create();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.0.0'));
+    server.retractPackageVersion('bar', '1.0.0');
     await pubGet(
         error:
             '''Because every version of foo depends on bar ^1.0.0 which doesn't match any versions, foo is forbidden. 
@@ -49,39 +48,51 @@
   // In this case we expect a newer version to be published at some point which
   // will then cause pub upgrade to choose that one.
   test('Allow retracted version when it was already in pubspec.lock', () async {
-    await servePackages((builder) => builder
+    final server = await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': '^1.0.0'})
       ..serve('bar', '1.0.0')
-      ..serve('bar', '1.1.0'));
+      ..serve('bar', '1.1.0');
     await d.appDir({'foo': '1.0.0'}).create();
 
     await pubGet();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.1.0'));
+    server.retractPackageVersion('bar', '1.1.0');
     await pubUpgrade();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) => builder..serve('bar', '2.0.0'));
+    server.serve('bar', '2.0.0');
     await pubUpgrade();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) => builder..serve('bar', '1.2.0'));
+    server.serve('bar', '1.2.0');
     await pubUpgrade();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.2.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.2.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.2.0'),
+    ]).validate();
   });
 
   test('Offline versions of pub commands also handle retracted packages',
       () async {
+    final server = await servePackages();
     await populateCache({
       'foo': ['1.0.0'],
       'bar': ['1.0.0', '1.1.0']
-    });
+    }, server);
 
     await d.cacheDir({
       'foo': '1.0.0',
@@ -89,25 +100,27 @@
     }).validate();
 
     final barVersionsCache =
-        p.join(globalPackageServer.cachingPath, '.cache', 'bar-versions.json');
+        p.join(globalServer.cachingPath, '.cache', 'bar-versions.json');
     expect(fileExists(barVersionsCache), isTrue);
     deleteEntry(barVersionsCache);
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.1.0'));
+    server.retractPackageVersion('bar', '1.1.0');
     await pubGet();
 
     await d.cacheDir({'bar': '1.1.0'}).validate();
 
     // Now serve only errors - to validate we are truly offline.
-    await serveErrors();
+    server.serveErrors();
 
     await d.appDir({'foo': '1.0.0', 'bar': '^1.0.0'}).create();
 
     await pubUpgrade(args: ['--offline']);
 
     // We choose bar 1.1.0 since we already have it in pubspec.lock
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
     // Delete lockfile so that retracted versions are not considered.
     final lockFile = p.join(d.sandbox, appPath, 'pubspec.lock');
@@ -115,15 +128,17 @@
     deleteEntry(lockFile);
 
     await pubGet(args: ['--offline']);
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('Allow retracted version when pinned in dependency_overrides', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '3.0.0');
-    });
+    final server = await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '3.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -133,31 +148,31 @@
       })
     ]).create();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('foo', '2.0.0'));
+    server.retractPackageVersion('foo', '2.0.0');
 
     await pubGet();
-    await d.appPackagesFile({'foo': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+    ]).validate();
   });
 
   test('Prefer retracted version in dependency_overrides over pubspec.lock',
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '3.0.0');
-    });
+    final server = await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '3.0.0');
 
     await d.appDir({'foo': 'any'}).create();
     await pubGet();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('foo', '2.0.0'));
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('foo', '3.0.0'));
+    server.retractPackageVersion('foo', '2.0.0');
+    server.retractPackageVersion('foo', '3.0.0');
 
     await pubUpgrade();
-    await d.appPackagesFile({'foo': '3.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '3.0.0'),
+    ]).validate();
 
     await d.dir(appPath, [
       d.pubspec({
@@ -168,6 +183,8 @@
     ]).create();
 
     await pubUpgrade();
-    await d.appPackagesFile({'foo': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/stay_locked_if_compatible_test.dart b/test/get/hosted/stay_locked_if_compatible_test.dart
index 588f395..df04579 100644
--- a/test/get/hosted/stay_locked_if_compatible_test.dart
+++ b/test/get/hosted/stay_locked_if_compatible_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,20 +11,24 @@
   test(
       "doesn't upgrade a locked pub server package with a new "
       'compatible constraint', () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
-
-    globalPackageServer.add((builder) => builder.serve('foo', '1.0.1'));
+    server.serve('foo', '1.0.1');
 
     await d.appDir({'foo': '>=1.0.0'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart b/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart
index 743c5f4..c75c435 100644
--- a/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart
+++ b/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,35 +11,34 @@
   test(
       "doesn't unlock dependencies if a new dependency is already "
       'satisfied', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'baz': '<2.0.0'});
-      builder.serve('baz', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
+    server.serve('bar', '1.0.0', deps: {'baz': '<2.0.0'});
+    server.serve('baz', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '1.0.0'),
+    ]).validate();
 
-    await d.appPackagesFile(
-        {'foo': '1.0.0', 'bar': '1.0.0', 'baz': '1.0.0'}).validate();
-
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
-      builder.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
-      builder.serve('baz', '2.0.0');
-      builder.serve('newdep', '2.0.0', deps: {'baz': '>=1.0.0'});
-    });
+    server.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
+    server.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
+    server.serve('baz', '2.0.0');
+    server.serve('newdep', '2.0.0', deps: {'baz': '>=1.0.0'});
 
     await d.appDir({'foo': 'any', 'newdep': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({
-      'foo': '1.0.0',
-      'bar': '1.0.0',
-      'baz': '1.0.0',
-      'newdep': '2.0.0'
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '1.0.0'),
+      d.packageConfigEntry(name: 'newdep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/stay_locked_test.dart b/test/get/hosted/stay_locked_test.dart
index c5c11ff..8b819ea 100644
--- a/test/get/hosted/stay_locked_test.dart
+++ b/test/get/hosted/stay_locked_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -15,24 +13,28 @@
   test(
       'keeps a hosted package locked to the version in the '
       'lockfile', () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     // This should lock the foo dependency to version 1.0.0.
     await pubGet();
-
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
 
     // Delete the .dart_tool/package_config.json file to simulate a new checkout of the application.
     deleteEntry(path.join(d.sandbox, packageConfigFilePath));
 
     // Start serving a newer package as well.
-    globalPackageServer.add((builder) => builder.serve('foo', '1.0.1'));
+    server.serve('foo', '1.0.1');
 
     // This shouldn't upgrade the foo dependency due to the lockfile.
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/unlock_if_incompatible_test.dart b/test/get/hosted/unlock_if_incompatible_test.dart
index f9f8748..007231e 100644
--- a/test/get/hosted/unlock_if_incompatible_test.dart
+++ b/test/get/hosted/unlock_if_incompatible_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,18 +11,23 @@
   test(
       'upgrades a locked pub server package with a new incompatible '
       'constraint', () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
-    globalPackageServer.add((builder) => builder.serve('foo', '1.0.1'));
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
+    server.serve('foo', '1.0.1');
     await d.appDir({'foo': '>1.0.0'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.1'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.1'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart b/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart
index feaf478..ae6a4e6 100644
--- a/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart
+++ b/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,42 +11,40 @@
   test(
       'unlocks dependencies if necessary to ensure that a new '
       'dependency is satisfied', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'baz': '<2.0.0'});
-      builder.serve('baz', '1.0.0', deps: {'qux': '<2.0.0'});
-      builder.serve('qux', '1.0.0');
-    });
+    final server = await servePackages();
+
+    server.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
+    server.serve('bar', '1.0.0', deps: {'baz': '<2.0.0'});
+    server.serve('baz', '1.0.0', deps: {'qux': '<2.0.0'});
+    server.serve('qux', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({
-      'foo': '1.0.0',
-      'bar': '1.0.0',
-      'baz': '1.0.0',
-      'qux': '1.0.0'
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '1.0.0'),
+      d.packageConfigEntry(name: 'qux', version: '1.0.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
-      builder.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
-      builder.serve('baz', '2.0.0', deps: {'qux': '<3.0.0'});
-      builder.serve('qux', '2.0.0');
-      builder.serve('newdep', '2.0.0', deps: {'baz': '>=1.5.0'});
-    });
+    server.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
+    server.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
+    server.serve('baz', '2.0.0', deps: {'qux': '<3.0.0'});
+    server.serve('qux', '2.0.0');
+    server.serve('newdep', '2.0.0', deps: {'baz': '>=1.5.0'});
 
     await d.appDir({'foo': 'any', 'newdep': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({
-      'foo': '2.0.0',
-      'bar': '2.0.0',
-      'baz': '2.0.0',
-      'qux': '1.0.0',
-      'newdep': '2.0.0'
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '2.0.0'),
+      d.packageConfigEntry(name: 'qux', version: '1.0.0'),
+      d.packageConfigEntry(name: 'newdep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/unlock_if_version_doesnt_exist_test.dart b/test/get/hosted/unlock_if_version_doesnt_exist_test.dart
index f42e12e..e4cc01b 100644
--- a/test/get/hosted/unlock_if_version_doesnt_exist_test.dart
+++ b/test/get/hosted/unlock_if_version_doesnt_exist_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -14,16 +12,23 @@
 void main() {
   test('upgrades a locked pub server package with a nonexistent version',
       () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
     await pubGet();
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
 
     deleteEntry(p.join(d.sandbox, cachePath));
 
-    globalPackageServer.replace((builder) => builder.serve('foo', '1.0.1'));
+    server.clearPackages();
+    server.serve('foo', '1.0.1');
+
     await pubGet();
-    await d.appPackagesFile({'foo': '1.0.1'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.1'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/warn_about_discontinued_test.dart b/test/get/hosted/warn_about_discontinued_test.dart
index 3913b2c..7057f9b 100644
--- a/test/get/hosted/warn_about_discontinued_test.dart
+++ b/test/get/hosted/warn_about_discontinued_test.dart
@@ -2,36 +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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
-import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
 void main() {
-  test('Warns about discontinued packages', () async {
-    await servePackages((builder) => builder
-      ..serve('foo', '1.2.3', deps: {'transitive': 'any'})
-      ..serve('transitive', '1.0.0'));
+  test('Warns about discontinued dependencies', () async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3', deps: {'transitive': 'any'});
+    server.serve('transitive', '1.0.0');
     await d.appDir({'foo': '1.2.3'}).create();
     await pubGet();
 
-    globalPackageServer.add((builder) => builder
+    server
       ..discontinue('foo')
-      ..discontinue('transitive'));
+      ..discontinue('transitive');
     // A pub get straight away will not trigger the warning, as we cache
     // responses for a while.
     await pubGet();
     final fooVersionsCache =
-        p.join(globalPackageServer.cachingPath, '.cache', 'foo-versions.json');
-    final transitiveVersionsCache = p.join(
-        globalPackageServer.cachingPath, '.cache', 'transitive-versions.json');
+        p.join(globalServer.cachingPath, '.cache', 'foo-versions.json');
+    final transitiveVersionsCache =
+        p.join(globalServer.cachingPath, '.cache', 'transitive-versions.json');
     expect(fileExists(fooVersionsCache), isTrue);
     expect(fileExists(transitiveVersionsCache), isTrue);
     deleteEntry(fooVersionsCache);
@@ -40,7 +38,6 @@
     await pubGet(output: '''
 Resolving dependencies...
   foo 1.2.3 (discontinued)
-  transitive 1.0.0 (discontinued)
 Got dependencies!
 ''');
     expect(fileExists(fooVersionsCache), isTrue);
@@ -49,12 +46,12 @@
     c['_fetchedAt'] =
         DateTime.now().subtract(Duration(days: 5)).toIso8601String();
     writeTextFile(fooVersionsCache, json.encode(c));
-    globalPackageServer
-        .add((builder) => builder.discontinue('foo', replacementText: 'bar'));
+
+    server.discontinue('foo', replacementText: 'bar');
+
     await pubGet(output: '''
 Resolving dependencies...
   foo 1.2.3 (discontinued replaced by bar)
-  transitive 1.0.0 (discontinued)
 Got dependencies!''');
     final c2 = json.decode(readTextFile(fooVersionsCache));
     // Make a bad cached value to test that responses are actually from cache.
@@ -62,22 +59,19 @@
     writeTextFile(fooVersionsCache, json.encode(c2));
     await pubGet(output: '''
 Resolving dependencies...
-  transitive 1.0.0 (discontinued)
 Got dependencies!''');
     // Repairing the cache should reset the package listing caches.
     await runPub(args: ['cache', 'repair']);
     await pubGet(output: '''
 Resolving dependencies...
   foo 1.2.3 (discontinued replaced by bar)
-  transitive 1.0.0 (discontinued)
 Got dependencies!''');
     // Test that --offline won't try to access the server for retrieving the
     // status.
-    await serveErrors();
+    server.serveErrors();
     await pubGet(args: ['--offline'], output: '''
 Resolving dependencies...
   foo 1.2.3 (discontinued replaced by bar)
-  transitive 1.0.0 (discontinued)
 Got dependencies!''');
     deleteEntry(fooVersionsCache);
     deleteEntry(transitiveVersionsCache);
@@ -87,18 +81,91 @@
 ''');
   });
 
+  test('Warns about discontinued dev dependencies', () async {
+    final builder = await servePackages();
+    builder
+      ..serve('foo', '1.2.3', deps: {'transitive': 'any'})
+      ..serve('transitive', '1.0.0');
+
+    await d.dir(appPath, [
+      d.file('pubspec.yaml', '''
+name: myapp
+dependencies:
+
+dev_dependencies:
+  foo: 1.2.3
+environment:
+  sdk: '>=0.1.2 <1.0.0'
+''')
+    ]).create();
+    await pubGet();
+
+    builder
+      ..discontinue('foo')
+      ..discontinue('transitive');
+    // A pub get straight away will not trigger the warning, as we cache
+    // responses for a while.
+    await pubGet();
+    final fooVersionsCache =
+        p.join(globalServer.cachingPath, '.cache', 'foo-versions.json');
+    expect(fileExists(fooVersionsCache), isTrue);
+    deleteEntry(fooVersionsCache);
+    // We warn only about the direct dependency here:
+    await pubGet(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued)
+Got dependencies!
+''');
+    expect(fileExists(fooVersionsCache), isTrue);
+    final c = json.decode(readTextFile(fooVersionsCache));
+    // Make the cache artificially old.
+    c['_fetchedAt'] =
+        DateTime.now().subtract(Duration(days: 5)).toIso8601String();
+    writeTextFile(fooVersionsCache, json.encode(c));
+    builder.discontinue('foo', replacementText: 'bar');
+    await pubGet(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued replaced by bar)
+Got dependencies!''');
+    final c2 = json.decode(readTextFile(fooVersionsCache));
+    // Make a bad cached value to test that responses are actually from cache.
+    c2['isDiscontinued'] = false;
+    writeTextFile(fooVersionsCache, json.encode(c2));
+    await pubGet(output: '''
+Resolving dependencies...
+Got dependencies!''');
+    // Repairing the cache should reset the package listing caches.
+    await runPub(args: ['cache', 'repair']);
+    await pubGet(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued replaced by bar)
+Got dependencies!''');
+    // Test that --offline won't try to access the server for retrieving the
+    // status.
+    builder.serveErrors();
+    await pubGet(args: ['--offline'], output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued replaced by bar)
+Got dependencies!''');
+    deleteEntry(fooVersionsCache);
+    await pubGet(args: ['--offline'], output: '''
+Resolving dependencies...
+Got dependencies!
+''');
+  });
+
   test('get does not fail when status listing fails', () async {
-    await servePackages((builder) => builder..serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
     await d.appDir({'foo': '1.2.3'}).create();
     await pubGet();
     final fooVersionsCache =
-        p.join(globalPackageServer.cachingPath, '.cache', 'foo-versions.json');
+        p.join(globalServer.cachingPath, '.cache', 'foo-versions.json');
     expect(fileExists(fooVersionsCache), isTrue);
     deleteEntry(fooVersionsCache);
     // Serve 400 on all requests.
-    globalPackageServer.extraHandlers
-      ..clear()
-      ..[RegExp('.*')] = (request) async => Response(400);
+    globalServer.handle(RegExp('.*'),
+        (shelf.Request request) => shelf.Response.notFound('Not found'));
 
     /// Even if we fail to get status we still report success if versions don't unlock.
     await pubGet();
diff --git a/test/get/hosted/warn_about_retracted_package_test.dart b/test/get/hosted/warn_about_retracted_package_test.dart
index 5258d7c..2336c7f 100644
--- a/test/get/hosted/warn_about_retracted_package_test.dart
+++ b/test/get/hosted/warn_about_retracted_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -13,38 +11,36 @@
 
 void main() {
   test('Report retracted packages', () async {
-    await servePackages((builder) => builder
+    final server = await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': 'any'})
-      ..serve('bar', '1.0.0'));
+      ..serve('bar', '1.0.0');
     await d.appDir({'foo': '1.0.0'}).create();
 
     await pubGet();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.0.0'));
+    server.retractPackageVersion('bar', '1.0.0');
     // Delete the cache to trigger the report.
     final barVersionsCache =
-        p.join(globalPackageServer.cachingPath, '.cache', 'bar-versions.json');
+        p.join(server.cachingPath, '.cache', 'bar-versions.json');
     expect(fileExists(barVersionsCache), isTrue);
     deleteEntry(barVersionsCache);
     await pubGet(output: contains('bar 1.0.0 (retracted)'));
   });
 
   test('Report retracted packages with newer version available', () async {
-    await servePackages((builder) => builder
+    final server = await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': '^1.0.0'})
       ..serve('bar', '1.0.0')
       ..serve('bar', '2.0.0')
-      ..serve('bar', '2.0.1-pre'));
+      ..serve('bar', '2.0.1-pre');
     await d.appDir({'foo': '1.0.0'}).create();
 
     await pubGet();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.0.0'));
+    server.retractPackageVersion('bar', '1.0.0');
     // Delete the cache to trigger the report.
     final barVersionsCache =
-        p.join(globalPackageServer.cachingPath, '.cache', 'bar-versions.json');
+        p.join(server.cachingPath, '.cache', 'bar-versions.json');
     expect(fileExists(barVersionsCache), isTrue);
     deleteEntry(barVersionsCache);
     await pubGet(output: contains('bar 1.0.0 (retracted, 2.0.0 available)'));
@@ -52,19 +48,18 @@
 
   test('Report retracted packages with newer prerelease version available',
       () async {
-    await servePackages((builder) => builder
+    final server = await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': '^1.0.0-pre'})
       ..serve('bar', '1.0.0-pre')
-      ..serve('bar', '2.0.1-pre'));
+      ..serve('bar', '2.0.1-pre');
     await d.appDir({'foo': '1.0.0'}).create();
 
     await pubGet();
 
-    globalPackageServer
-        .add((builder) => builder..retractPackageVersion('bar', '1.0.0-pre'));
+    server.retractPackageVersion('bar', '1.0.0-pre');
     // Delete the cache to trigger the report.
     final barVersionsCache =
-        p.join(globalPackageServer.cachingPath, '.cache', 'bar-versions.json');
+        p.join(server.cachingPath, '.cache', 'bar-versions.json');
     expect(fileExists(barVersionsCache), isTrue);
     deleteEntry(barVersionsCache);
     await pubGet(
diff --git a/test/get/package_name_test.dart b/test/get/package_name_test.dart
index f385cfe..b1328cc 100644
--- a/test/get/package_name_test.dart
+++ b/test/get/package_name_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -58,7 +56,8 @@
     await pubGet();
 
     await d.dir(appPath, [
-      d.packagesFile({'foo.bar.baz': '.'}),
+      d.packageConfigFile(
+          [d.packageConfigEntry(name: 'foo.bar.baz', path: '.')])
     ]).validate();
   });
 }
diff --git a/test/get/path/absolute_path_test.dart b/test/get/path/absolute_path_test.dart
index 7304f52..e1897e9 100644
--- a/test/get/path/absolute_path_test.dart
+++ b/test/get/path/absolute_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
@@ -23,6 +21,8 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': path.join(d.sandbox, 'foo')}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: path.join(d.sandbox, 'foo')),
+    ]).validate();
   });
 }
diff --git a/test/get/path/absolute_symlink_test.dart b/test/get/path/absolute_symlink_test.dart
index b0cd147..83d3433 100644
--- a/test/get/path/absolute_symlink_test.dart
+++ b/test/get/path/absolute_symlink_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
@@ -26,8 +24,8 @@
 
     await pubGet();
 
-    await d.dir(appPath, [
-      d.packagesFile({'myapp': '.', 'foo': fooPath})
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: fooPath),
     ]).validate();
 
     await d.dir('moved').create();
@@ -37,9 +35,9 @@
     renameInSandbox(appPath, path.join('moved', appPath));
 
     await d.dir('moved', [
-      d.dir(appPath, [
-        d.packagesFile({'myapp': '.', 'foo': fooPath})
-      ])
+      d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', path: fooPath),
+      ]),
     ]).validate();
   });
 }
diff --git a/test/get/path/empty_pubspec_test.dart b/test/get/path/empty_pubspec_test.dart
index 5b94d70..0f9843f 100644
--- a/test/get/path/empty_pubspec_test.dart
+++ b/test/get/path/empty_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
diff --git a/test/get/path/no_pubspec_test.dart b/test/get/path/no_pubspec_test.dart
index d12477d..d328f25 100644
--- a/test/get/path/no_pubspec_test.dart
+++ b/test/get/path/no_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
diff --git a/test/get/path/nonexistent_dir_test.dart b/test/get/path/nonexistent_dir_test.dart
index 993590f..7616087 100644
--- a/test/get/path/nonexistent_dir_test.dart
+++ b/test/get/path/nonexistent_dir_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
diff --git a/test/get/path/path_is_file_test.dart b/test/get/path/path_is_file_test.dart
index 119a794..6fafd82 100644
--- a/test/get/path/path_is_file_test.dart
+++ b/test/get/path/path_is_file_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
diff --git a/test/get/path/relative_path_test.dart b/test/get/path/relative_path_test.dart
index a1d7437..e56a453 100644
--- a/test/get/path/relative_path_test.dart
+++ b/test/get/path/relative_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/lock_file.dart';
 import 'package:pub/src/source_registry.dart';
@@ -25,7 +23,9 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
   });
 
   test('path is relative to containing pubspec', () async {
@@ -47,8 +47,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '../relative/foo', 'bar': '../relative/bar'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../relative/foo'),
+      d.packageConfigEntry(name: 'bar', path: '../relative/bar'),
+    ]).validate();
   });
 
   test('path is relative to containing pubspec when using --directory',
@@ -74,9 +76,10 @@
         workingDirectory: d.sandbox,
         output: contains('Changed 2 dependencies in myapp!'));
 
-    await d.appPackagesFile(
-      {'foo': '../relative/foo', 'bar': '../relative/bar'},
-    ).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../relative/foo'),
+      d.packageConfigEntry(name: 'bar', path: '../relative/bar'),
+    ]).validate();
   });
 
   test('relative path preserved in the lockfile', () async {
@@ -93,7 +96,7 @@
 
     var lockfilePath = path.join(d.sandbox, appPath, 'pubspec.lock');
     var lockfile = LockFile.load(lockfilePath, SourceRegistry());
-    var description = lockfile.packages['foo'].description;
+    var description = lockfile.packages['foo']!.description;
 
     expect(description['relative'], isTrue);
     expect(description['path'], path.join(d.sandbox, 'foo'));
diff --git a/test/get/path/relative_symlink_test.dart b/test/get/path/relative_symlink_test.dart
index e48ba41..9136a34 100644
--- a/test/get/path/relative_symlink_test.dart
+++ b/test/get/path/relative_symlink_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 // Pub uses NTFS junction points to create links in the packages directory.
 // These (unlike the symlinks that are supported in Vista and later) do not
 // support relative paths. So this test, by design, will not pass on Windows.
@@ -30,8 +28,8 @@
 
     await pubGet();
 
-    await d.dir(appPath, [
-      d.packagesFile({'myapp': '.', 'foo': '../foo'})
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
     ]).validate();
 
     await d.dir('moved').create();
@@ -43,8 +41,8 @@
     renameInSandbox(appPath, path.join('moved', appPath));
 
     await d.dir('moved', [
-      d.dir(appPath, [
-        d.packagesFile({'myapp': '.', 'foo': '../foo'})
+      d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', path: '../foo'),
       ])
     ]).validate();
   });
diff --git a/test/get/path/shared_dependency_symlink_test.dart b/test/get/path/shared_dependency_symlink_test.dart
index 7d1369c..d450838 100644
--- a/test/get/path/shared_dependency_symlink_test.dart
+++ b/test/get/path/shared_dependency_symlink_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 
 import 'package:test/test.dart';
@@ -42,13 +40,10 @@
 
     await pubGet();
 
-    await d.dir(appPath, [
-      d.packagesFile({
-        'myapp': '.',
-        'foo': '../foo',
-        'bar': '../bar',
-        'shared': '../shared'
-      })
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+      d.packageConfigEntry(name: 'shared', path: '../shared'),
     ]).validate();
   });
 }
diff --git a/test/get/path/shared_dependency_test.dart b/test/get/path/shared_dependency_test.dart
index 3525e6b..3ef91ae 100644
--- a/test/get/path/shared_dependency_test.dart
+++ b/test/get/path/shared_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -37,8 +35,11 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '../foo', 'bar': '../bar', 'shared': '../shared'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+      d.packageConfigEntry(name: 'shared', path: '../shared'),
+    ]).validate();
   });
 
   test('shared dependency with paths that normalize the same', () async {
@@ -68,7 +69,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '../foo', 'bar': '../bar', 'shared': '../shared'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+      d.packageConfigEntry(name: 'shared', path: '../shared'),
+    ]).validate();
   });
 }
diff --git a/test/get/preserve_lock_file_line_endings_test.dart b/test/get/preserve_lock_file_line_endings_test.dart
index 8f6dc26..bfd015a 100644
--- a/test/get/preserve_lock_file_line_endings_test.dart
+++ b/test/get/preserve_lock_file_line_endings_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/entrypoint.dart';
 import 'package:test/test.dart';
diff --git a/test/get/sdk_constraint_required_test.dart b/test/get/sdk_constraint_required_test.dart
index 3cb0f86..3f1e5c0 100644
--- a/test/get/sdk_constraint_required_test.dart
+++ b/test/get/sdk_constraint_required_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -27,8 +25,8 @@
       d.nothing('pubspec.lock'),
       // The "packages" directory should not have been generated.
       d.nothing('packages'),
-      // The ".packages" file should not have been created.
-      d.nothing('.packages'),
+      // The package config file should not have been created.
+      d.nothing('.dart_tool/package_config.json'),
     ]).validate();
   });
 }
diff --git a/test/get/switch_source_test.dart b/test/get/switch_source_test.dart
index 2dee748..7e56652 100644
--- a/test/get/switch_source_test.dart
+++ b/test/get/switch_source_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -11,7 +9,8 @@
 
 void main() {
   test('re-gets a package if its source has changed', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.dir('foo',
         [d.libDir('foo', 'foo 0.0.1'), d.libPubspec('foo', '0.0.1')]).create();
@@ -22,11 +21,15 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
   });
 }
diff --git a/test/get/unknown_sdk_test.dart b/test/get/unknown_sdk_test.dart
new file mode 100644
index 0000000..d0c1de3
--- /dev/null
+++ b/test/get/unknown_sdk_test.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, 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:pub/src/exit_codes.dart' as exit_codes;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  test('pub get barks at unknown sdk', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'environment': {'foo': '>=1.2.4 <2.0.0'}
+      })
+    ]).create();
+
+    await pubGet(
+      error: contains(
+          "Error on line 1, column 40 of pubspec.yaml: pubspec.yaml refers to an unknown sdk 'foo'."),
+      exitCode: exit_codes.DATA,
+    );
+  });
+}
diff --git a/test/get/with_empty_environment_test.dart b/test/get/with_empty_environment_test.dart
new file mode 100644
index 0000000..30768c4
--- /dev/null
+++ b/test/get/with_empty_environment_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2021, 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 'dart:io';
+
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  test(r'runs even with an empty environment (eg. no $HOME)', () async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+
+    await d.appDir({'foo': 'any'}).create();
+
+    await pubGet(environment: {
+      '_PUB_TEST_CONFIG_DIR': null,
+      if (Platform.isWindows) ...{
+        'SYSTEMROOT': Platform.environment['SYSTEMROOT'],
+        'TMP': Platform.environment['TMP'],
+      },
+    }, includeParentEnvironment: false);
+  });
+}
diff --git a/test/global/activate/activate_git_after_hosted_test.dart b/test/global/activate/activate_git_after_hosted_test.dart
index 5dd32c1..c553041 100644
--- a/test/global/activate/activate_git_after_hosted_test.dart
+++ b/test/global/activate/activate_git_after_hosted_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,11 +11,10 @@
   test('activating a Git package deactivates the hosted one', () async {
     ensureGit();
 
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
+    ]);
 
     await d.git('foo.git', [
       d.libPubspec('foo', '1.0.0'),
diff --git a/test/global/activate/activate_hosted_after_git_test.dart b/test/global/activate/activate_hosted_after_git_test.dart
index a86b2cc..1546280 100644
--- a/test/global/activate/activate_hosted_after_git_test.dart
+++ b/test/global/activate/activate_hosted_after_git_test.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +10,10 @@
 
 void main() {
   test('activating a hosted package deactivates the Git one', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '2.0.0', contents: [
+      d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
+    ]);
 
     await d.git('foo.git', [
       d.libPubspec('foo', '1.0.0'),
@@ -24,8 +22,9 @@
 
     await runPub(args: ['global', 'activate', '-sgit', '../foo.git']);
 
+    final locationUri = p.toUri(p.join(d.sandbox, 'foo.git'));
     await runPub(args: ['global', 'activate', 'foo'], output: '''
-        Package foo is currently active from Git repository "../foo.git".
+        Package foo is currently active from Git repository "$locationUri".
         Resolving dependencies...
         + foo 2.0.0
         Downloading foo 2.0.0...
diff --git a/test/global/activate/activate_hosted_after_path_test.dart b/test/global/activate/activate_hosted_after_path_test.dart
index 1e01735..4cd753f 100644
--- a/test/global/activate/activate_hosted_after_path_test.dart
+++ b/test/global/activate/activate_hosted_after_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -13,11 +11,10 @@
 
 void main() {
   test('activating a hosted package deactivates the path one', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '2.0.0', contents: [
+      d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
+    ]);
 
     await d.dir('foo', [
       d.libPubspec('foo', '1.0.0'),
diff --git a/test/global/activate/activate_hosted_twice_test.dart b/test/global/activate/activate_hosted_twice_test.dart
index 925a205..a4d1337 100644
--- a/test/global/activate/activate_hosted_twice_test.dart
+++ b/test/global/activate/activate_hosted_twice_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,7 +9,8 @@
 
 void main() {
   test('activating a hosted package twice will not precompile', () async {
-    await servePackages((builder) => builder
+    final server = await servePackages();
+    server
       ..serve('foo', '1.0.0', deps: {
         'bar': 'any'
       }, contents: [
@@ -23,23 +22,15 @@
       ])
       ..serve('bar', '1.0.0', contents: [
         d.dir('lib', [d.file('bar.dart', 'final version = "1.0.0";')])
-      ]));
+      ]);
 
-    await runPub(args: ['global', 'activate', 'foo'], output: '''
-Resolving dependencies...
-+ bar 1.0.0
-+ foo 1.0.0
-Downloading foo 1.0.0...
-Downloading bar 1.0.0...
-Building package executables...
-Built foo:foo.
-Activated foo 1.0.0.''');
+    await runPub(args: ['global', 'activate', 'foo'], output: anything);
 
     await runPub(args: ['global', 'activate', 'foo'], output: '''
 Package foo is currently active at version 1.0.0.
 Resolving dependencies...
 The package foo is already activated at newest available version.
-To recompile executables, first run `global deactivate foo`.
+To recompile executables, first run `dart pub global deactivate foo`.
 Activated foo 1.0.0.''');
 
     var pub = await pubRun(global: true, args: ['foo']);
@@ -48,10 +39,9 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    globalPackageServer
-        .add((builder) => builder.serve('bar', '2.0.0', contents: [
-              d.dir('lib', [d.file('bar.dart', 'final version = "2.0.0";')])
-            ]));
+    server.serve('bar', '2.0.0', contents: [
+      d.dir('lib', [d.file('bar.dart', 'final version = "2.0.0";')])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo'], output: '''
 Package foo is currently active at version 1.0.0.
diff --git a/test/global/activate/activate_path_after_hosted_test.dart b/test/global/activate/activate_path_after_hosted_test.dart
index c73cb01..01d493d 100644
--- a/test/global/activate/activate_path_after_hosted_test.dart
+++ b/test/global/activate/activate_path_after_hosted_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -12,12 +10,11 @@
 import '../../test_pub.dart';
 
 void main() {
-  test('activating a hosted package deactivates the path one', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
-      ]);
-    });
+  test('activating a path package deactivates the hosted one', () async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('foo.dart', "main(args) => print('hosted');")])
+    ]);
 
     await d.dir('foo', [
       d.libPubspec('foo', '2.0.0'),
diff --git a/test/global/activate/bad_version_test.dart b/test/global/activate/bad_version_test.dart
index 651c342..c5c4635 100644
--- a/test/global/activate/bad_version_test.dart
+++ b/test/global/activate/bad_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/global/activate/cached_package_test.dart b/test/global/activate/cached_package_test.dart
index 7ddcf10..98db402 100644
--- a/test/global/activate/cached_package_test.dart
+++ b/test/global/activate/cached_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('can activate an already cached package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+    ]);
 
     await runPub(args: ['cache', 'add', 'foo']);
 
diff --git a/test/global/activate/constraint_test.dart b/test/global/activate/constraint_test.dart
index b712d00..23386c0 100644
--- a/test/global/activate/constraint_test.dart
+++ b/test/global/activate/constraint_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,12 +9,11 @@
 
 void main() {
   test('chooses the highest version that matches the constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '1.0.1');
-      builder.serve('foo', '1.1.0');
-      builder.serve('foo', '1.2.3');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '1.0.1')
+      ..serve('foo', '1.1.0')
+      ..serve('foo', '1.2.3');
 
     await runPub(args: ['global', 'activate', 'foo', '<1.1.0']);
 
diff --git a/test/global/activate/constraint_with_path_test.dart b/test/global/activate/constraint_with_path_test.dart
index 1a5beb7..359cd60 100644
--- a/test/global/activate/constraint_with_path_test.dart
+++ b/test/global/activate/constraint_with_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/global/activate/custom_hosted_url_test.dart b/test/global/activate/custom_hosted_url_test.dart
index 7443887..8b2f0fc 100644
--- a/test/global/activate/custom_hosted_url_test.dart
+++ b/test/global/activate/custom_hosted_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../test_pub.dart';
@@ -11,22 +9,21 @@
 void main() {
   test('activating a package from a custom pub server', () async {
     // The default pub server (i.e. pub.dartlang.org).
-    await servePackages((builder) {
-      builder.serve('baz', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('baz', '1.0.0');
 
     // The custom pub server.
-    final customServer = await PackageServer.start((builder) {
-      Map<String, dynamic> hostedDep(String name, String constraint) => {
-            'hosted': {
-              'url': builder.serverUrl,
-              'name': name,
-            },
-            'version': constraint,
-          };
-      builder.serve('foo', '1.0.0', deps: {'bar': hostedDep('bar', 'any')});
-      builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
-    });
+    final customServer = await startPackageServer();
+    Map<String, dynamic> hostedDep(String name, String constraint) => {
+          'hosted': {
+            'url': customServer.url,
+            'name': name,
+          },
+          'version': constraint,
+        };
+
+    customServer.serve('foo', '1.0.0', deps: {'bar': hostedDep('bar', 'any')});
+    customServer.serve('bar', '1.0.0', deps: {'baz': 'any'});
 
     await runPub(
         args: ['global', 'activate', 'foo', '-u', customServer.url],
diff --git a/test/global/activate/different_version_test.dart b/test/global/activate/different_version_test.dart
index 61ba228..8406b92 100644
--- a/test/global/activate/different_version_test.dart
+++ b/test/global/activate/different_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,14 +11,13 @@
   test(
       "discards the previous active version if it doesn't match the "
       'constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+    await servePackages()
+      ..serve('foo', '1.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi");')])
+      ])
+      ..serve('foo', '2.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi2");')])
       ]);
-      builder.serve('foo', '2.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', 'main() => print("hi2"); ')])
-      ]);
-    });
 
     // Activate 1.0.0.
     await runPub(args: ['global', 'activate', 'foo', '1.0.0']);
diff --git a/test/global/activate/doesnt_snapshot_path_executables_test.dart b/test/global/activate/doesnt_snapshot_path_executables_test.dart
index b809ea7..2ca8d3a 100644
--- a/test/global/activate/doesnt_snapshot_path_executables_test.dart
+++ b/test/global/activate/doesnt_snapshot_path_executables_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/activate/empty_constraint_test.dart b/test/global/activate/empty_constraint_test.dart
index 9e0bde9..d893bcd 100644
--- a/test/global/activate/empty_constraint_test.dart
+++ b/test/global/activate/empty_constraint_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,10 +9,9 @@
 
 void main() {
   test('errors if the constraint matches no versions', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '1.0.1');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '1.0.1');
 
     await runPub(
         args: ['global', 'activate', 'foo', '>1.1.0'],
diff --git a/test/global/activate/feature_test.dart b/test/global/activate/feature_test.dart
index 2d30460..58119d1 100644
--- a/test/global/activate/feature_test.dart
+++ b/test/global/activate/feature_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 @Skip()
 
 import 'package:test/test.dart';
@@ -12,8 +10,8 @@
 
 void main() {
   test('enables default-on features by default', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
@@ -23,11 +21,9 @@
             'dependencies': {'baz': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0');
 
     await runPub(args: ['global', 'activate', 'foo'], output: contains('''
 Resolving dependencies...
@@ -37,8 +33,8 @@
   });
 
   test('can enable default-off features', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
@@ -48,11 +44,9 @@
             'dependencies': {'baz': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0');
 
     await runPub(
         args: ['global', 'activate', 'foo', '--features', 'things'],
@@ -65,8 +59,8 @@
   });
 
   test('can disable default-on features', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
@@ -76,11 +70,9 @@
             'dependencies': {'baz': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0');
 
     await runPub(
         args: ['global', 'activate', 'foo', '--omit-features', 'stuff'],
@@ -91,8 +83,8 @@
   });
 
   test('supports multiple arguments', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
@@ -103,11 +95,9 @@
             'dependencies': {'baz': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0');
 
     await runPub(
         args: ['global', 'activate', 'foo', '--features', 'things,stuff'],
@@ -120,8 +110,8 @@
   });
 
   test('can both enable and disable', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
@@ -131,11 +121,9 @@
             'dependencies': {'baz': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0');
 
     await runPub(args: [
       'global',
diff --git a/test/global/activate/git_package_test.dart b/test/global/activate/git_package_test.dart
index 43cd218..ee88de7 100644
--- a/test/global/activate/git_package_test.dart
+++ b/test/global/activate/git_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/activate/ignores_active_version_test.dart b/test/global/activate/ignores_active_version_test.dart
index d405f54..777a631 100644
--- a/test/global/activate/ignores_active_version_test.dart
+++ b/test/global/activate/ignores_active_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,15 +9,14 @@
 
 void main() {
   test('ignores previously activated version', () async {
-    await servePackages((builder) {
-      builder.serve(
+    await servePackages()
+      ..serve(
         'foo',
         '1.2.3',
-      );
-      builder.serve('foo', '1.3.0', contents: [
+      )
+      ..serve('foo', '1.3.0', contents: [
         d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
       ]);
-    });
 
     // Activate 1.2.3.
     await runPub(args: ['global', 'activate', 'foo', '1.2.3']);
diff --git a/test/global/activate/installs_dependencies_for_git_test.dart b/test/global/activate/installs_dependencies_for_git_test.dart
index 8e5b477..6018f50 100644
--- a/test/global/activate/installs_dependencies_for_git_test.dart
+++ b/test/global/activate/installs_dependencies_for_git_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,10 +9,9 @@
 
 void main() {
   test('activating a Git package installs its dependencies', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
-      builder.serve('baz', '1.0.0');
-    });
+    await servePackages()
+      ..serve('bar', '1.0.0', deps: {'baz': 'any'})
+      ..serve('baz', '1.0.0');
 
     await d.git('foo.git', [
       d.libPubspec('foo', '1.0.0', deps: {'bar': 'any'}),
diff --git a/test/global/activate/installs_dependencies_for_path_test.dart b/test/global/activate/installs_dependencies_for_path_test.dart
index 0afe76e..4688f07 100644
--- a/test/global/activate/installs_dependencies_for_path_test.dart
+++ b/test/global/activate/installs_dependencies_for_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,10 +9,9 @@
 
 void main() {
   test('activating a path package installs dependencies', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
-      builder.serve('baz', '2.0.0');
-    });
+    await servePackages()
+      ..serve('bar', '1.0.0', deps: {'baz': 'any'})
+      ..serve('baz', '2.0.0');
 
     await d.dir('foo', [
       d.libPubspec('foo', '0.0.0', deps: {'bar': 'any'}),
diff --git a/test/global/activate/installs_dependencies_test.dart b/test/global/activate/installs_dependencies_test.dart
index 0b26d8b..770ebcf 100644
--- a/test/global/activate/installs_dependencies_test.dart
+++ b/test/global/activate/installs_dependencies_test.dart
@@ -2,19 +2,16 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../test_pub.dart';
 
 void main() {
   test('activating a package installs its dependencies', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
-      builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
-      builder.serve('baz', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': 'any'})
+      ..serve('bar', '1.0.0', deps: {'baz': 'any'})
+      ..serve('baz', '1.0.0');
 
     await runPub(
         args: ['global', 'activate', 'foo'],
diff --git a/test/global/activate/missing_git_repo_test.dart b/test/global/activate/missing_git_repo_test.dart
index bbb2345..8db65c3 100644
--- a/test/global/activate/missing_git_repo_test.dart
+++ b/test/global/activate/missing_git_repo_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/global/activate/missing_package_arg_test.dart b/test/global/activate/missing_package_arg_test.dart
index 5923bd9..588ffcf 100644
--- a/test/global/activate/missing_package_arg_test.dart
+++ b/test/global/activate/missing_package_arg_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -12,8 +10,9 @@
 void main() {
   test('fails if no package was given', () {
     return runPub(
-        args: ['global', 'activate'],
-        error: contains('No package to activate given.'),
-        exitCode: exit_codes.USAGE);
+      args: ['global', 'activate'],
+      error: contains('No package to activate given.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/global/activate/outdated_binstub_test.dart b/test/global/activate/outdated_binstub_test.dart
index 43bed90..6d9e298 100644
--- a/test/global/activate/outdated_binstub_test.dart
+++ b/test/global/activate/outdated_binstub_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
-const _OUTDATED_BINSTUB = '''
+const _outdatedBinstub = '''
 #!/usr/bin/env sh
 # This file was created by pub v0.1.2-3.
 # Package: foo
@@ -21,19 +19,17 @@
 
 void main() {
   test('an outdated binstub is replaced', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'foo-script': 'script'}
-      }, contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'foo-script': 'script'}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
     await d.dir(cachePath, [
-      d.dir('bin', [d.file(binStubName('foo-script'), _OUTDATED_BINSTUB)])
+      d.dir('bin', [d.file(binStubName('foo-script'), _outdatedBinstub)])
     ]).create();
 
     await runPub(args: ['global', 'activate', 'foo']);
diff --git a/test/global/activate/path_package_test.dart b/test/global/activate/path_package_test.dart
index 5153760..514176a 100644
--- a/test/global/activate/path_package_test.dart
+++ b/test/global/activate/path_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -57,14 +55,13 @@
   });
 
   test("Doesn't precompile binaries when activating from path", () async {
-    await servePackages(
-      (builder) => builder.serve(
-        'bar',
-        '1.0.0',
-        contents: [
-          d.dir('bin', [d.file('bar.dart', "main() => print('bar');")])
-        ],
-      ),
+    final server = await servePackages();
+    server.serve(
+      'bar',
+      '1.0.0',
+      contents: [
+        d.dir('bin', [d.file('bar.dart', "main() => print('bar');")])
+      ],
     );
 
     await d.dir('foo', [
diff --git a/test/global/activate/reactivating_git_upgrades_test.dart b/test/global/activate/reactivating_git_upgrades_test.dart
index 0ccbb65..68102da 100644
--- a/test/global/activate/reactivating_git_upgrades_test.dart
+++ b/test/global/activate/reactivating_git_upgrades_test.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -31,11 +30,12 @@
     await d.git('foo.git', [d.libPubspec('foo', '1.0.1')]).commit();
 
     // Activating it again pulls down the latest commit.
+    final locationUri = p.toUri(p.join(d.sandbox, 'foo.git'));
     await runPub(
         args: ['global', 'activate', '-sgit', '../foo.git'],
         output: allOf(
             startsWith('Package foo is currently active from Git repository '
-                '"../foo.git".\n'
+                '"$locationUri".\n'
                 'Resolving dependencies...\n'
                 '+ foo 1.0.1 from git ../foo.git at '),
             // Specific revision number goes here.
diff --git a/test/global/activate/removes_old_lockfile_test.dart b/test/global/activate/removes_old_lockfile_test.dart
deleted file mode 100644
index 7d7a9e0..0000000
--- a/test/global/activate/removes_old_lockfile_test.dart
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2014, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('removes the 1.6-style lockfile', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-    });
-
-    await d.dir(cachePath, [
-      d.dir('global_packages', [
-        d.file(
-            'foo.lock',
-            'packages: {foo: {description: foo, source: hosted, '
-                'version: "1.0.0"}}}')
-      ])
-    ]).create();
-
-    await runPub(args: ['global', 'activate', 'foo']);
-
-    await d.dir(cachePath, [
-      d.dir('global_packages', [
-        d.nothing('foo.lock'),
-        d.dir('foo', [d.file('pubspec.lock', contains('1.0.0'))])
-      ])
-    ]).validate();
-  });
-}
diff --git a/test/global/activate/snapshots_git_executables_test.dart b/test/global/activate/snapshots_git_executables_test.dart
index 81a0152..aee6a71 100644
--- a/test/global/activate/snapshots_git_executables_test.dart
+++ b/test/global/activate/snapshots_git_executables_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/activate/snapshots_hosted_executables_test.dart b/test/global/activate/snapshots_hosted_executables_test.dart
index e9d2a2d..bc2b332 100644
--- a/test/global/activate/snapshots_hosted_executables_test.dart
+++ b/test/global/activate/snapshots_hosted_executables_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,16 +9,15 @@
 
 void main() {
   test('snapshots the executables for a hosted package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [
-          d.file('hello.dart', "void main() => print('hello!');"),
-          d.file('goodbye.dart', "void main() => print('goodbye!');"),
-          d.file('shell.sh', 'echo shell'),
-          d.dir('subdir', [d.file('sub.dart', "void main() => print('sub!');")])
-        ])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [
+        d.file('hello.dart', "void main() => print('hello!');"),
+        d.file('goodbye.dart', "void main() => print('goodbye!');"),
+        d.file('shell.sh', 'echo shell'),
+        d.dir('subdir', [d.file('sub.dart', "void main() => print('sub!');")])
+      ])
+    ]);
 
     await runPub(
         args: ['global', 'activate', 'foo'],
diff --git a/test/global/activate/supports_version_solver_backtracking_test.dart b/test/global/activate/supports_version_solver_backtracking_test.dart
index e43e735..c8c138d 100644
--- a/test/global/activate/supports_version_solver_backtracking_test.dart
+++ b/test/global/activate/supports_version_solver_backtracking_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,14 +9,13 @@
 
 void main() {
   test('performs verison solver backtracking if necessary', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.1.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.1.0', pubspec: {
         'environment': {'sdk': '>=0.1.2 <0.2.0'}
-      });
-      builder.serve('foo', '1.2.0', pubspec: {
+      })
+      ..serve('foo', '1.2.0', pubspec: {
         'environment': {'sdk': '>=0.1.3 <0.2.0'}
       });
-    });
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/activate/uncached_package_test.dart b/test/global/activate/uncached_package_test.dart
index 3bdb185..1a18d70 100644
--- a/test/global/activate/uncached_package_test.dart
+++ b/test/global/activate/uncached_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,17 +9,16 @@
 
 void main() {
   test('installs and activates the best version of a package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', 'main() => print("hi"); ')])
+    await servePackages()
+      ..serve('foo', '1.0.0', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi");')])
+      ])
+      ..serve('foo', '1.2.3', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi 1.2.3");')])
+      ])
+      ..serve('foo', '2.0.0-wildly.unstable', contents: [
+        d.dir('bin', [d.file('foo.dart', 'main() => print("hi unstable");')])
       ]);
-      builder.serve('foo', '1.2.3', contents: [
-        d.dir('bin', [d.file('foo.dart', 'main() => print("hi 1.2.3"); ')])
-      ]);
-      builder.serve('foo', '2.0.0-wildly.unstable', contents: [
-        d.dir('bin', [d.file('foo.dart', 'main() => print("hi unstable"); ')])
-      ]);
-    });
 
     await runPub(args: ['global', 'activate', 'foo'], output: '''
         Resolving dependencies...
diff --git a/test/global/activate/unexpected_arguments_test.dart b/test/global/activate/unexpected_arguments_test.dart
index d7bb4cf..be6633d 100644
--- a/test/global/activate/unexpected_arguments_test.dart
+++ b/test/global/activate/unexpected_arguments_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/global/activate/unknown_package_test.dart b/test/global/activate/unknown_package_test.dart
index 6a4fe84..271545d 100644
--- a/test/global/activate/unknown_package_test.dart
+++ b/test/global/activate/unknown_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,7 +9,7 @@
 
 void main() {
   test('errors if the package could not be found', () async {
-    await serveNoPackages();
+    await servePackages();
 
     await runPub(
         args: ['global', 'activate', 'foo'],
diff --git a/test/global/binstubs/binstub_runs_executable_test.dart b/test/global/binstubs/binstub_runs_executable_test.dart
index 284729b..98ab53b 100644
--- a/test/global/binstubs/binstub_runs_executable_test.dart
+++ b/test/global/binstubs/binstub_runs_executable_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 import 'package:test_process/test_process.dart';
@@ -14,14 +12,12 @@
 
 void main() {
   test('the generated binstub runs a snapshotted executable', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'foo-script': 'script'}
-      }, contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'foo-script': 'script'}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart b/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart
index 25a6281..8024c3a 100644
--- a/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart
+++ b/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart b/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart
index f59d8e2..cd477db 100644
--- a/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart
+++ b/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,13 +9,12 @@
 
 void main() {
   test('the binstubs runs a built snapshot if present', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'foo-script': 'script'}
-      }, contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'foo-script': 'script'}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/binstubs/creates_executables_in_pubspec_test.dart b/test/global/binstubs/creates_executables_in_pubspec_test.dart
index 1529d3e..a6604ab 100644
--- a/test/global/binstubs/creates_executables_in_pubspec_test.dart
+++ b/test/global/binstubs/creates_executables_in_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,17 +9,16 @@
 
 void main() {
   test('creates binstubs for each executable in the pubspec', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'one': null, 'two-renamed': 'second'}
-      }, contents: [
-        d.dir('bin', [
-          d.file('one.dart', "main(args) => print('one');"),
-          d.file('second.dart', "main(args) => print('two');"),
-          d.file('nope.dart', "main(args) => print('nope');")
-        ])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'one': null, 'two-renamed': 'second'}
+    }, contents: [
+      d.dir('bin', [
+        d.file('one.dart', "main(args) => print('one');"),
+        d.file('second.dart', "main(args) => print('two');"),
+        d.file('nope.dart', "main(args) => print('nope');")
+      ])
+    ]);
 
     await runPub(
         args: ['global', 'activate', 'foo'],
diff --git a/test/global/binstubs/does_not_warn_if_no_executables_test.dart b/test/global/binstubs/does_not_warn_if_no_executables_test.dart
index e172b1b..9814f15 100644
--- a/test/global/binstubs/does_not_warn_if_no_executables_test.dart
+++ b/test/global/binstubs/does_not_warn_if_no_executables_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,12 +9,10 @@
 
 void main() {
   test('does not warn if the package has no executables', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     await runPub(
         args: ['global', 'activate', 'foo'],
diff --git a/test/global/binstubs/does_not_warn_if_on_path_test.dart b/test/global/binstubs/does_not_warn_if_on_path_test.dart
index 07cba16..9b11386 100644
--- a/test/global/binstubs/does_not_warn_if_on_path_test.dart
+++ b/test/global/binstubs/does_not_warn_if_on_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -14,14 +12,12 @@
 
 void main() {
   test('does not warn if the binstub directory is on the path', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'script': null}
-      }, contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'script': null}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     // Add the test's cache bin directory to the path.
     var binDir = p.dirname(Platform.executable);
diff --git a/test/global/binstubs/explicit_and_no_executables_options_test.dart b/test/global/binstubs/explicit_and_no_executables_options_test.dart
index d493bd7..68bbd5b 100644
--- a/test/global/binstubs/explicit_and_no_executables_options_test.dart
+++ b/test/global/binstubs/explicit_and_no_executables_options_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/global/binstubs/explicit_executables_test.dart b/test/global/binstubs/explicit_executables_test.dart
index e032c32..e721f73 100644
--- a/test/global/binstubs/explicit_executables_test.dart
+++ b/test/global/binstubs/explicit_executables_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/missing_script_test.dart b/test/global/binstubs/missing_script_test.dart
index 96b5e89..433f9cf 100644
--- a/test/global/binstubs/missing_script_test.dart
+++ b/test/global/binstubs/missing_script_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
diff --git a/test/global/binstubs/name_collision_test.dart b/test/global/binstubs/name_collision_test.dart
index 54afc02..75b72ce 100644
--- a/test/global/binstubs/name_collision_test.dart
+++ b/test/global/binstubs/name_collision_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/name_collision_with_overwrite_test.dart b/test/global/binstubs/name_collision_with_overwrite_test.dart
index caed6d1..6e5a869 100644
--- a/test/global/binstubs/name_collision_with_overwrite_test.dart
+++ b/test/global/binstubs/name_collision_with_overwrite_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/no_executables_flag_test.dart b/test/global/binstubs/no_executables_flag_test.dart
index d7bc037..39f91cd 100644
--- a/test/global/binstubs/no_executables_flag_test.dart
+++ b/test/global/binstubs/no_executables_flag_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart b/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart
index f4cd075..442dfbc 100644
--- a/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart
+++ b/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -15,7 +13,7 @@
 import 'utils.dart';
 
 /// The contents of the binstub for [executable], or `null` if it doesn't exist.
-String binStub(String executable) {
+String? binStub(String executable) {
   final f = File(p.join(d.sandbox, cachePath, 'bin', binStubName(executable)));
   if (f.existsSync()) {
     return f.readAsStringSync();
@@ -26,23 +24,22 @@
 void main() {
   test("an outdated binstub runs 'pub global run', which replaces old binstub",
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {
-          'foo-script': 'script',
-          'foo-script2': 'script',
-          'foo-script-not-installed': 'script',
-          'foo-another-script': 'another-script',
-          'foo-another-script-not-installed': 'another-script'
-        }
-      }, contents: [
-        d.dir('bin', [
-          d.file('script.dart', r"main(args) => print('ok $args');"),
-          d.file('another-script.dart',
-              r"main(args) => print('not so good $args');")
-        ])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {
+        'foo-script': 'script',
+        'foo-script2': 'script',
+        'foo-script-not-installed': 'script',
+        'foo-another-script': 'another-script',
+        'foo-another-script-not-installed': 'another-script'
+      }
+    }, contents: [
+      d.dir('bin', [
+        d.file('script.dart', r"main(args) => print('ok $args');"),
+        d.file(
+            'another-script.dart', r"main(args) => print('not so good $args');")
+      ])
+    ]);
 
     await runPub(args: [
       'global',
diff --git a/test/global/binstubs/outdated_snapshot_test.dart b/test/global/binstubs/outdated_snapshot_test.dart
index c1181b9..91bfff6 100644
--- a/test/global/binstubs/outdated_snapshot_test.dart
+++ b/test/global/binstubs/outdated_snapshot_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -15,14 +13,12 @@
 
 void main() {
   test("a binstub runs 'pub global run' for an outdated snapshot", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'foo-script': 'script'}
-      }, contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'foo-script': 'script'}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/binstubs/path_package_test.dart b/test/global/binstubs/path_package_test.dart
index d702054..0461e35 100644
--- a/test/global/binstubs/path_package_test.dart
+++ b/test/global/binstubs/path_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/reactivate_removes_old_executables_test.dart b/test/global/binstubs/reactivate_removes_old_executables_test.dart
index e7f6ade..80b50ff 100644
--- a/test/global/binstubs/reactivate_removes_old_executables_test.dart
+++ b/test/global/binstubs/reactivate_removes_old_executables_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/removes_even_if_not_in_pubspec_test.dart b/test/global/binstubs/removes_even_if_not_in_pubspec_test.dart
index ed215ae..4cd458e 100644
--- a/test/global/binstubs/removes_even_if_not_in_pubspec_test.dart
+++ b/test/global/binstubs/removes_even_if_not_in_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/binstubs/removes_when_deactivated_test.dart b/test/global/binstubs/removes_when_deactivated_test.dart
index d241f27..975defe 100644
--- a/test/global/binstubs/removes_when_deactivated_test.dart
+++ b/test/global/binstubs/removes_when_deactivated_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,16 +9,15 @@
 
 void main() {
   test('removes binstubs when the package is deactivated', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'one': null, 'two': null}
-      }, contents: [
-        d.dir('bin', [
-          d.file('one.dart', "main(args) => print('one');"),
-          d.file('two.dart', "main(args) => print('two');")
-        ])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'one': null, 'two': null}
+    }, contents: [
+      d.dir('bin', [
+        d.file('one.dart', "main(args) => print('one');"),
+        d.file('two.dart', "main(args) => print('two');")
+      ])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
     await runPub(args: ['global', 'deactivate', 'foo']);
diff --git a/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart b/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart
index d53af96..99eeaaf 100644
--- a/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart
+++ b/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -16,18 +14,17 @@
   test(
       'runs only once even when dart on path is a batch file (as in flutter/bin)',
       () async {
-    await servePackages((builder) {
-      builder.serve(
-        'foo',
-        '1.0.0',
-        contents: [
-          d.dir('bin', [d.file('script.dart', 'main(args) => print(args);')]),
-        ],
-        pubspec: {
-          'executables': {'script': 'script'},
-        },
-      );
-    });
+    final server = await servePackages();
+    server.serve(
+      'foo',
+      '1.0.0',
+      contents: [
+        d.dir('bin', [d.file('script.dart', 'main(args) => print(args);')]),
+      ],
+      pubspec: {
+        'executables': {'script': 'script'},
+      },
+    );
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/binstubs/unknown_explicit_executable_test.dart b/test/global/binstubs/unknown_explicit_executable_test.dart
index 0b27527..33b82e7 100644
--- a/test/global/binstubs/unknown_explicit_executable_test.dart
+++ b/test/global/binstubs/unknown_explicit_executable_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/global/binstubs/utils.dart b/test/global/binstubs/utils.dart
index e2346cc..59a8df0 100644
--- a/test/global/binstubs/utils.dart
+++ b/test/global/binstubs/utils.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -16,7 +14,7 @@
 ///
 /// The `pub`/`pub.bat` command on the PATH will be the one in tool/test-bin not
 /// the one from the sdk.
-Map getEnvironment() {
+Map<String, String> getEnvironment() {
   var binDir = p.dirname(Platform.resolvedExecutable);
   var separator = Platform.isWindows ? ';' : ':';
   var pubBin = p.absolute('tool', 'test-bin');
diff --git a/test/global/binstubs/warns_if_not_on_path_test.dart b/test/global/binstubs/warns_if_not_on_path_test.dart
index fcfdb0c..d98bf76 100644
--- a/test/global/binstubs/warns_if_not_on_path_test.dart
+++ b/test/global/binstubs/warns_if_not_on_path_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,14 +9,12 @@
 
 void main() {
   test('warns if the binstub directory is not on the path', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'executables': {'some-dart-script': 'script'}
-      }, contents: [
-        d.dir(
-            'bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'executables': {'some-dart-script': 'script'}
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok \$args');")])
+    ]);
 
     await runPub(
         args: ['global', 'activate', 'foo'],
diff --git a/test/global/deactivate/deactivate_and_reactivate_package_test.dart b/test/global/deactivate/deactivate_and_reactivate_package_test.dart
index ffad990..da33486 100644
--- a/test/global/deactivate/deactivate_and_reactivate_package_test.dart
+++ b/test/global/deactivate/deactivate_and_reactivate_package_test.dart
@@ -2,18 +2,15 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../test_pub.dart';
 
 void main() {
   test('activates a different version after deactivating', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0');
 
     // Activate an old version.
     await runPub(args: ['global', 'activate', 'foo', '1.0.0']);
diff --git a/test/global/deactivate/git_package_test.dart b/test/global/deactivate/git_package_test.dart
index 3aa047d..7fc2855 100644
--- a/test/global/deactivate/git_package_test.dart
+++ b/test/global/deactivate/git_package_test.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -20,9 +19,10 @@
 
     await runPub(args: ['global', 'activate', '-sgit', '../foo.git']);
 
+    final locationUri = p.toUri(p.join(d.sandbox, 'foo.git'));
     await runPub(
         args: ['global', 'deactivate', 'foo'],
         output:
-            'Deactivated package foo 1.0.0 from Git repository "../foo.git".');
+            'Deactivated package foo 1.0.0 from Git repository "$locationUri".');
   });
 }
diff --git a/test/global/deactivate/hosted_package_test.dart b/test/global/deactivate/hosted_package_test.dart
index 884e70f..fa91829 100644
--- a/test/global/deactivate/hosted_package_test.dart
+++ b/test/global/deactivate/hosted_package_test.dart
@@ -2,15 +2,14 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../test_pub.dart';
 
 void main() {
   test('deactivates an active hosted package', () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/deactivate/missing_package_arg_test.dart b/test/global/deactivate/missing_package_arg_test.dart
index 7d41b82..8de8ab1 100644
--- a/test/global/deactivate/missing_package_arg_test.dart
+++ b/test/global/deactivate/missing_package_arg_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,13 +9,10 @@
 
 void main() {
   test('fails if no package was given', () {
-    return runPub(args: ['global', 'deactivate'], error: '''
-            No package to deactivate given.
-
-            Usage: pub global deactivate <package>
-            -h, --help    Print this usage information.
-
-            Run "pub help" to see global options.
-            ''', exitCode: exit_codes.USAGE);
+    return runPub(
+      args: ['global', 'deactivate'],
+      error: contains('No package to deactivate given.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/global/deactivate/path_package_test.dart b/test/global/deactivate/path_package_test.dart
index f28d8d1..1f50713 100644
--- a/test/global/deactivate/path_package_test.dart
+++ b/test/global/deactivate/path_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
diff --git a/test/global/deactivate/removes_precompiled_snapshots_test.dart b/test/global/deactivate/removes_precompiled_snapshots_test.dart
index 7d075b3..a7670e2 100644
--- a/test/global/deactivate/removes_precompiled_snapshots_test.dart
+++ b/test/global/deactivate/removes_precompiled_snapshots_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,7 +9,8 @@
 
 void main() {
   test('removes built snapshots', () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0'));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/deactivate/unexpected_arguments_test.dart b/test/global/deactivate/unexpected_arguments_test.dart
index d5748f2..dd1f11f 100644
--- a/test/global/deactivate/unexpected_arguments_test.dart
+++ b/test/global/deactivate/unexpected_arguments_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -12,13 +10,9 @@
 void main() {
   test('fails if there are extra arguments', () {
     return runPub(
-        args: ['global', 'deactivate', 'foo', 'bar', 'baz'], error: '''
-            Unexpected arguments "bar" and "baz".
-
-            Usage: pub global deactivate <package>
-            -h, --help    Print this usage information.
-
-            Run "pub help" to see global options.
-            ''', exitCode: exit_codes.USAGE);
+      args: ['global', 'deactivate', 'foo', 'bar', 'baz'],
+      error: contains('Unexpected arguments "bar" and "baz".'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/global/deactivate/unknown_package_test.dart b/test/global/deactivate/unknown_package_test.dart
index ad0973e..35023ad 100644
--- a/test/global/deactivate/unknown_package_test.dart
+++ b/test/global/deactivate/unknown_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,7 +9,7 @@
 
 void main() {
   test('errors if the package is not activated', () async {
-    await serveNoPackages();
+    await servePackages();
 
     await runPub(
         args: ['global', 'deactivate', 'foo'],
diff --git a/test/global/list_test.dart b/test/global/list_test.dart
index 8b7293a..00ca22c 100644
--- a/test/global/list_test.dart
+++ b/test/global/list_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 
@@ -14,9 +12,8 @@
 
 void main() {
   test('lists an activated hosted package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await runPub(args: ['global', 'activate', 'foo']);
 
@@ -33,9 +30,10 @@
 
     await runPub(args: ['global', 'activate', '-sgit', '../foo.git']);
 
+    final locationUri = p.toUri(p.join(d.sandbox, 'foo.git'));
     await runPub(
         args: ['global', 'list'],
-        output: 'foo 1.0.0 from Git repository "../foo.git"');
+        output: 'foo 1.0.0 from Git repository "$locationUri"');
   });
 
   test('lists an activated Path package', () async {
@@ -51,11 +49,10 @@
   });
 
   test('lists activated packages in alphabetical order', () async {
-    await servePackages((builder) {
-      builder.serve('aaa', '1.0.0');
-      builder.serve('bbb', '1.0.0');
-      builder.serve('ccc', '1.0.0');
-    });
+    await servePackages()
+      ..serve('aaa', '1.0.0')
+      ..serve('bbb', '1.0.0')
+      ..serve('ccc', '1.0.0');
 
     await runPub(args: ['global', 'activate', 'ccc']);
     await runPub(args: ['global', 'activate', 'aaa']);
diff --git a/test/global/run/errors_if_outside_bin_test.dart b/test/global/run/errors_if_outside_bin_test.dart
index 497f26a..72bcd4f 100644
--- a/test/global/run/errors_if_outside_bin_test.dart
+++ b/test/global/run/errors_if_outside_bin_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -12,27 +10,17 @@
 
 void main() {
   test('errors if the script is in a subdirectory.', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('example', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('example', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
-    await runPub(args: ['global', 'run', 'foo:example/script'], error: '''
-Cannot run an executable in a subdirectory of a global package.
-
-Usage: pub global run <package>:<executable> [args...]
--h, --help                              Print this usage information.
-    --[no-]enable-asserts               Enable assert statements.
-    --enable-experiment=<experiment>    Runs the executable in a VM with the
-                                        given experiments enabled. (Will disable
-                                        snapshotting, resulting in slower
-                                        startup).
-    --[no-]sound-null-safety            Override the default null safety
-                                        execution mode.
-
-Run "pub help" to see global options.
-''', exitCode: exit_codes.USAGE);
+    await runPub(
+      args: ['global', 'run', 'foo:example/script'],
+      error: contains(
+          'Cannot run an executable in a subdirectory of a global package.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/global/run/fails_if_sdk_constraint_is_unmet_test.dart b/test/global/run/fails_if_sdk_constraint_is_unmet_test.dart
index 73b5556..3bef340 100644
--- a/test/global/run/fails_if_sdk_constraint_is_unmet_test.dart
+++ b/test/global/run/fails_if_sdk_constraint_is_unmet_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -12,11 +10,10 @@
 
 void main() {
   test("fails if the current SDK doesn't match the constraint", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
@@ -43,15 +40,14 @@
   });
 
   test('fails if SDK is downgraded below the constraints', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'environment': {
-          'sdk': '>=2.0.0 <3.0.0',
-        },
-      }, contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('123-OK');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'environment': {
+        'sdk': '>=2.0.0 <3.0.0',
+      },
+    }, contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('123-OK');")])
+    ]);
 
     await runPub(
       environment: {'_PUB_TEST_SDK_VERSION': '2.0.0'},
@@ -71,8 +67,8 @@
   });
 
   test('fails if SDK is downgraded below dependency SDK constraints', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {
         'bar': '^1.0.0',
       }, pubspec: {
         'environment': {
@@ -80,13 +76,12 @@
         },
       }, contents: [
         d.dir('bin', [d.file('script.dart', "main(args) => print('123-OK');")])
-      ]);
-      builder.serve('bar', '1.0.0', pubspec: {
+      ])
+      ..serve('bar', '1.0.0', pubspec: {
         'environment': {
           'sdk': '>=2.2.0 <3.0.0',
         },
       });
-    });
 
     await runPub(
       environment: {'_PUB_TEST_SDK_VERSION': '2.2.0'},
diff --git a/test/global/run/implicit_executable_name_test.dart b/test/global/run/implicit_executable_name_test.dart
index a2f8c12..9757772 100644
--- a/test/global/run/implicit_executable_name_test.dart
+++ b/test/global/run/implicit_executable_name_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('defaults to the package name if the script is omitted', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('foo.dart', "main(args) => print('foo');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('foo.dart', "main(args) => print('foo');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/missing_executable_arg_test.dart b/test/global/run/missing_executable_arg_test.dart
index 2bb8ec8..b64d387 100644
--- a/test/global/run/missing_executable_arg_test.dart
+++ b/test/global/run/missing_executable_arg_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,20 +9,10 @@
 
 void main() {
   test('fails if no executable was given', () {
-    return runPub(args: ['global', 'run'], error: '''
-Must specify an executable to run.
-
-Usage: pub global run <package>:<executable> [args...]
--h, --help                              Print this usage information.
-    --[no-]enable-asserts               Enable assert statements.
-    --enable-experiment=<experiment>    Runs the executable in a VM with the
-                                        given experiments enabled. (Will disable
-                                        snapshotting, resulting in slower
-                                        startup).
-    --[no-]sound-null-safety            Override the default null safety
-                                        execution mode.
-
-Run "pub help" to see global options.
-''', exitCode: exit_codes.USAGE);
+    return runPub(
+      args: ['global', 'run'],
+      error: contains('Must specify an executable to run.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/global/run/missing_path_package_test.dart b/test/global/run/missing_path_package_test.dart
index f15d976..99cf30a 100644
--- a/test/global/run/missing_path_package_test.dart
+++ b/test/global/run/missing_path_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
diff --git a/test/global/run/nonexistent_script_test.dart b/test/global/run/nonexistent_script_test.dart
index dc0a738..8ff0a42 100644
--- a/test/global/run/nonexistent_script_test.dart
+++ b/test/global/run/nonexistent_script_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
@@ -12,9 +10,10 @@
 
 void main() {
   test('errors if the script does not exist.', () async {
-    await servePackages((builder) => builder.serve('foo', '1.0.0', pubspec: {
-          'dev_dependencies': {'bar': '1.0.0'}
-        }));
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'dev_dependencies': {'bar': '1.0.0'}
+    });
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/package_api_test.dart b/test/global/run/package_api_test.dart
index c95fc5b..5db401b 100644
--- a/test/global/run/package_api_test.dart
+++ b/test/global/run/package_api_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
@@ -12,10 +10,9 @@
 
 void main() {
   test('an immutable application sees a file: package config', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0');
-
-      builder.serve('foo', '1.0.0', deps: {
+    await servePackages()
+      ..serve('bar', '1.0.0')
+      ..serve('foo', '1.0.0', deps: {
         'bar': '1.0.0'
       }, contents: [
         d.dir('bin', [
@@ -33,7 +30,6 @@
 """)
         ])
       ]);
-    });
 
     await runPub(args: ['global', 'activate', 'foo']);
 
@@ -45,12 +41,12 @@
         'global_packages/foo/.dart_tool/package_config.json');
     expect(pub.stdout, emits(p.toUri(packageConfigPath).toString()));
 
-    var fooResourcePath = p.join(
-        globalPackageServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
+    var fooResourcePath =
+        p.join(globalServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(fooResourcePath).toString()));
 
-    var barResourcePath = p.join(
-        globalPackageServer.pathInCache('bar', '1.0.0'), 'lib/resource.txt');
+    var barResourcePath =
+        p.join(globalServer.pathInCache('bar', '1.0.0'), 'lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(barResourcePath).toString()));
     await pub.shouldExit(0);
   });
diff --git a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
index 4ace362..24e65bf 100644
--- a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
+++ b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -13,11 +11,10 @@
 
 void main() {
   test('recompiles a script if the snapshot is out-of-date', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/reflects_changes_to_local_package_test.dart b/test/global/run/reflects_changes_to_local_package_test.dart
index 5bc9e55..864111b 100644
--- a/test/global/run/reflects_changes_to_local_package_test.dart
+++ b/test/global/run/reflects_changes_to_local_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/run/runs_git_script_test.dart b/test/global/run/runs_git_script_test.dart
index 8d120b4..900b586 100644
--- a/test/global/run/runs_git_script_test.dart
+++ b/test/global/run/runs_git_script_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/run/runs_path_script_test.dart b/test/global/run/runs_path_script_test.dart
index 90db648..7cc90d4 100644
--- a/test/global/run/runs_path_script_test.dart
+++ b/test/global/run/runs_path_script_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/global/run/runs_script_in_checked_mode_test.dart b/test/global/run/runs_script_in_checked_mode_test.dart
index 1b399c5..d0511cf 100644
--- a/test/global/run/runs_script_in_checked_mode_test.dart
+++ b/test/global/run/runs_script_in_checked_mode_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('runs a script with assertions enabled', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', 'main() { assert(false); }')])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', 'main() { assert(false); }')])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/runs_script_in_unchecked_mode_test.dart b/test/global/run/runs_script_in_unchecked_mode_test.dart
index 005bad3..ca752b2 100644
--- a/test/global/run/runs_script_in_unchecked_mode_test.dart
+++ b/test/global/run/runs_script_in_unchecked_mode_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
 
-const SCRIPT = '''
+const _script = '''
 main() {
   assert(false);
   print("no checks");
@@ -18,11 +16,10 @@
 
 void main() {
   test('runs a script in unchecked mode by default', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', SCRIPT)])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', _script)])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/runs_script_test.dart b/test/global/run/runs_script_test.dart
index bab4d1c..f3f0e99 100644
--- a/test/global/run/runs_script_test.dart
+++ b/test/global/run/runs_script_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,10 @@
 
 void main() {
   test('runs a script in an activated package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/runs_script_without_packages_file_test.dart b/test/global/run/runs_script_without_packages_file_test.dart
index aa5e691..dc225ba 100644
--- a/test/global/run/runs_script_without_packages_file_test.dart
+++ b/test/global/run/runs_script_without_packages_file_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -14,11 +12,10 @@
 void main() {
   test('runs a snapshotted script without a .dart_tool/package_config file',
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
+    ]);
 
     await runPub(args: ['global', 'activate', 'foo']);
 
diff --git a/test/global/run/unknown_package_test.dart b/test/global/run/unknown_package_test.dart
index 55a875a..adcb666 100644
--- a/test/global/run/unknown_package_test.dart
+++ b/test/global/run/unknown_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -11,7 +9,7 @@
 
 void main() {
   test('errors if the package is not activated', () async {
-    await serveNoPackages();
+    await servePackages();
 
     await runPub(
         args: ['global', 'run', 'foo:bar'],
diff --git a/test/global/run/uses_old_lockfile_test.dart b/test/global/run/uses_old_lockfile_test.dart
deleted file mode 100644
index 4ae12ef..0000000
--- a/test/global/run/uses_old_lockfile_test.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2014, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('uses the 1.6-style lockfile if necessary', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0');
-      builder.serve('foo', '1.0.0', deps: {
-        'bar': 'any'
-      }, contents: [
-        d.dir('bin', [
-          d.file('script.dart', """
-              import 'package:bar/bar.dart' as bar;
-
-              main(args) => print(bar.main());""")
-        ])
-      ]);
-    });
-
-    await runPub(args: ['cache', 'add', 'foo']);
-    await runPub(args: ['cache', 'add', 'bar']);
-
-    await d.dir(cachePath, [
-      d.dir('global_packages', [
-        d.file('foo.lock', '''
-packages:
-  foo:
-    description: foo
-    source: hosted
-    version: "1.0.0"
-  bar:
-    description: bar
-    source: hosted
-    version: "1.0.0"''')
-      ])
-    ]).create();
-
-    var pub = await pubRun(global: true, args: ['foo:script']);
-    expect(pub.stdout, emitsThrough('bar 1.0.0'));
-    await pub.shouldExit();
-
-    await d.dir(cachePath, [
-      d.dir('global_packages', [
-        d.nothing('foo.lock'),
-        d.dir('foo', [d.file('pubspec.lock', contains('1.0.0'))])
-      ])
-    ]).validate();
-  });
-}
diff --git a/test/golden_file.dart b/test/golden_file.dart
index 055c5c5..7927776 100644
--- a/test/golden_file.dart
+++ b/test/golden_file.dart
@@ -2,35 +2,210 @@
 // 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.
 
-// @dart=2.10
-
+import 'dart:async';
 import 'dart:io';
 
-import 'package:path/path.dart' as path;
+import 'package:path/path.dart' as p;
+import 'package:pub/src/ascii_tree.dart' as ascii_tree;
+import 'package:pub/src/io.dart';
+import 'package:stack_trace/stack_trace.dart' show Trace;
 import 'package:test/test.dart';
 
-/// Will test [actual] against the contests of the file at [goldenFilePath].
-///
-/// If the file doesn't exist, the file is instead created containing [actual].
-void expectMatchesGoldenFile(String actual, String goldenFilePath) {
-  var goldenFile = File(goldenFilePath);
-  if (goldenFile.existsSync()) {
-    expect(
-        actual, equals(goldenFile.readAsStringSync().replaceAll('\r\n', '\n')),
-        reason: 'goldenFilePath: "$goldenFilePath"');
-  } else {
-    // This enables writing the updated file when run in otherwise hermetic
-    // settings.
-    //
-    // This is to make updating the golden files easier in a bazel environment
-    // See https://docs.bazel.build/versions/2.0.0/user-manual.html#run .
-    final workspaceDirectory =
-        Platform.environment['BUILD_WORKSPACE_DIRECTORY'];
-    if (workspaceDirectory != null) {
-      goldenFile = File(path.join(workspaceDirectory, goldenFilePath));
-    }
-    goldenFile
-      ..createSync(recursive: true)
-      ..writeAsStringSync(actual);
+import 'ascii_tree_test.dart';
+import 'descriptor.dart' as d;
+import 'test_pub.dart';
+
+final _isCI = () {
+  final p = RegExp(r'^1|(?:true)$', caseSensitive: false);
+  final ci = Platform.environment['CI'];
+  return ci != null && ci.isNotEmpty && p.hasMatch(ci);
+}();
+
+/// Find the current `_test.dart` filename invoked from stack-trace.
+String _findCurrentTestFilename() => Trace.current()
+    .frames
+    .lastWhere(
+      (frame) =>
+          frame.uri.isScheme('file') &&
+          p.basename(frame.uri.toFilePath()).endsWith('_test.dart'),
+    )
+    .uri
+    .toFilePath();
+
+class GoldenTestContext {
+  static const _endOfSection = ''
+      '--------------------------------'
+      ' END OF OUTPUT '
+      '---------------------------------\n\n';
+
+  late final String _currentTestFile;
+  late final String _testName;
+
+  late String _goldenFilePath;
+  late File _goldenFile;
+  late String _header;
+  final _results = <String>[];
+  late bool _goldenFileExists;
+  bool _generatedNewData = false; // track if new data is generated
+  int _nextSectionIndex = 0;
+
+  GoldenTestContext._(this._currentTestFile, this._testName) {
+    final rel = p.relative(
+      _currentTestFile.replaceAll(RegExp(r'\.dart$'), ''),
+      from: p.join(p.current, 'test'),
+    );
+    _goldenFilePath = p.join(
+      'test',
+      'testdata',
+      'goldens',
+      rel,
+      // Sanitize the name, and add .txt
+      _testName.replaceAll(RegExp(r'[<>:"/\|?*%#]'), '~') + '.txt',
+    );
+    _goldenFile = File(_goldenFilePath);
+    _header = '# GENERATED BY: ${p.relative(_currentTestFile)}\n\n';
   }
+
+  void _readGoldenFile() {
+    _goldenFileExists = _goldenFile.existsSync();
+
+    // Read the golden file for this test
+    if (_goldenFileExists) {
+      var text = _goldenFile.readAsStringSync().replaceAll('\r\n', '\n');
+      // Strip header line
+      if (text.startsWith('#') && text.contains('\n\n')) {
+        text = text.substring(text.indexOf('\n\n') + 2);
+      }
+      _results.addAll(text.split(_endOfSection));
+    }
+  }
+
+  /// Expect section [sectionIndex] to match [actual].
+  void _expectSection(int sectionIndex, String actual) {
+    if (_goldenFileExists &&
+        _results.length > sectionIndex &&
+        _results[sectionIndex].isNotEmpty) {
+      expect(
+        actual,
+        equals(_results[sectionIndex]),
+        reason: 'Expect matching section $sectionIndex from "$_goldenFilePath"',
+      );
+    } else {
+      while (_results.length <= sectionIndex) {
+        _results.add('');
+      }
+      _results[sectionIndex] = actual;
+      _generatedNewData = true;
+    }
+  }
+
+  void _writeGoldenFile() {
+    // If we generated new data, then we need to write a new file, and fail the
+    // test case, or mark it as skipped.
+    if (_generatedNewData) {
+      // This enables writing the updated file when run in otherwise hermetic
+      // settings.
+      //
+      // This is to make updating the golden files easier in a bazel environment
+      // See https://docs.bazel.build/versions/2.0.0/user-manual.html#run .
+      var goldenFile = _goldenFile;
+      final workspaceDirectory =
+          Platform.environment['BUILD_WORKSPACE_DIRECTORY'];
+      if (workspaceDirectory != null) {
+        goldenFile = File(p.join(workspaceDirectory, _goldenFilePath));
+      }
+      goldenFile
+        ..createSync(recursive: true)
+        ..writeAsStringSync(_header + _results.join(_endOfSection));
+
+      // If running in CI we should fail if the golden file doesn't already
+      // exist, or is missing entries.
+      // This typically happens if we forgot to commit a file to git.
+      if (_isCI) {
+        fail('Missing golden file: "$_goldenFilePath", '
+            'try running tests again and commit the file');
+      } else {
+        // If not running in CI, then we consider the test as skipped, we've
+        // generated the file, but the user should run the tests again.
+        // Or push to CI in which case we'll run the tests again anyways.
+        markTestSkipped(
+          'Generated golden file: "$_goldenFilePath" instead of running test',
+        );
+      }
+    }
+  }
+
+  /// Expect the next section in the golden file to match [actual].
+  ///
+  /// This will create the section if it is missing.
+  ///
+  /// **Warning**: Take care when using this in an async context, sections are
+  /// numbered based on the other in which calls are made. Hence, ensure
+  /// consistent ordering of calls.
+  void expectNextSection(String actual) =>
+      _expectSection(_nextSectionIndex++, actual);
+
+  /// Run `pub` [args] with [environment] variables in [workingDirectory], and
+  /// log stdout/stderr and exitcode to golden file.
+  Future<void> run(
+    List<String> args, {
+    Map<String, String>? environment,
+    String? workingDirectory,
+  }) async {
+    // Create new section index number (before doing anything async)
+    final sectionIndex = _nextSectionIndex++;
+    final s = StringBuffer();
+    s.writeln('## Section $sectionIndex');
+    await runPubIntoBuffer(
+      args,
+      s,
+      environment: environment,
+      workingDirectory: workingDirectory,
+    );
+
+    _expectSection(sectionIndex, s.toString());
+  }
+
+  /// Log directory tree structure under [directory] to golden file.
+  Future<void> tree([String? directory]) async {
+    // Create new section index number (before doing anything async)
+    final sectionIndex = _nextSectionIndex++;
+
+    final target = p.join(d.sandbox, directory ?? '.');
+
+    final s = StringBuffer();
+    s.writeln('## Section $sectionIndex');
+    if (directory != null) {
+      s.writeln('\$ cd $directory');
+    }
+    s.writeln('\$ tree');
+    s.writeln(stripColors(ascii_tree.fromFiles(
+      listDir(target, recursive: true),
+      baseDir: target,
+    )));
+
+    _expectSection(sectionIndex, s.toString());
+  }
+}
+
+/// Create a [test] with [GoldenTestContext] which allows running golden tests.
+///
+/// This will create a golden file containing output of calls to:
+///  * [GoldenTestContext.run]
+///  * [GoldenTestContext.tree]
+///
+/// The golden file with the recorded output will be created at:
+///   `test/testdata/goldens/path/to/myfile_test/<name>.txt`
+/// , when `path/to/myfile_test.dart` is the `_test.dart` file from which this
+/// function is called.
+void testWithGolden(
+  String name,
+  FutureOr<void> Function(GoldenTestContext ctx) fn,
+) {
+  final ctx = GoldenTestContext._(_findCurrentTestFilename(), name);
+  test(name, () async {
+    ctx._readGoldenFile();
+    await fn(ctx);
+    ctx._writeGoldenFile();
+  });
 }
diff --git a/test/goldens/directory_option.txt b/test/goldens/directory_option.txt
deleted file mode 100644
index 356480c..0000000
--- a/test/goldens/directory_option.txt
+++ /dev/null
@@ -1,84 +0,0 @@
-$ pub add --directory=myapp foo
-Resolving dependencies in myapp...
-+ foo 1.0.0
-Changed 1 dependency in myapp!
-
-$ pub -C myapp add bar
-Resolving dependencies in myapp...
-+ bar 1.2.3
-Changed 1 dependency in myapp!
-
-$ pub -C myapp/example get --directory=myapp bar
-Resolving dependencies in myapp...
-Got dependencies in myapp!
-
-$ pub remove bar -C myapp
-Resolving dependencies in myapp...
-These packages are no longer being depended on:
-- bar 1.2.3
-Changed 1 dependency in myapp!
-
-$ pub get bar -C myapp
-Resolving dependencies in myapp...
-Got dependencies in myapp!
-
-$ pub get bar -C myapp/example
-Resolving dependencies in myapp/example...
-+ foo 1.0.0
-+ test_pkg 1.0.0 from path myapp
-Changed 2 dependencies in myapp/example!
-
-$ pub get bar -C myapp/example2
-Resolving dependencies in myapp/example2...
-[ERR] Error on line 1, column 9 of myapp/pubspec.yaml: "name" field doesn't match expected name "myapp".
-[ERR]   â•·
-[ERR] 1 │ {"name":"test_pkg","version":"1.0.0","homepage":"http://pub.dartlang.org","description":"A package, I guess.","environment":{"sdk":">=1.8.0 <=2.0.0"}, dependencies: { foo: ^1.0.0}}
-[ERR]   │         ^^^^^^^^^^
-[ERR]   ╵
-[Exit code] 65
-
-$ pub get bar -C myapp/broken_dir
-[ERR] Could not find a file named "pubspec.yaml" in "$SANDBOX/myapp/broken_dir".
-[Exit code] 66
-
-$ pub downgrade -C myapp
-Resolving dependencies in myapp...
-  foo 1.0.0
-No dependencies changed in myapp.
-
-$ pub upgrade bar -C myapp
-Resolving dependencies in myapp...
-  foo 1.0.0
-No dependencies changed in myapp.
-
-$ pub run -C myapp bin/app.dart
-Building package executable...
-Built test_pkg:app.
-Hi
-
-$ pub publish -C myapp --dry-run
-Publishing test_pkg 1.0.0 to http://localhost:$PORT:
-|-- CHANGELOG.md
-|-- LICENSE
-|-- README.md
-|-- bin
-|   '-- app.dart
-|-- example
-|   '-- pubspec.yaml
-|-- example2
-|   '-- pubspec.yaml
-|-- lib
-|   '-- test_pkg.dart
-'-- pubspec.yaml
-The server may enforce additional checks.
-[ERR] 
-[ERR] Package has 0 warnings.
-
-$ pub uploader -C myapp add sigurdm@google.com
-Good job!
-
-$ pub deps -C myapp
-Dart SDK 1.12.0
-test_pkg 1.0.0
-'-- foo 1.0.0
-
diff --git a/test/goldens/help.txt b/test/goldens/help.txt
deleted file mode 100644
index 10997b4..0000000
--- a/test/goldens/help.txt
+++ /dev/null
@@ -1,386 +0,0 @@
-[command]
-> pub add --help
-[stdout]
-Add a dependency to pubspec.yaml.
-
-Usage: pub add <package>[:<constraint>] [options]
--h, --help               Print this usage information.
--d, --dev                Adds package to the development dependencies instead.
-    --git-url            Git URL of the package
-    --git-ref            Git branch or commit to be retrieved
-    --git-path           Path of git package in repository
-    --hosted-url         URL of package host server
-    --path               Local path
-    --sdk                SDK source for package
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-add for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub build --help
-[stdout]
-Deprecated command
-
-Usage: pub build <subcommand> [arguments...]
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub cache --help
-[stdout]
-Work with the system cache.
-
-Usage: pub cache [arguments...]
--h, --help    Print this usage information.
-
-Available subcommands:
-  add      Install a package.
-  repair   Reinstall cached packages.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub cache add --help
-[stdout]
-Install a package.
-
-Usage: pub cache add <package> [--version <constraint>] [--all]
--h, --help       Print this usage information.
-    --all        Install all matching versions.
--v, --version    Version constraint.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub cache list --help
-[stdout]
-List packages in the system cache.
-
-Usage: pub cache list <subcommand> [arguments...]
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub cache repair --help
-[stdout]
-Reinstall cached packages.
-
-Usage: pub cache repair <subcommand> [arguments...]
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub deps --help
-[stdout]
-Print package dependencies.
-
-Usage: pub deps [arguments...]
--h, --help           Print this usage information.
--s, --style          How output should be displayed.
-                     [compact, tree (default), list]
-    --[no-]dev       Whether to include dev dependencies.
-                     (defaults to on)
-    --executables    List all available executables.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-deps for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub downgrade --help
-[stdout]
-Downgrade the current package's dependencies to oldest versions.
-
-This doesn't modify the lockfile, so it can be reset with "pub get".
-
-Usage: pub downgrade [dependencies...]
--h, --help            Print this usage information.
-    --[no-]offline    Use cached packages instead of accessing the network.
--n, --dry-run         Report what dependencies would change but don't change
-                      any.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-downgrade for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub global --help
-[stdout]
-Work with global packages.
-
-Usage: pub global [arguments...]
--h, --help    Print this usage information.
-
-Available subcommands:
-  activate     Make a package's executables globally available.
-  deactivate   Remove a previously activated package.
-  list         List globally activated packages.
-  run          Run an executable from a globally activated package.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub get --help
-[stdout]
-Get the current package's dependencies.
-
-Usage: pub get <subcommand> [arguments...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-get for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub list-package-dirs --help
-[stdout]
-Print local paths to dependencies.
-
-Usage: pub list-package-dirs
--h, --help      Print this usage information.
-    --format    How output should be displayed.
-                [json]
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub publish --help
-[stdout]
-Publish the current package to pub.dartlang.org.
-
-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.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub outdated --help
-[stdout]
-Analyze your dependencies to find which ones can be upgraded.
-
-Usage: pub outdated [options]
--h, --help                         Print this usage information.
-    --[no-]color                   Whether to color the output.
-                                   Defaults to color when connected to a
-                                   terminal, and no-color otherwise.
-    --[no-]dependency-overrides    Show resolutions with `dependency_overrides`.
-                                   (defaults to on)
-    --[no-]dev-dependencies        Take dev dependencies into account.
-                                   (defaults to on)
-    --json                         Output the results using a json format.
-    --mode=<PROPERTY>              Highlight versions with PROPERTY.
-                                   Only packages currently missing that PROPERTY
-                                   will be included unless --show-all.
-                                   [outdated (default), null-safety]
-    --[no-]prereleases             Include prereleases in latest version.
-                                   (defaults to on in --mode=null-safety).
-    --[no-]show-all                Include dependencies that are already
-                                   fullfilling --mode.
-    --[no-]transitive              Show transitive dependencies.
-                                   (defaults to off in --mode=null-safety).
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-outdated for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub remove --help
-[stdout]
-Removes a dependency from the current package.
-
-Usage: pub remove <package>
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-remove for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub run --help
-[stdout]
-Run an executable from a package.
-
-Usage: pub run <executable> [arguments...]
--h, --help                              Print this usage information.
-    --[no-]enable-asserts               Enable assert statements.
-    --enable-experiment=<experiment>    Runs the executable in a VM with the
-                                        given experiments enabled.
-                                        (Will disable snapshotting, resulting in
-                                        slower startup).
-    --[no-]sound-null-safety            Override the default null safety
-                                        execution mode.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub serve --help
-[stdout]
-Deprecated command
-
-Usage: pub serve <subcommand> [arguments...]
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub upgrade --help
-[stdout]
-Upgrade the current package's dependencies to latest versions.
-
-Usage: pub upgrade [dependencies...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
-    --null-safety        Upgrade constraints in pubspec.yaml to null-safety
-                         versions
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-upgrade for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub uploader --help
-[stdout]
-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)
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-uploader for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub login --help
-[stdout]
-Log into pub.dev.
-
-Usage: pub login
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub logout --help
-[stdout]
-Log out of pub.dev.
-
-Usage: pub logout <subcommand> [arguments...]
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub version --help
-[stdout]
-Print pub version.
-
-Usage: pub version
--h, --help    Print this usage information.
-
-Run "pub help" to see global options.
-[stderr]
-
-[exitCode]
-0
-
diff --git a/test/goldens/upgrade_major_versions_example.txt b/test/goldens/upgrade_major_versions_example.txt
deleted file mode 100644
index 448a874..0000000
--- a/test/goldens/upgrade_major_versions_example.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-$ pub upgrade --major-versions --example
-Resolving dependencies...
-+ bar 2.0.0
-Changed 1 dependency!
-
-Changed 1 constraint in pubspec.yaml:
-  bar: ^1.0.0 -> ^2.0.0
-Resolving dependencies in ./example...
-Got dependencies in ./example.
-[ERR] Running `upgrade --major-versions` only in `.`. Run `dart pub upgrade --major-versions --directory example/` separately.
-
-$ pub upgrade --major-versions --directory example
-Resolving dependencies in example...
-  bar 2.0.0
-> foo 2.0.0 (was 1.0.0)
-  myapp 0.0.0 from path .
-Changed 1 dependency in example!
-
-Changed 1 constraint in pubspec.yaml:
-  foo: ^1.0.0 -> ^2.0.0
-
diff --git a/test/goldens/usage_exception.txt b/test/goldens/usage_exception.txt
deleted file mode 100644
index 5c7ead7..0000000
--- a/test/goldens/usage_exception.txt
+++ /dev/null
@@ -1,66 +0,0 @@
-[command]
-> pub 
-[stdout]
-Pub is a package manager for Dart.
-
-Usage: pub <command> [arguments]
-
-Global options:
--h, --help             Print this usage information.
-    --version          Print pub version.
-    --[no-]trace       Print debugging information when an error occurs.
-    --verbosity        Control output verbosity.
-
-          [all]        Show all output including internal tracing messages.
-          [error]      Show only errors.
-          [io]         Also show IO operations.
-          [normal]     Show errors, warnings, and user messages.
-          [solver]     Show steps during version resolution.
-          [warning]    Show only errors and warnings.
-
--v, --verbose          Shortcut for "--verbosity=all".
-
-Available commands:
-  add         Add a dependency to pubspec.yaml.
-  cache       Work with the system cache.
-  deps        Print package dependencies.
-  downgrade   Downgrade the current package's dependencies to oldest versions.
-  get         Get the current package's dependencies.
-  global      Work with global packages.
-  login       Log into pub.dev.
-  logout      Log out of pub.dev.
-  outdated    Analyze your dependencies to find which ones can be upgraded.
-  publish     Publish the current package to pub.dartlang.org.
-  remove      Removes a dependency from the current package.
-  run         Run an executable from a package.
-  upgrade     Upgrade the current package's dependencies to latest versions.
-  uploader    Manage uploaders for a package on pub.dartlang.org.
-  version     Print pub version.
-
-Run "pub help <command>" for more information about a command.
-See https://dart.dev/tools/pub/cmd for detailed documentation.
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub global
-[stdout]
-
-[stderr]
-Missing subcommand for "pub global".
-
-Usage: pub global [arguments...]
--h, --help    Print this usage information.
-
-Available subcommands:
-  activate     Make a package's executables globally available.
-  deactivate   Remove a previously activated package.
-  list         List globally activated packages.
-  run          Run an executable from a globally activated package.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
-[exitCode]
-64
diff --git a/test/goldens/version.txt b/test/goldens/version.txt
deleted file mode 100644
index a093c93..0000000
--- a/test/goldens/version.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-[command]
-> pub --version
-[stdout]
-Pub 0.1.2+3
-[stderr]
-
-[exitCode]
-0
-
-[command]
-> pub version
-[stdout]
-Pub 0.1.2+3
-[stderr]
-
-[exitCode]
-0
diff --git a/test/help_test.dart b/test/help_test.dart
new file mode 100644
index 0000000..e66f8a4
--- /dev/null
+++ b/test/help_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, 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:args/command_runner.dart';
+import 'package:pub/src/command_runner.dart' show PubCommandRunner;
+
+import 'golden_file.dart';
+
+/// Extract all commands and subcommands.
+///
+/// Result will be an iterable of lists, illustrated as follows:
+/// ```
+/// [
+///   [pub]
+///   [pub, get]
+///   ...
+/// ]
+/// ```
+Iterable<List<String>> _extractCommands(
+  List<String> parents,
+  Iterable<Command> cmds,
+) sync* {
+  if (parents.isNotEmpty) {
+    yield parents;
+  }
+  // Track that we don't add more than once, we don't want to test aliases
+  final names = <String>{};
+  yield* cmds
+      .where((sub) => !sub.hidden && names.add(sub.name))
+      .map((sub) => _extractCommands(
+            [...parents, sub.name],
+            sub.subcommands.values,
+          ))
+      .expand((cmds) => cmds);
+}
+
+/// Tests for `pub ... --help`.
+Future<void> main() async {
+  final cmds = _extractCommands([], PubCommandRunner().commands.values);
+  for (final c in cmds) {
+    testWithGolden('pub ${c.join(' ')} --help', (ctx) async {
+      await ctx.run([...c, '--help']);
+    });
+  }
+}
diff --git a/test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart b/test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
index f5ad35c..7fb22a3 100644
--- a/test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
+++ b/test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -11,6 +9,7 @@
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
+import '../golden_file.dart';
 import '../test_pub.dart';
 
 void main() {
@@ -18,15 +17,18 @@
     test(
         'fails gracefully if the package server responds with broken package listings',
         () async {
-      await servePackages((b) => b..serve('foo', '1.2.3'));
-      globalPackageServer.extraHandlers[RegExp('/api/packages/.*')] =
-          expectAsync1((request) {
-        expect(request.method, 'GET');
-        return Response(200,
-            body: jsonEncode({
-              'notTheRight': {'response': 'type'}
-            }));
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
+      server.expect(
+        'GET',
+        RegExp('/api/packages/.*'),
+        expectAsync1((request) {
+          return Response(200,
+              body: jsonEncode({
+                'notTheRight': {'response': 'type'}
+              }));
+        }),
+      );
       await d.appDir({'foo': '1.2.3'}).create();
 
       await pubCommand(command,
@@ -38,4 +40,80 @@
           exitCode: exit_codes.DATA);
     });
   });
+
+  testWithGolden('bad_json', (ctx) async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+    server.expect('GET', RegExp('/api/packages/.*'), (request) {
+      return Response(200,
+          body: jsonEncode({
+            'notTheRight': {'response': 'type'}
+          }));
+    });
+    await d.appDir({'foo': '1.2.3'}).create();
+
+    await ctx.run(['get']);
+  });
+
+  testWithGolden('403', (ctx) async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+    server.expect('GET', RegExp('/api/packages/.*'), (request) {
+      return Response(403,
+          body: jsonEncode({
+            'notTheRight': {'response': 'type'}
+          }));
+    });
+    await d.appDir({'foo': '1.2.3'}).create();
+
+    await ctx.run(['get']);
+  });
+
+  testWithGolden('401', (ctx) async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+    server.expect('GET', RegExp('/api/packages/.*'), (request) {
+      return Response(401,
+          body: jsonEncode({
+            'notTheRight': {'response': 'type'}
+          }));
+    });
+    await d.appDir({'foo': '1.2.3'}).create();
+
+    await ctx.run(['get']);
+  });
+
+  testWithGolden('403-with-message', (ctx) async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+    server.expect('GET', RegExp('/api/packages/.*'), (request) {
+      return Response(403,
+          headers: {
+            'www-authenticate': 'Bearer realm="pub", message="<message>"',
+          },
+          body: jsonEncode({
+            'notTheRight': {'response': 'type'}
+          }));
+    });
+    await d.appDir({'foo': '1.2.3'}).create();
+
+    await ctx.run(['get']);
+  });
+
+  testWithGolden('401-with-message', (ctx) async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
+    server.expect('GET', RegExp('/api/packages/.*'), (request) {
+      return Response(401,
+          headers: {
+            'www-authenticate': 'Bearer realm="pub", message="<message>"',
+          },
+          body: jsonEncode({
+            'notTheRight': {'response': 'type'}
+          }));
+    });
+    await d.appDir({'foo': '1.2.3'}).create();
+
+    await ctx.run(['get']);
+  });
 }
diff --git a/test/hosted/fail_gracefully_on_invalid_url_test.dart b/test/hosted/fail_gracefully_on_invalid_url_test.dart
index 9cbfbe8..94922c0 100644
--- a/test/hosted/fail_gracefully_on_invalid_url_test.dart
+++ b/test/hosted/fail_gracefully_on_invalid_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/hosted/fail_gracefully_on_missing_package_test.dart b/test/hosted/fail_gracefully_on_missing_package_test.dart
index 882d11c..7c6290d 100644
--- a/test/hosted/fail_gracefully_on_missing_package_test.dart
+++ b/test/hosted/fail_gracefully_on_missing_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -13,7 +11,7 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test('fails gracefully if the package does not exist', () async {
-      await serveNoPackages();
+      await servePackages();
 
       await d.appDir({'foo': '1.2.3'}).create();
 
diff --git a/test/hosted/fail_gracefully_on_url_resolve_test.dart b/test/hosted/fail_gracefully_on_url_resolve_test.dart
index 9e3abcd..7a91e7a 100644
--- a/test/hosted/fail_gracefully_on_url_resolve_test.dart
+++ b/test/hosted/fail_gracefully_on_url_resolve_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/hosted/fail_gracefully_with_hint_test.dart b/test/hosted/fail_gracefully_with_hint_test.dart
new file mode 100644
index 0000000..4f1a326
--- /dev/null
+++ b/test/hosted/fail_gracefully_with_hint_test.dart
@@ -0,0 +1,64 @@
+// 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:pub/src/exit_codes.dart' as exit_codes;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../golden_file.dart';
+import '../test_pub.dart';
+
+void main() {
+  testWithGolden('hint: try without --offline', (ctx) async {
+    // Run the server so that we know what URL to use in the system cache.
+    (await servePackages()).serveErrors();
+
+    await d.appDir({'foo': 'any'}).create();
+
+    await pubGet(
+      args: ['--offline'],
+      exitCode: exit_codes.UNAVAILABLE,
+      error: contains('Try again without --offline!'),
+    );
+  });
+
+  testWithGolden('supports two hints', (ctx) async {
+    // Run the server so that we know what URL to use in the system cache.
+    (await servePackages()).serveErrors();
+
+    await d.hostedCache([
+      d.dir('foo-1.2.3', [
+        d.pubspec({
+          'name': 'foo',
+          'version': '1.2.3',
+          'environment': {
+            'flutter': 'any', // generates hint -> flutter pub get
+          },
+        }),
+      ]),
+      d.dir('foo-1.2.4', [
+        d.pubspec({
+          'name': 'foo',
+          'version': '1.2.4',
+          'dependencies': {
+            'bar': 'any', // generates hint -> try without --offline
+          },
+        }),
+      ]),
+    ]).create();
+
+    await d.appDir({'foo': 'any'}).create();
+
+    await pubGet(
+      args: ['--offline'],
+      exitCode: exit_codes.UNAVAILABLE,
+      error: allOf(
+        contains('Try again without --offline!'),
+        contains('flutter pub get'), // hint that
+      ),
+    );
+
+    await ctx.run(['get', '--offline']);
+  });
+}
diff --git a/test/hosted/metadata_test.dart b/test/hosted/metadata_test.dart
index 6ba59f5..c705ac7 100644
--- a/test/hosted/metadata_test.dart
+++ b/test/hosted/metadata_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:test/test.dart';
@@ -14,9 +12,8 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test('sends metadata headers for a direct dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.appDir({'foo': '1.0.0'}).create();
 
@@ -35,9 +32,8 @@
     });
 
     test('sends metadata headers for a dev dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -61,9 +57,8 @@
     });
 
     test('sends metadata headers for a transitive dependency', () async {
-      await servePackages((builder) {
-        builder.serve('bar', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('bar', '1.0.0');
 
       await d.appDir({
         'foo': {'path': '../foo'}
@@ -84,9 +79,8 @@
     });
 
     test("doesn't send metadata headers to a foreign server", () async {
-      var server = await PackageServer.start((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      var server = await startPackageServer()
+        ..serve('foo', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -99,9 +93,7 @@
     });
 
     test("doesn't send metadata headers when CI=true", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      (await servePackages()).serve('foo', '1.0.0');
 
       await d.appDir({'foo': '1.0.0'}).create();
 
diff --git a/test/hosted/offline_test.dart b/test/hosted/offline_test.dart
index ef738c8..ca3dc80 100644
--- a/test/hosted/offline_test.dart
+++ b/test/hosted/offline_test.dart
@@ -2,22 +2,19 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-Future<void> populateCache(Map<String, List<String>> versions) async {
-  await servePackages((b) {
-    for (final entry in versions.entries) {
-      for (final version in entry.value) {
-        b.serve(entry.key, version);
-      }
+Future<void> populateCache(
+    Map<String, List<String>> versions, PackageServer server) async {
+  for (final entry in versions.entries) {
+    for (final version in entry.value) {
+      server.serve(entry.key, version);
     }
-  });
+  }
   for (final entry in versions.entries) {
     for (final version in entry.value) {
       await d.appDir({entry.key: version}).create();
@@ -29,37 +26,41 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test('upgrades a package using the cache', () async {
+      final server = await servePackages();
       await populateCache({
         'foo': ['1.2.2', '1.2.3'],
         'bar': ['1.2.3']
-      });
+      }, server);
 
       // Now serve only errors - to validate we are truly offline.
-      await serveErrors();
+      server.serveErrors();
 
       await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
-      String warning;
+      String? warning;
       if (command == RunCommand.upgrade) {
         warning = 'Warning: Upgrading when offline may not update you '
             'to the latest versions of your dependencies.';
       }
 
       await pubCommand(command, args: ['--offline'], warning: warning);
-
-      await d.appPackagesFile({'foo': '1.2.3', 'bar': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+        d.packageConfigEntry(name: 'bar', version: '1.2.3'),
+      ]).validate();
     });
 
     test('supports prerelease versions', () async {
+      final server = await servePackages();
       await populateCache({
         'foo': ['1.2.3-alpha.1']
-      });
+      }, server);
       // Now serve only errors - to validate we are truly offline.
-      await serveErrors();
+      server.serveErrors();
 
       await d.appDir({'foo': 'any'}).create();
 
-      String warning;
+      String? warning;
       if (command == RunCommand.upgrade) {
         warning = 'Warning: Upgrading when offline may not update you '
             'to the latest versions of your dependencies.';
@@ -67,12 +68,15 @@
 
       await pubCommand(command, args: ['--offline'], warning: warning);
 
-      await d.appPackagesFile({'foo': '1.2.3-alpha.1'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3-alpha.1'),
+      ]).validate();
     });
 
     test('fails gracefully if a dependency is not cached', () async {
       // Run the server so that we know what URL to use in the system cache.
-      await serveErrors();
+      final server = await servePackages();
+      server.serveErrors();
 
       await d.appDir({'foo': 'any'}).create();
 
@@ -82,16 +86,19 @@
           error: equalsIgnoringWhitespace("""
             Because myapp depends on foo any which doesn't exist (could not find
               package foo in cache), version solving failed.
+
+            Try again without --offline!
           """));
     });
 
     test('fails gracefully if no cached versions match', () async {
+      final server = await servePackages();
       await populateCache({
         'foo': ['1.2.2', '1.2.3']
-      });
+      }, server);
 
       // Run the server so that we know what URL to use in the system cache.
-      await serveErrors();
+      server.serveErrors();
 
       await d.appDir({'foo': '>2.0.0'}).create();
 
@@ -105,8 +112,10 @@
     test(
         'fails gracefully if a dependency is not cached and a lockfile '
         'exists', () async {
+      final server = await servePackages();
+
       // Run the server so that we know what URL to use in the system cache.
-      await serveErrors();
+      server.serveErrors();
 
       await d.appDir({'foo': 'any'}).create();
 
@@ -118,15 +127,19 @@
           error: equalsIgnoringWhitespace("""
             Because myapp depends on foo any which doesn't exist (could not find
               package foo in cache), version solving failed.
+
+            Try again without --offline!
           """));
     });
 
     test('downgrades to the version in the cache if necessary', () async {
+      final server = await servePackages();
+
       await populateCache({
         'foo': ['1.2.2', '1.2.3']
-      });
+      }, server);
       // Run the server so that we know what URL to use in the system cache.
-      await serveErrors();
+      server.serveErrors();
 
       await d.appDir({'foo': 'any'}).create();
 
@@ -134,15 +147,19 @@
 
       await pubCommand(command, args: ['--offline']);
 
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
     });
 
     test('skips invalid cached versions', () async {
+      final server = await servePackages();
+
       await populateCache({
         'foo': ['1.2.2', '1.2.3']
-      });
+      }, server);
       // Run the server so that we know what URL to use in the system cache.
-      await serveErrors();
+      server.serveErrors();
 
       await d.hostedCache([
         d.dir('foo-1.2.3', [d.file('pubspec.yaml', '{')]),
@@ -153,15 +170,19 @@
 
       await pubCommand(command, args: ['--offline']);
 
-      await d.appPackagesFile({'foo': '1.2.2'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+      ]).validate();
     });
 
     test('skips invalid locked versions', () async {
+      final server = await servePackages();
+
       await populateCache({
         'foo': ['1.2.2', '1.2.3']
-      });
+      }, server);
       // Run the server so that we know what URL to use in the system cache.
-      await serveErrors();
+      server.serveErrors();
 
       await d.hostedCache([
         d.dir('foo-1.2.3', [d.file('pubspec.yaml', '{')])
@@ -173,7 +194,9 @@
 
       await pubCommand(command, args: ['--offline']);
 
-      await d.appPackagesFile({'foo': '1.2.2'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+      ]).validate();
     });
   });
 }
diff --git a/test/hosted/remove_removed_dependency_test.dart b/test/hosted/remove_removed_dependency_test.dart
index 9d530d7..a0446c0 100644
--- a/test/hosted/remove_removed_dependency_test.dart
+++ b/test/hosted/remove_removed_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -12,22 +10,25 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test("removes a dependency that's removed from the pubspec", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('bar', '1.0.0');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('bar', '1.0.0');
 
       await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
       await pubCommand(command);
-
-      await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      ]).validate();
 
       await d.appDir({'foo': 'any'}).create();
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      ]).validate();
     });
   });
 }
diff --git a/test/hosted/remove_removed_transitive_dependency_test.dart b/test/hosted/remove_removed_transitive_dependency_test.dart
index 43ac01c..182ec3f 100644
--- a/test/hosted/remove_removed_transitive_dependency_test.dart
+++ b/test/hosted/remove_removed_transitive_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -14,31 +12,30 @@
     test(
         "removes a transitive dependency that's no longer depended "
         'on', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', deps: {'shared_dep': 'any'});
-        builder.serve('bar', '1.0.0',
-            deps: {'shared_dep': 'any', 'bar_dep': 'any'});
-        builder.serve('shared_dep', '1.0.0');
-        builder.serve('bar_dep', '1.0.0');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0', deps: {'shared_dep': 'any'})
+        ..serve('bar', '1.0.0', deps: {'shared_dep': 'any', 'bar_dep': 'any'})
+        ..serve('shared_dep', '1.0.0')
+        ..serve('bar_dep', '1.0.0');
 
       await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
       await pubCommand(command);
-
-      await d.appPackagesFile({
-        'foo': '1.0.0',
-        'bar': '1.0.0',
-        'shared_dep': '1.0.0',
-        'bar_dep': '1.0.0',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+        d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar_dep', version: '1.0.0'),
+      ]).validate();
 
       await d.appDir({'foo': 'any'}).create();
 
       await pubCommand(command);
 
-      await d
-          .appPackagesFile({'foo': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+      ]).validate();
     });
   });
 }
diff --git a/test/hosted/short_syntax_test.dart b/test/hosted/short_syntax_test.dart
new file mode 100644
index 0000000..5d4cf28
--- /dev/null
+++ b/test/hosted/short_syntax_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, 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 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  setUp(() async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3', pubspec: {
+      'environment': {'sdk': '^2.0.0'}
+    });
+  });
+  forBothPubGetAndUpgrade((command) {
+    Future<void> testWith(dynamic dependency) async {
+      await d.dir(appPath, [
+        d.libPubspec(
+          'app',
+          '1.0.0',
+          deps: {'foo': dependency},
+          sdk: '^2.15.0',
+        ),
+      ]).create();
+
+      await pubCommand(
+        command,
+        exitCode: 0,
+        environment: {'_PUB_TEST_SDK_VERSION': '2.15.0'},
+      );
+
+      final lockFile = loadYaml(
+        await File(p.join(d.sandbox, appPath, 'pubspec.lock')).readAsString(),
+      );
+
+      expect(lockFile['packages']['foo'], {
+        'dependency': 'direct main',
+        'source': 'hosted',
+        'description': {
+          'name': 'foo',
+          'url': globalServer.url,
+        },
+        'version': '1.2.3',
+      });
+    }
+
+    test('supports hosted: <url> syntax', () async {
+      return testWith({'hosted': globalServer.url});
+    });
+
+    test('supports hosted map without name', () {
+      return testWith({
+        'hosted': {'url': globalServer.url},
+      });
+    });
+
+    test('interprets hosted string as name for older versions', () async {
+      await d.dir(appPath, [
+        d.libPubspec(
+          'app',
+          '1.0.0',
+          deps: {
+            'foo': {'hosted': 'foo', 'version': '^1.2.3'}
+          },
+          sdk: '^2.0.0',
+        ),
+      ]).create();
+
+      await pubCommand(
+        command,
+        exitCode: 0,
+        environment: {'_PUB_TEST_SDK_VERSION': '2.15.0'},
+      );
+
+      final lockFile = loadYaml(
+        await File(p.join(d.sandbox, appPath, 'pubspec.lock')).readAsString(),
+      );
+
+      expect(
+          lockFile['packages']['foo']['description']['url'], globalServer.url);
+    });
+  });
+}
diff --git a/test/hosted/version_negotiation_test.dart b/test/hosted/version_negotiation_test.dart
index 3d12703..44dbe7a 100644
--- a/test/hosted/version_negotiation_test.dart
+++ b/test/hosted/version_negotiation_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
@@ -18,11 +16,11 @@
 
       await d.appDir({
         'foo': {
-          'hosted': {'name': 'foo', 'url': globalPackageServer.url}
+          'hosted': {'name': 'foo', 'url': globalServer.url}
         }
       }).create();
 
-      globalPackageServer.expect('GET', '/api/packages/foo', (request) {
+      globalServer.expect('GET', '/api/packages/foo', (request) {
         expect(
             request.headers['accept'], equals('application/vnd.pub.v2+json'));
         return shelf.Response(404);
@@ -37,13 +35,13 @@
 
       await d.appDir({
         'foo': {
-          'hosted': {'name': 'foo', 'url': globalPackageServer.url}
+          'hosted': {'name': 'foo', 'url': globalServer.url}
         }
       }).create();
 
       var pub = await startPub(args: [command.name]);
 
-      globalPackageServer.expect(
+      globalServer.expect(
           'GET', '/api/packages/foo', (request) => shelf.Response(406));
 
       await pub.shouldExit(1);
diff --git a/test/hosted/will_normalize_hosted_url_test.dart b/test/hosted/will_normalize_hosted_url_test.dart
index f2da266..6cc3888 100644
--- a/test/hosted/will_normalize_hosted_url_test.dart
+++ b/test/hosted/will_normalize_hosted_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:http/http.dart' as http;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:shelf/shelf.dart';
@@ -15,47 +13,50 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test('does not require slash on bare domain', () async {
-      await servePackages((b) => b..serve('foo', '1.2.3'));
-      // All the tests in this file assumes that [globalPackageServer.url]
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
+      // All the tests in this file assumes that [globalServer.url]
       // will be on the form:
       //   http://localhost:<port>
       // In particular, that it doesn't contain anything path segment.
-      expect(Uri.parse(globalPackageServer.url).path, isEmpty);
+      expect(Uri.parse(globalServer.url).path, isEmpty);
 
       await d.dir(appPath, [
         d.appPubspec({
           'foo': {
-            'hosted': {'name': 'foo', 'url': globalPackageServer.url},
+            'hosted': {'name': 'foo', 'url': globalServer.url},
           },
         }),
       ]).create();
 
       await pubCommand(
         command,
-        silent: contains('${globalPackageServer.url}/api/packages/foo'),
+        silent: contains('${globalServer.url}/api/packages/foo'),
       );
     });
 
     test('normalizes extra slash', () async {
-      await servePackages((b) => b..serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
 
       await d.dir(appPath, [
         d.appPubspec({
           'foo': {
-            'hosted': {'name': 'foo', 'url': globalPackageServer.url + '/'},
+            'hosted': {'name': 'foo', 'url': globalServer.url + '/'},
           },
         }),
       ]).create();
 
       await pubCommand(
         command,
-        silent: contains('${globalPackageServer.url}/api/packages/foo'),
+        silent: contains('${globalServer.url}/api/packages/foo'),
       );
     });
 
     test('cannot normalize double slash', () async {
-      await servePackages((b) => b..serve('foo', '1.2.3'));
-      globalPackageServer.expect(
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
+      globalServer.expect(
         'GET',
         '//api/packages/foo',
         (request) => Response.notFound(''),
@@ -64,15 +65,14 @@
       await d.dir(appPath, [
         d.appPubspec({
           'foo': {
-            'hosted': {'name': 'foo', 'url': globalPackageServer.url + '//'},
+            'hosted': {'name': 'foo', 'url': globalServer.url + '//'},
           },
         }),
       ]).create();
 
       await pubCommand(
         command,
-        error: contains(
-            'could not find package foo at ${globalPackageServer.url}//'),
+        error: contains('could not find package foo at ${globalServer.url}//'),
         exitCode: exit_codes.UNAVAILABLE,
       );
     });
@@ -82,27 +82,31 @@
     /// This is a bit of a hack, to easily test if hosted pub URLs with a path
     /// segment works and if the slashes are normalized.
     void _proxyMyFolderToRoot() {
-      globalPackageServer.extraHandlers[RegExp('/my-folder/.*')] = (r) async {
-        if (r.method != 'GET' && r.method != 'HEAD') {
-          return Response.forbidden(null);
-        }
-        final path = r.requestedUri.path.substring('/my-folder/'.length);
-        final res = await http.get(
-          Uri.parse(globalPackageServer.url + '/$path'),
-        );
-        return Response(res.statusCode, body: res.bodyBytes, headers: {
-          'Content-Type': res.headers['Content-Type'],
-        });
-      };
+      globalServer.handle(
+        RegExp('/my-folder/.*'),
+        (r) async {
+          if (r.method != 'GET' && r.method != 'HEAD') {
+            return Response.forbidden(null);
+          }
+          final path = r.requestedUri.path.substring('/my-folder/'.length);
+          final res = await http.get(
+            Uri.parse(globalServer.url + '/$path'),
+          );
+          return Response(res.statusCode, body: res.bodyBytes, headers: {
+            'Content-Type': res.headers['content-type']!,
+          });
+        },
+      );
     }
 
     test('will use normalized url with path', () async {
-      await servePackages((b) => b..serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
       _proxyMyFolderToRoot();
 
       // testing with a normalized URL
-      final testUrl = globalPackageServer.url + '/my-folder/';
-      final normalizedUrl = globalPackageServer.url + '/my-folder/';
+      final testUrl = globalServer.url + '/my-folder/';
+      final normalizedUrl = globalServer.url + '/my-folder/';
 
       await d.dir(appPath, [
         d.appPubspec({
@@ -120,12 +124,13 @@
     });
 
     test('will normalize url with path by adding slash', () async {
-      await servePackages((b) => b..serve('foo', '1.2.3'));
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
       _proxyMyFolderToRoot();
 
       // Testing with a URL that is missing the slash.
-      final testUrl = globalPackageServer.url + '/my-folder';
-      final normalizedUrl = globalPackageServer.url + '/my-folder/';
+      final testUrl = globalServer.url + '/my-folder';
+      final normalizedUrl = globalServer.url + '/my-folder/';
 
       await d.dir(appPath, [
         d.appPubspec({
diff --git a/test/ignore_test.dart b/test/ignore_test.dart
index 75238b5..a8ab8ac 100644
--- a/test/ignore_test.dart
+++ b/test/ignore_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:pub/src/ignore.dart';
@@ -41,10 +39,10 @@
           return [path.substring(0, nextSlash == -1 ? path.length : nextSlash)];
         }
 
-        Ignore ignoreForDir(String dir) => c.patterns[dir] == null
+        Ignore? ignoreForDir(String dir) => c.patterns[dir] == null
             ? null
             : Ignore(
-                c.patterns[dir],
+                c.patterns[dir]!,
                 onInvalidPattern: (_, __) => hasWarning = true,
                 ignoreCase: ignoreCase,
               );
@@ -87,17 +85,18 @@
 
     for (final c in testData) {
       c.paths.forEach((path, expected) {
-        if (c.ignoreCase == null) {
+        var ignoreCase = c.ignoreCase;
+        if (ignoreCase == null) {
           _testIgnorePath(c, path, expected, false);
           _testIgnorePath(c, path, expected, true);
         } else {
-          _testIgnorePath(c, path, expected, c.ignoreCase);
+          _testIgnorePath(c, path, expected, ignoreCase);
         }
       });
     }
   });
 
-  ProcessResult runGit(List<String> args, {String workingDirectory}) {
+  ProcessResult runGit(List<String> args, {String? workingDirectory}) {
     final executable = Platform.isWindows ? 'cmd' : 'git';
     args = Platform.isWindows ? ['/c', 'git', ...args] : args;
     return Process.runSync(executable, args,
@@ -105,24 +104,24 @@
   }
 
   group('git', () {
-    Directory tmp;
+    Directory? tmp;
 
     setUpAll(() async {
       tmp = await Directory.systemTemp.createTemp('package-ignore-test-');
 
-      final ret = runGit(['init'], workingDirectory: tmp.path);
+      final ret = runGit(['init'], workingDirectory: tmp!.path);
       expect(ret.exitCode, equals(0),
           reason:
               'Running "git init" failed. StdErr: ${ret.stderr} StdOut: ${ret.stdout}');
     });
 
     tearDownAll(() async {
-      await tmp.delete(recursive: true);
+      await tmp!.delete(recursive: true);
       tmp = null;
     });
 
     tearDown(() async {
-      runGit(['clean', '-f', '-d', '-x'], workingDirectory: tmp.path);
+      runGit(['clean', '-f', '-d', '-x'], workingDirectory: tmp!.path);
     });
 
     void _testIgnorePath(
@@ -137,7 +136,7 @@
         expect(
           runGit(
             ['config', '--local', 'core.ignoreCase', ignoreCase.toString()],
-            workingDirectory: tmp.path,
+            workingDirectory: tmp!.path,
           ).exitCode,
           anyOf(0, 1),
           reason: 'Running "git config --local core.ignoreCase ..." failed',
@@ -145,17 +144,17 @@
 
         for (final directory in c.patterns.keys) {
           final resolvedDirectory =
-              directory == '' ? tmp.uri : tmp.uri.resolve(directory + '/');
+              directory == '' ? tmp!.uri : tmp!.uri.resolve(directory + '/');
           Directory.fromUri(resolvedDirectory).createSync(recursive: true);
           final gitIgnore =
               File.fromUri(resolvedDirectory.resolve('.gitignore'));
           gitIgnore.writeAsStringSync(
-            c.patterns[directory].join('\n') + '\n',
+            c.patterns[directory]!.join('\n') + '\n',
           );
         }
         final process = runGit(
-            ['-C', tmp.path, 'check-ignore', '--no-index', path],
-            workingDirectory: tmp.path);
+            ['-C', tmp!.path, 'check-ignore', '--no-index', path],
+            workingDirectory: tmp!.path);
         expect(process.exitCode, anyOf(0, 1),
             reason: 'Running "git check-ignore" failed');
         final ignored = process.exitCode == 0;
@@ -172,11 +171,12 @@
 
     for (final c in testData) {
       c.paths.forEach((path, expected) {
-        if (c.ignoreCase == null) {
+        var ignoreCase = c.ignoreCase;
+        if (ignoreCase == null) {
           _testIgnorePath(c, path, expected, false);
           _testIgnorePath(c, path, expected, true);
         } else {
-          _testIgnorePath(c, path, expected, c.ignoreCase);
+          _testIgnorePath(c, path, expected, ignoreCase);
         }
       });
     }
@@ -200,7 +200,7 @@
   final bool skipOnWindows;
 
   /// Test with `core.ignoreCase` set to `true`, `false` or both (if `null`).
-  final bool ignoreCase;
+  final bool? ignoreCase;
 
   TestData(
     this.name,
@@ -977,6 +977,13 @@
     'folder/a.txt': true,
   }),
 
+  TestData('folder/* does not ignore `folder` itself', {
+    '.': ['folder/*', '!folder/a.txt'],
+  }, {
+    'folder/a.txt': false,
+    'folder/b.txt': true,
+  }),
+
   // Case sensitivity
   TestData(
     'simple',
diff --git a/test/io_test.dart b/test/io_test.dart
index b54bc91..2c529cd 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -463,14 +461,14 @@
 }
 
 void testExistencePredicate(String name, bool Function(String path) predicate,
-    {bool forFile,
-    bool forFileSymlink,
-    bool forMultiLevelFileSymlink,
-    bool forDirectory,
-    bool forDirectorySymlink,
-    bool forMultiLevelDirectorySymlink,
-    bool forBrokenSymlink,
-    bool forMultiLevelBrokenSymlink}) {
+    {required bool forFile,
+    required bool forFileSymlink,
+    required bool forMultiLevelFileSymlink,
+    required bool forDirectory,
+    required bool forDirectorySymlink,
+    required bool forMultiLevelDirectorySymlink,
+    required bool forBrokenSymlink,
+    required bool forMultiLevelBrokenSymlink}) {
   group(name, () {
     test('returns $forFile for a file', () {
       expect(withTempDir((temp) {
diff --git a/test/levenshtein_test.dart b/test/levenshtein_test.dart
index 6d113c7..67361af 100644
--- a/test/levenshtein_test.dart
+++ b/test/levenshtein_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/levenshtein.dart';
 import 'package:test/test.dart';
 
diff --git a/test/lish/archives_and_uploads_a_package_test.dart b/test/lish/archives_and_uploads_a_package_test.dart
index 2efeb4a..88e4862 100644
--- a/test/lish/archives_and_uploads_a_package_test.dart
+++ b/test/lish/archives_and_uploads_a_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:path/path.dart' as p;
@@ -21,14 +19,44 @@
 
   test('archives and uploads a package', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
+      return shelf.Response.ok(jsonEncode({
+        'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
+      }));
+    });
+
+    expect(pub.stdout, emits(startsWith('Uploading...')));
+    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    await pub.shouldExit(exit_codes.SUCCESS);
+  });
+
+  test('publishes to hosted-url with path', () async {
+    await servePackages();
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': globalServer.url + '/sub/folder', 'env': 'TOKEN'},
+      ]
+    }).create();
+    var pub = await startPublish(
+      globalServer,
+      path: '/sub/folder',
+      authMethod: 'token',
+      environment: {'TOKEN': 'access token'},
+    );
+
+    await confirmPublish(pub);
+    handleUploadForm(globalServer, path: '/sub/folder');
+    handleUpload(globalServer);
+
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
@@ -55,14 +83,14 @@
     await d.dir(p.join(appPath, 'empty')).create();
 
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
diff --git a/test/lish/cloud_storage_upload_doesnt_redirect_test.dart b/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
index f2e542f..0f792d5 100644
--- a/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
+++ b/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -16,13 +14,13 @@
 
   test("cloud storage upload doesn't redirect", () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
+    handleUploadForm(globalServer);
 
-    globalPackageServer.expect('POST', '/upload', (request) async {
+    globalServer.expect('POST', '/upload', (request) async {
       await request.read().drain();
       return shelf.Response(200);
     });
diff --git a/test/lish/cloud_storage_upload_provides_an_error_test.dart b/test/lish/cloud_storage_upload_provides_an_error_test.dart
index a67d85b..5dec03d 100644
--- a/test/lish/cloud_storage_upload_provides_an_error_test.dart
+++ b/test/lish/cloud_storage_upload_provides_an_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -16,13 +14,13 @@
 
   test('cloud storage upload provides an error', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
+    handleUploadForm(globalServer);
 
-    globalPackageServer.expect('POST', '/upload', (request) {
+    globalServer.expect('POST', '/upload', (request) {
       return request.read().drain().then((_) {
         return shelf.Response.notFound(
             '<Error><Message>Your request sucked.</Message></Error>',
diff --git a/test/lish/does_not_include_dot_file.dart b/test/lish/does_not_include_dot_file.dart
index 6dfa622..cec8ab0 100644
--- a/test/lish/does_not_include_dot_file.dart
+++ b/test/lish/does_not_include_dot_file.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -32,14 +30,14 @@
 
   test('Check if package doesn\'t include dot-files', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
diff --git a/test/lish/does_not_publish_if_private_test.dart b/test/lish/does_not_publish_if_private_test.dart
index 9ab8712..52321c2 100644
--- a/test/lish/does_not_publish_if_private_test.dart
+++ b/test/lish/does_not_publish_if_private_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
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 c33b1a3..689609b 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
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/lish/dot_folder_name_test.dart b/test/lish/dot_folder_name_test.dart
new file mode 100644
index 0000000..9f64839
--- /dev/null
+++ b/test/lish/dot_folder_name_test.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, 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:pub/src/exit_codes.dart' as exit_codes;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  test('Can publish files in a .folder', () async {
+    await d.git(appPath).create();
+    await d.validPackage.create();
+    await d.dir(appPath, [
+      d.dir('.vscode', [d.file('a')]),
+      d.file('.pubignore', '!.vscode/')
+    ]).create();
+
+    await runPub(
+      args: ['lish', '--dry-run'],
+      output: contains('''
+|-- .vscode
+|   '-- a'''),
+      exitCode: exit_codes.SUCCESS,
+    );
+  });
+}
diff --git a/test/lish/dry_run_warns_about_server_checks.dart b/test/lish/dry_run_warns_about_server_checks.dart
index 8e12f9b..ad7abe0 100644
--- a/test/lish/dry_run_warns_about_server_checks.dart
+++ b/test/lish/dry_run_warns_about_server_checks.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
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 def3f7a..9d2233c 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,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -14,17 +12,10 @@
   setUp(d.validPackage.create);
 
   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.
--C, --directory=<dir>    Run this in the directory<dir>.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-lish for detailed documentation.
-''', exitCode: exit_codes.USAGE);
+    await runPub(
+      args: ['lish', '--force', '--dry-run'],
+      error: contains('Cannot use both --force and --dry-run.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/lish/force_does_not_publish_if_private_test.dart b/test/lish/force_does_not_publish_if_private_test.dart
index 9ca4bf6..0d4ad59 100644
--- a/test/lish/force_does_not_publish_if_private_test.dart
+++ b/test/lish/force_does_not_publish_if_private_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/lish/force_does_not_publish_if_there_are_errors_test.dart b/test/lish/force_does_not_publish_if_there_are_errors_test.dart
index 26978ad..481f627 100644
--- a/test/lish/force_does_not_publish_if_there_are_errors_test.dart
+++ b/test/lish/force_does_not_publish_if_there_are_errors_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -23,7 +21,7 @@
     ]).create();
 
     await servePackages();
-    var pub = await startPublish(globalPackageServer, args: ['--force']);
+    var pub = await startPublish(globalServer, args: ['--force']);
 
     await pub.shouldExit(exit_codes.DATA);
     expect(
diff --git a/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart b/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart
index 59c8233..c5d8957 100644
--- a/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart
+++ b/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -19,13 +17,13 @@
 
   test('--force publishes if there are no warnings or errors', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer, args: ['--force']);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer, args: ['--force']);
 
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
diff --git a/test/lish/force_publishes_if_there_are_warnings_test.dart b/test/lish/force_publishes_if_there_are_warnings_test.dart
index c217b29..9fb3d53 100644
--- a/test/lish/force_publishes_if_there_are_warnings_test.dart
+++ b/test/lish/force_publishes_if_there_are_warnings_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -24,13 +22,13 @@
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer, args: ['--force']);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer, args: ['--force']);
 
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
diff --git a/test/lish/many_files_test.dart b/test/lish/many_files_test.dart
index 02db6b2..36f4ed7 100644
--- a/test/lish/many_files_test.dart
+++ b/test/lish/many_files_test.dart
@@ -2,11 +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.
 
-// @dart=2.10
-
 import 'dart:convert';
 import 'dart:io';
-import 'dart:math' as math;
 
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -38,7 +35,7 @@
     int argMax;
     if (Platform.isWindows) {
       // On Windows, the maximum argument list length is 8^5 bytes.
-      argMax = math.pow(8, 5);
+      argMax = 32768; // 8^5
     } else {
       // On POSIX, the maximum argument list length can be retrieved
       // automatically.
@@ -76,14 +73,14 @@
     }
 
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
diff --git a/test/lish/package_creation_provides_a_malformed_error_test.dart b/test/lish/package_creation_provides_a_malformed_error_test.dart
index d78e495..7ab0bcd 100644
--- a/test/lish/package_creation_provides_a_malformed_error_test.dart
+++ b/test/lish/package_creation_provides_a_malformed_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -18,15 +16,15 @@
 
   test('package creation provides a malformed error', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
     var body = {'error': 'Your package was too boring.'};
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.notFound(jsonEncode(body));
     });
 
diff --git a/test/lish/package_creation_provides_a_malformed_success_test.dart b/test/lish/package_creation_provides_a_malformed_success_test.dart
index 67c10ef..089d09e 100644
--- a/test/lish/package_creation_provides_a_malformed_success_test.dart
+++ b/test/lish/package_creation_provides_a_malformed_success_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -18,15 +16,15 @@
 
   test('package creation provides a malformed success', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
     var body = {'success': 'Your package was awesome.'};
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode(body));
     });
 
diff --git a/test/lish/package_creation_provides_an_error_test.dart b/test/lish/package_creation_provides_an_error_test.dart
index 49166c1..f5ff128 100644
--- a/test/lish/package_creation_provides_an_error_test.dart
+++ b/test/lish/package_creation_provides_an_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -18,14 +16,14 @@
 
   test('package creation provides an error', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.notFound(jsonEncode({
         'error': {'message': 'Your package was too boring.'}
       }));
diff --git a/test/lish/package_creation_provides_invalid_json_test.dart b/test/lish/package_creation_provides_invalid_json_test.dart
index 8cdfd5b..2cd6212 100644
--- a/test/lish/package_creation_provides_invalid_json_test.dart
+++ b/test/lish/package_creation_provides_invalid_json_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -16,14 +14,14 @@
 
   test('package creation provides invalid JSON', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok('{not json');
     });
 
diff --git a/test/lish/package_validation_has_a_warning_and_continues_test.dart b/test/lish/package_validation_has_a_warning_and_continues_test.dart
index ec55be9..c39dbc4 100644
--- a/test/lish/package_validation_has_a_warning_and_continues_test.dart
+++ b/test/lish/package_validation_has_a_warning_and_continues_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:pub/src/exit_codes.dart' as exit_codes;
@@ -24,13 +22,13 @@
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
     pub.stdin.writeln('y');
-    handleUploadForm(globalPackageServer);
-    handleUpload(globalPackageServer);
+    handleUploadForm(globalServer);
+    handleUpload(globalServer);
 
-    globalPackageServer.expect('GET', '/create', (request) {
+    globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode({
         'success': {'message': 'Package test_pkg 1.0.0 uploaded!'}
       }));
diff --git a/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart b/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
index fbdacc4..0d5a563 100644
--- a/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
+++ b/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -20,7 +18,7 @@
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
     await servePackages();
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
 
     pub.stdin.writeln('n');
     await pub.shouldExit(exit_codes.DATA);
diff --git a/test/lish/package_validation_has_an_error_test.dart b/test/lish/package_validation_has_an_error_test.dart
index 9fab3a5..14c83c0 100644
--- a/test/lish/package_validation_has_an_error_test.dart
+++ b/test/lish/package_validation_has_an_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -23,7 +21,7 @@
     ]).create();
 
     await servePackages();
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
 
     await pub.shouldExit(exit_codes.DATA);
     expect(
diff --git a/test/lish/preview_errors_if_private_test.dart b/test/lish/preview_errors_if_private_test.dart
index c816c6e..fb7160a 100644
--- a/test/lish/preview_errors_if_private_test.dart
+++ b/test/lish/preview_errors_if_private_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
diff --git a/test/lish/preview_package_validation_has_a_warning_test.dart b/test/lish/preview_package_validation_has_a_warning_test.dart
index c3aa01d..69f59af 100644
--- a/test/lish/preview_package_validation_has_a_warning_test.dart
+++ b/test/lish/preview_package_validation_has_a_warning_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
@@ -21,7 +19,7 @@
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
     await servePackages();
-    var pub = await startPublish(globalPackageServer, args: ['--dry-run']);
+    var pub = await startPublish(globalServer, args: ['--dry-run']);
 
     await pub.shouldExit(exit_codes.DATA);
     expect(
diff --git a/test/lish/preview_package_validation_has_no_warnings_test.dart b/test/lish/preview_package_validation_has_no_warnings_test.dart
index 586708c..179c648 100644
--- a/test/lish/preview_package_validation_has_no_warnings_test.dart
+++ b/test/lish/preview_package_validation_has_no_warnings_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
@@ -19,8 +17,8 @@
         packageMap('test_pkg', '1.0.0', null, null, {'sdk': '>=1.8.0 <2.0.0'});
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
-    await servePackages((_) {});
-    var pub = await startPublish(globalPackageServer, args: ['--dry-run']);
+    await servePackages();
+    var pub = await startPublish(globalServer, args: ['--dry-run']);
 
     await pub.shouldExit(exit_codes.SUCCESS);
     expect(pub.stderr, emitsThrough('Package has 0 warnings.'));
diff --git a/test/lish/server_arg_does_not_override_private_test.dart b/test/lish/server_arg_does_not_override_private_test.dart
index baba7d6..58a1af1 100644
--- a/test/lish/server_arg_does_not_override_private_test.dart
+++ b/test/lish/server_arg_does_not_override_private_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
diff --git a/test/lish/server_arg_overrides_publish_to_url_test.dart b/test/lish/server_arg_overrides_publish_to_url_test.dart
index 508c0b3..1597146 100644
--- a/test/lish/server_arg_overrides_publish_to_url_test.dart
+++ b/test/lish/server_arg_overrides_publish_to_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
@@ -15,16 +13,15 @@
   test('an explicit --server argument overrides a "publish_to" url', () async {
     // Create a real server that can reject requests because validators will
     // try to ping it, and will use multiple retries when doing so.
-    final packageServer = await DescriptorServer.start();
-    final fakePackageServer = 'http://localhost:${packageServer.port}';
+    final packageServer = await startPackageServer();
 
     var pkg = packageMap('test_pkg', '1.0.0');
     pkg['publish_to'] = 'http://pubspec.com';
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
     await runPub(
-        args: ['lish', '--dry-run', '--server', fakePackageServer],
-        output: contains(fakePackageServer),
+        args: ['lish', '--dry-run', '--server', packageServer.url],
+        output: contains(packageServer.url),
         exitCode: exit_codes.DATA);
 
     await packageServer.close();
diff --git a/test/lish/upload_form_fields_has_a_non_string_value_test.dart b/test/lish/upload_form_fields_has_a_non_string_value_test.dart
index 3ecb2fb..78a54fa 100644
--- a/test/lish/upload_form_fields_has_a_non_string_value_test.dart
+++ b/test/lish/upload_form_fields_has_a_non_string_value_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:test/test.dart';
@@ -16,9 +14,9 @@
   setUp(d.validPackage.create);
 
   test('upload form fields has a non-string value', () async {
-    await servePackages((_) {});
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await servePackages();
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
@@ -26,7 +24,7 @@
       'url': 'http://example.com/upload',
       'fields': {'field': 12}
     };
-    handleUploadForm(globalPackageServer, body);
+    handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
     await pub.shouldExit(1);
diff --git a/test/lish/upload_form_fields_is_not_a_map_test.dart b/test/lish/upload_form_fields_is_not_a_map_test.dart
index fed0f38..d18c1ee 100644
--- a/test/lish/upload_form_fields_is_not_a_map_test.dart
+++ b/test/lish/upload_form_fields_is_not_a_map_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:test/test.dart';
@@ -17,13 +15,13 @@
 
   test('upload form fields is not a map', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
     var body = {'url': 'http://example.com/upload', 'fields': 12};
-    handleUploadForm(globalPackageServer, body);
+    handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
     await pub.shouldExit(1);
diff --git a/test/lish/upload_form_is_missing_fields_test.dart b/test/lish/upload_form_is_missing_fields_test.dart
index a49b0d6..b0032f8 100644
--- a/test/lish/upload_form_is_missing_fields_test.dart
+++ b/test/lish/upload_form_is_missing_fields_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:test/test.dart';
@@ -17,13 +15,13 @@
 
   test('upload form is missing fields', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
     var body = {'url': 'http://example.com/upload'};
-    handleUploadForm(globalPackageServer, body);
+    handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
     await pub.shouldExit(1);
diff --git a/test/lish/upload_form_is_missing_url_test.dart b/test/lish/upload_form_is_missing_url_test.dart
index 50f5fa5..eae43ea 100644
--- a/test/lish/upload_form_is_missing_url_test.dart
+++ b/test/lish/upload_form_is_missing_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:test/test.dart';
@@ -17,8 +15,8 @@
 
   test('upload form is missing url', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
@@ -26,7 +24,7 @@
       'fields': {'field1': 'value1', 'field2': 'value2'}
     };
 
-    handleUploadForm(globalPackageServer, body);
+    handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
     await pub.shouldExit(1);
diff --git a/test/lish/upload_form_provides_an_error_test.dart b/test/lish/upload_form_provides_an_error_test.dart
index c5fd41e..35932b8 100644
--- a/test/lish/upload_form_provides_an_error_test.dart
+++ b/test/lish/upload_form_provides_an_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -16,15 +14,13 @@
   setUp(d.validPackage.create);
 
   test('upload form provides an error', () async {
-    await servePackages((_) {});
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await servePackages();
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    globalPackageServer.extraHandlers['/api/packages/versions/new'] =
-        expectAsync1((request) {
-      expect(request.method, 'GET');
+    globalServer.expect('GET', '/api/packages/versions/new', (request) async {
       return shelf.Response.notFound(jsonEncode({
         'error': {'message': 'your request sucked'}
       }));
diff --git a/test/lish/upload_form_provides_invalid_json_test.dart b/test/lish/upload_form_provides_invalid_json_test.dart
index a4fa5d7..a046755 100644
--- a/test/lish/upload_form_provides_invalid_json_test.dart
+++ b/test/lish/upload_form_provides_invalid_json_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -15,12 +13,12 @@
 
   test('upload form provides invalid JSON', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    globalPackageServer.expect('GET', '/api/packages/versions/new',
+    globalServer.expect('GET', '/api/packages/versions/new',
         (request) => shelf.Response.ok('{not json'));
 
     expect(
diff --git a/test/lish/upload_form_url_is_not_a_string_test.dart b/test/lish/upload_form_url_is_not_a_string_test.dart
index 99909a4..f69fd1f 100644
--- a/test/lish/upload_form_url_is_not_a_string_test.dart
+++ b/test/lish/upload_form_url_is_not_a_string_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:test/test.dart';
@@ -17,8 +15,8 @@
 
   test('upload form url is not a string', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
@@ -27,7 +25,7 @@
       'fields': {'field1': 'value1', 'field2': 'value2'}
     };
 
-    handleUploadForm(globalPackageServer, body);
+    handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
     await pub.shouldExit(1);
diff --git a/test/lish/uses_publish_to_url_test.dart b/test/lish/uses_publish_to_url_test.dart
index e7f9cf3..47496f7 100644
--- a/test/lish/uses_publish_to_url_test.dart
+++ b/test/lish/uses_publish_to_url_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
diff --git a/test/lish/utils.dart b/test/lish/utils.dart
index 5f18444..f134e4a 100644
--- a/test/lish/utils.dart
+++ b/test/lish/utils.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -11,8 +9,8 @@
 
 import '../test_pub.dart';
 
-void handleUploadForm(PackageServer server, [Map body]) {
-  server.expect('GET', '/api/packages/versions/new', (request) {
+void handleUploadForm(PackageServer server, {Map? body, String path = ''}) {
+  server.expect('GET', '$path/api/packages/versions/new', (request) {
     expect(
         request.headers, containsPair('authorization', 'Bearer access token'));
 
diff --git a/test/list_package_dirs/ignores_updated_pubspec_test.dart b/test/list_package_dirs/ignores_updated_pubspec_test.dart
index edec570..2524681 100644
--- a/test/list_package_dirs/ignores_updated_pubspec_test.dart
+++ b/test/list_package_dirs/ignores_updated_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 
diff --git a/test/list_package_dirs/includes_dev_dependencies_test.dart b/test/list_package_dirs/includes_dev_dependencies_test.dart
index 10f4f56..61cc56b 100644
--- a/test/list_package_dirs/includes_dev_dependencies_test.dart
+++ b/test/list_package_dirs/includes_dev_dependencies_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 
diff --git a/test/list_package_dirs/lists_dependency_directories_test.dart b/test/list_package_dirs/lists_dependency_directories_test.dart
index 5d5b982..2cd9664 100644
--- a/test/list_package_dirs/lists_dependency_directories_test.dart
+++ b/test/list_package_dirs/lists_dependency_directories_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 
@@ -14,7 +12,8 @@
 
 void main() {
   test('prints the local paths to all packages in the lockfile', () async {
-    await servePackages((builder) => builder.serve('bar', '1.0.0'));
+    final server = await servePackages()
+      ..serve('bar', '1.0.0');
 
     await d
         .dir('foo', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
@@ -39,7 +38,7 @@
       'packages': {
         'foo': path.join(d.sandbox, 'foo', 'lib'),
         'bar': path.join(d.sandbox, cachePath, 'hosted',
-            'localhost%58${globalServer.port}', 'bar-1.0.0', 'lib'),
+            'localhost%58${server.port}', 'bar-1.0.0', 'lib'),
         'myapp': canonicalize(path.join(d.sandbox, appPath, 'lib'))
       },
       'input_files': [
diff --git a/test/list_package_dirs/lockfile_error_test.dart b/test/list_package_dirs/lockfile_error_test.dart
index 16860f4..f4440ec 100644
--- a/test/list_package_dirs/lockfile_error_test.dart
+++ b/test/list_package_dirs/lockfile_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
diff --git a/test/list_package_dirs/missing_pubspec_test.dart b/test/list_package_dirs/missing_pubspec_test.dart
index d3c61de..83a58c2 100644
--- a/test/list_package_dirs/missing_pubspec_test.dart
+++ b/test/list_package_dirs/missing_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
diff --git a/test/list_package_dirs/no_lockfile_test.dart b/test/list_package_dirs/no_lockfile_test.dart
index 9e1078b..5939099 100644
--- a/test/list_package_dirs/no_lockfile_test.dart
+++ b/test/list_package_dirs/no_lockfile_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
diff --git a/test/list_package_dirs/pubspec_error_test.dart b/test/list_package_dirs/pubspec_error_test.dart
index 321e555..c282c5c 100644
--- a/test/list_package_dirs/pubspec_error_test.dart
+++ b/test/list_package_dirs/pubspec_error_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
diff --git a/test/lock_file_test.dart b/test/lock_file_test.dart
index 18edc94..4360c7c 100644
--- a/test/lock_file_test.dart
+++ b/test/lock_file_test.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:pub/src/language_version.dart';
 import 'package:pub/src/lock_file.dart';
 import 'package:pub/src/package_name.dart';
 import 'package:pub/src/source.dart';
@@ -22,14 +21,15 @@
       throw UnsupportedError('Cannot download fake packages.');
 
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description,
+      {String? containingPath, LanguageVersion? languageVersion}) {
     if (!description.endsWith(' desc')) throw FormatException('Bad');
     return PackageRef(name, this, description);
   }
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (!description.endsWith(' desc')) throw FormatException('Bad');
     return PackageId(name, this, version, description);
   }
@@ -79,13 +79,13 @@
 
         expect(lockFile.packages.length, equals(2));
 
-        var bar = lockFile.packages['bar'];
+        var bar = lockFile.packages['bar']!;
         expect(bar.name, equals('bar'));
         expect(bar.version, equals(Version(1, 2, 3)));
         expect(bar.source, equals(fakeSource));
         expect(bar.description, equals('bar desc'));
 
-        var foo = lockFile.packages['foo'];
+        var foo = lockFile.packages['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.version, equals(Version(2, 3, 4)));
         expect(foo.source, equals(fakeSource));
@@ -100,7 +100,7 @@
     version: 1.2.3
     description: foo desc
 ''', sources);
-        var foo = lockFile.packages['foo'];
+        var foo = lockFile.packages['foo']!;
         expect(foo.source, equals(sources['bad']));
       });
 
@@ -261,7 +261,7 @@
       });
 
       expect(
-          loadYaml(lockfile.serialize(null)),
+          loadYaml(lockfile.serialize('')),
           equals({
             'sdks': {'dart': 'any'},
             'packages': {
diff --git a/test/must_pub_get_test.dart b/test/must_pub_get_test.dart
index edea182..71d9c16 100644
--- a/test/must_pub_get_test.dart
+++ b/test/must_pub_get_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -16,12 +14,14 @@
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
 
+late PackageServer server;
+
 void main() {
   setUp(() async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-    });
+    server = await servePackages();
+
+    server.serve('foo', '1.0.0');
+    server.serve('foo', '2.0.0');
 
     await d.dir(appPath, [
       d.appPubspec(),
@@ -213,7 +213,7 @@
           d.appPubspec({'foo': '1.0.0'})
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         deleteEntry(p.join(d.sandbox, cachePath));
 
@@ -235,7 +235,7 @@
           })
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         await createPackagesFile(appPath);
 
@@ -257,7 +257,7 @@
           })
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         await d.dir(appPath, [
           d.file('.packages', '''
@@ -284,7 +284,7 @@
           })
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         await createPackagesFile(appPath, dependenciesInSandBox: ['foo']);
 
@@ -334,10 +334,8 @@
       setUp(() async {
         // Avoid using a path dependency because it triggers the full validation
         // logic. We want to be sure SDK-validation works without that logic.
-        globalPackageServer.add((builder) {
-          builder.serve('foo', '3.0.0', pubspec: {
-            'environment': {'sdk': '>=1.0.0 <2.0.0'}
-          });
+        server.serve('foo', '3.0.0', pubspec: {
+          'environment': {'sdk': '>=1.0.0 <2.0.0'}
         });
 
         await d.dir(appPath, [
@@ -362,10 +360,8 @@
         'current Flutter SDK', () async {
       // Avoid using a path dependency because it triggers the full validation
       // logic. We want to be sure SDK-validation works without that logic.
-      globalPackageServer.add((builder) {
-        builder.serve('foo', '3.0.0', pubspec: {
-          'environment': {'flutter': '>=1.0.0 <2.0.0'}
-        });
+      server.serve('foo', '3.0.0', pubspec: {
+        'environment': {'flutter': '>=1.0.0 <2.0.0'}
       });
 
       await d.dir('flutter', [d.file('version', '1.2.3')]).create();
@@ -456,7 +452,7 @@
   group("doesn't require the user to run pub get first if", () {
     group(
         'the pubspec is older than the lockfile which is older than the '
-        'packages file, even if the contents are wrong', () {
+        'package-config, even if the contents are wrong', () {
       setUp(() async {
         await d.dir(appPath, [
           d.appPubspec({'foo': '1.0.0'})
@@ -465,7 +461,6 @@
         await _touch('pubspec.yaml');
 
         await _touch('pubspec.lock');
-        await _touch('.packages');
         await _touch('.dart_tool/package_config.json');
       });
 
@@ -524,10 +519,8 @@
 
     group("an overridden dependency's SDK constraint is unmatched", () {
       setUp(() async {
-        globalPackageServer.add((builder) {
-          builder.serve('bar', '1.0.0', pubspec: {
-            'environment': {'sdk': '0.0.0-fake'}
-          });
+        server.serve('bar', '1.0.0', pubspec: {
+          'environment': {'sdk': '0.0.0-fake'}
         });
 
         await d.dir(appPath, [
@@ -549,10 +542,8 @@
         () async {
       // Avoid using a path dependency because it triggers the full validation
       // logic. We want to be sure SDK-validation works without that logic.
-      globalPackageServer.add((builder) {
-        builder.serve('foo', '3.0.0', pubspec: {
-          'environment': {'flutter': '>=1.0.0 <2.0.0'}
-        });
+      server.serve('foo', '3.0.0', pubspec: {
+        'environment': {'flutter': '>=1.0.0 <2.0.0'}
       });
 
       await d.dir('flutter', [d.file('version', '1.2.3')]).create();
@@ -608,14 +599,11 @@
           File(p.join(d.sandbox, 'myapp/pubspec.yaml')).lastModifiedSync();
       var lockFileModified =
           File(p.join(d.sandbox, 'myapp/pubspec.lock')).lastModifiedSync();
-      var packagesModified =
-          File(p.join(d.sandbox, 'myapp/.packages')).lastModifiedSync();
       var packageConfigModified =
           File(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json'))
               .lastModifiedSync();
 
       expect(!pubspecModified.isAfter(lockFileModified), isTrue);
-      expect(!lockFileModified.isAfter(packagesModified), isTrue);
       expect(!lockFileModified.isAfter(packageConfigModified), isTrue);
     });
   }
diff --git a/test/no_packages_dir_test.dart b/test/no_packages_dir_test.dart
index cdd6351..3a88bc5 100644
--- a/test/no_packages_dir_test.dart
+++ b/test/no_packages_dir_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import 'descriptor.dart' as d;
diff --git a/test/oauth2/logout_test.dart b/test/oauth2/logout_test.dart
index 81d4125..a12a652 100644
--- a/test/oauth2/logout_test.dart
+++ b/test/oauth2/logout_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -13,7 +11,7 @@
   test('with an existing credentials file, deletes it.', () async {
     await servePackages();
     await d
-        .credentialsFile(globalPackageServer, 'access token',
+        .credentialsFile(globalServer, 'access token',
             refreshToken: 'refresh token',
             expiration: DateTime.now().add(Duration(hours: 1)))
         .create();
@@ -30,7 +28,7 @@
     await servePackages();
     await d
         .credentialsFile(
-          globalPackageServer,
+          globalServer,
           'access token',
           refreshToken: 'refresh token',
           expiration: DateTime.now().add(Duration(hours: 1)),
@@ -39,7 +37,7 @@
 
     await d
         .legacyCredentialsFile(
-          globalPackageServer,
+          globalServer,
           'access token',
           refreshToken: 'refresh token',
           expiration: DateTime.now().add(Duration(hours: 1)),
diff --git a/test/oauth2/utils.dart b/test/oauth2/utils.dart
index b7d765e..dab8c92 100644
--- a/test/oauth2/utils.dart
+++ b/test/oauth2/utils.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -24,10 +22,10 @@
 
   var line = await pub.stdout.next;
   var match =
-      RegExp(r'[?&]redirect_uri=([0-9a-zA-Z.%+-]+)[$&]').firstMatch(line);
+      RegExp(r'[?&]redirect_uri=([0-9a-zA-Z.%+-]+)[$&]').firstMatch(line)!;
   expect(match, isNotNull);
 
-  var redirectUrl = Uri.parse(Uri.decodeComponent(match.group(1)));
+  var redirectUrl = Uri.parse(Uri.decodeComponent(match.group(1)!));
   redirectUrl = _addQueryParameters(redirectUrl, {'code': 'access code'});
 
   // Expect the /token request
@@ -61,8 +59,8 @@
 }
 
 /// Convert a [Map] from parameter names to values to a URL query string.
-String _mapToQuery(Map<String, String> map) {
-  var pairs = <List<String>>[];
+String _mapToQuery(Map<String, String?> map) {
+  var pairs = <List<String?>>[];
   map.forEach((key, value) {
     key = Uri.encodeQueryComponent(key);
     value = (value == null || value.isEmpty)
diff --git a/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart b/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart
index 5585526..75b47ab 100644
--- a/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart
+++ b/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -21,11 +19,11 @@
     await d.dir(
         configPath, [d.file('pub-credentials.json', '{bad json')]).create();
 
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
     await confirmPublish(pub);
-    await authorizePub(pub, globalPackageServer, 'new access token');
+    await authorizePub(pub, globalServer, 'new access token');
 
-    globalPackageServer.expect('GET', '/api/packages/versions/new', (request) {
+    globalServer.expect('GET', '/api/packages/versions/new', (request) {
       expect(request.headers,
           containsPair('authorization', 'Bearer new access token'));
 
@@ -36,6 +34,6 @@
     // do so rather than killing it so it'll write out the credentials file.
     await pub.shouldExit(1);
 
-    await d.credentialsFile(globalPackageServer, 'new access token').validate();
+    await d.credentialsFile(globalServer, 'new access token').validate();
   });
 }
diff --git a/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart b/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart
index ad6559f..3c22994 100644
--- a/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart
+++ b/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -14,8 +12,8 @@
     await d.validPackage.create();
 
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
diff --git a/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart b/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
index 3354bbc..624f427 100644
--- a/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
+++ b/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -23,14 +21,14 @@
 
     await servePackages();
     await d
-        .credentialsFile(globalPackageServer, 'access token',
+        .credentialsFile(globalServer, 'access token',
             refreshToken: 'bad refresh token',
             expiration: DateTime.now().subtract(Duration(hours: 1)))
         .create();
 
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
 
-    globalPackageServer.expect('POST', '/token', (request) {
+    globalServer.expect('POST', '/token', (request) {
       return request.read().drain().then((_) {
         return shelf.Response(400,
             body: jsonEncode({'error': 'invalid_request'}),
@@ -41,11 +39,10 @@
     await confirmPublish(pub);
 
     await expectLater(pub.stdout, emits(startsWith('Uploading...')));
-    await authorizePub(pub, globalPackageServer, 'new access token');
+    await authorizePub(pub, globalServer, 'new access token');
 
     var done = Completer();
-    globalPackageServer.expect('GET', '/api/packages/versions/new',
-        (request) async {
+    globalServer.expect('GET', '/api/packages/versions/new', (request) async {
       expect(request.headers,
           containsPair('authorization', 'Bearer new access token'));
 
diff --git a/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart b/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart
index 16062a7..7fbbb9f 100644
--- a/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart
+++ b/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -20,15 +18,15 @@
 
     await servePackages();
     await d
-        .credentialsFile(globalPackageServer, 'access token',
+        .credentialsFile(globalServer, 'access token',
             refreshToken: 'refresh token',
             expiration: DateTime.now().subtract(Duration(hours: 1)))
         .create();
 
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
     await confirmPublish(pub);
 
-    globalPackageServer.expect('POST', '/token', (request) {
+    globalServer.expect('POST', '/token', (request) {
       return request.readAsString().then((body) {
         expect(
             body, matches(RegExp(r'(^|&)refresh_token=refresh\+token(&|$)')));
@@ -40,7 +38,7 @@
       });
     });
 
-    globalPackageServer.expect('GET', '/api/packages/versions/new', (request) {
+    globalServer.expect('GET', '/api/packages/versions/new', (request) {
       expect(request.headers,
           containsPair('authorization', 'Bearer new access token'));
 
@@ -50,7 +48,7 @@
     await pub.shouldExit();
 
     await d
-        .credentialsFile(globalPackageServer, 'new access token',
+        .credentialsFile(globalServer, 'new access token',
             refreshToken: 'refresh token')
         .validate();
   });
diff --git a/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart b/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart
index 6437a5c..803c529 100644
--- a/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart
+++ b/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -19,20 +17,20 @@
 
     await servePackages();
     await d
-        .credentialsFile(globalPackageServer, 'access token',
+        .credentialsFile(globalServer, 'access token',
             expiration: DateTime.now().subtract(Duration(hours: 1)))
         .create();
 
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
     await confirmPublish(pub);
 
     await expectLater(
         pub.stderr,
         emits("Pub's authorization to upload packages has expired and "
             "can't be automatically refreshed."));
-    await authorizePub(pub, globalPackageServer, 'new access token');
+    await authorizePub(pub, globalServer, 'new access token');
 
-    globalPackageServer.expect('GET', '/api/packages/versions/new', (request) {
+    globalServer.expect('GET', '/api/packages/versions/new', (request) {
       expect(request.headers,
           containsPair('authorization', 'Bearer new access token'));
 
@@ -43,6 +41,6 @@
     // do so rather than killing it so it'll write out the credentials file.
     await pub.shouldExit(1);
 
-    await d.credentialsFile(globalPackageServer, 'new access token').validate();
+    await d.credentialsFile(globalServer, 'new access token').validate();
   });
 }
diff --git a/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart b/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart
index 2106960..961a465 100644
--- a/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart
+++ b/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
@@ -18,11 +16,11 @@
     await d.validPackage.create();
 
     await servePackages();
-    var pub = await startPublish(globalPackageServer);
+    var pub = await startPublish(globalServer);
     await confirmPublish(pub);
-    await authorizePub(pub, globalPackageServer);
+    await authorizePub(pub, globalServer);
 
-    globalPackageServer.expect('GET', '/api/packages/versions/new', (request) {
+    globalServer.expect('GET', '/api/packages/versions/new', (request) {
       expect(request.headers,
           containsPair('authorization', 'Bearer access token'));
 
@@ -33,6 +31,6 @@
     // do so rather than killing it so it'll write out the credentials file.
     await pub.shouldExit(1);
 
-    await d.credentialsFile(globalPackageServer, 'access token').validate();
+    await d.credentialsFile(globalServer, 'access token').validate();
   });
 }
diff --git a/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart b/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart
index 9ef5e69..2e3b62f 100644
--- a/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart
+++ b/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:shelf/shelf.dart' as shelf;
@@ -18,12 +16,12 @@
       'credentials.json', () async {
     await d.validPackage.create();
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPublish(globalPackageServer);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    globalPackageServer.expect('GET', '/api/packages/versions/new', (request) {
+    globalServer.expect('GET', '/api/packages/versions/new', (request) {
       return shelf.Response(401,
           body: jsonEncode({
             'error': {'message': 'your token sucks'}
diff --git a/test/outdated/goldens/bad_arguments.txt b/test/outdated/goldens/bad_arguments.txt
deleted file mode 100644
index b184530..0000000
--- a/test/outdated/goldens/bad_arguments.txt
+++ /dev/null
@@ -1,58 +0,0 @@
-$ pub outdated random_argument
-[ERR] Command "outdated" does not take any arguments.
-[ERR] 
-[ERR] Usage: pub outdated [options]
-[ERR] -h, --help                         Print this usage information.
-[ERR]     --[no-]color                   Whether to color the output.
-[ERR]                                    Defaults to color when connected to a
-[ERR]                                    terminal, and no-color otherwise.
-[ERR]     --[no-]dependency-overrides    Show resolutions with `dependency_overrides`.
-[ERR]                                    (defaults to on)
-[ERR]     --[no-]dev-dependencies        Take dev dependencies into account.
-[ERR]                                    (defaults to on)
-[ERR]     --json                         Output the results using a json format.
-[ERR]     --mode=<PROPERTY>              Highlight versions with PROPERTY.
-[ERR]                                    Only packages currently missing that PROPERTY
-[ERR]                                    will be included unless --show-all.
-[ERR]                                    [outdated (default), null-safety]
-[ERR]     --[no-]prereleases             Include prereleases in latest version.
-[ERR]                                    (defaults to on in --mode=null-safety).
-[ERR]     --[no-]show-all                Include dependencies that are already
-[ERR]                                    fullfilling --mode.
-[ERR]     --[no-]transitive              Show transitive dependencies.
-[ERR]                                    (defaults to off in --mode=null-safety).
-[ERR] -C, --directory=<dir>              Run this in the directory<dir>.
-[ERR] 
-[ERR] Run "pub help" to see global options.
-[ERR] See https://dart.dev/tools/pub/cmd/pub-outdated for detailed documentation.
-[Exit code] 64
-
-$ pub outdated --bad_flag
-[ERR] Could not find an option named "bad_flag".
-[ERR] 
-[ERR] Usage: pub outdated [options]
-[ERR] -h, --help                         Print this usage information.
-[ERR]     --[no-]color                   Whether to color the output.
-[ERR]                                    Defaults to color when connected to a
-[ERR]                                    terminal, and no-color otherwise.
-[ERR]     --[no-]dependency-overrides    Show resolutions with `dependency_overrides`.
-[ERR]                                    (defaults to on)
-[ERR]     --[no-]dev-dependencies        Take dev dependencies into account.
-[ERR]                                    (defaults to on)
-[ERR]     --json                         Output the results using a json format.
-[ERR]     --mode=<PROPERTY>              Highlight versions with PROPERTY.
-[ERR]                                    Only packages currently missing that PROPERTY
-[ERR]                                    will be included unless --show-all.
-[ERR]                                    [outdated (default), null-safety]
-[ERR]     --[no-]prereleases             Include prereleases in latest version.
-[ERR]                                    (defaults to on in --mode=null-safety).
-[ERR]     --[no-]show-all                Include dependencies that are already
-[ERR]                                    fullfilling --mode.
-[ERR]     --[no-]transitive              Show transitive dependencies.
-[ERR]                                    (defaults to off in --mode=null-safety).
-[ERR] -C, --directory=<dir>              Run this in the directory<dir>.
-[ERR] 
-[ERR] Run "pub help" to see global options.
-[ERR] See https://dart.dev/tools/pub/cmd/pub-outdated for detailed documentation.
-[Exit code] 64
-
diff --git a/test/outdated/goldens/no_pubspec.txt b/test/outdated/goldens/no_pubspec.txt
deleted file mode 100644
index ec3a40a..0000000
--- a/test/outdated/goldens/no_pubspec.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-$ pub outdated
-[ERR] Could not find a file named "pubspec.yaml" in "$SANDBOX/myapp".
-[Exit code] 66
-
diff --git a/test/outdated/outdated_test.dart b/test/outdated/outdated_test.dart
index 925e0de..31113c7 100644
--- a/test/outdated/outdated_test.dart
+++ b/test/outdated/outdated_test.dart
@@ -2,75 +2,67 @@
 // 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.
 
-// @dart=2.10
-
-import 'package:test/test.dart';
 import '../descriptor.dart' as d;
 import '../golden_file.dart';
 import '../test_pub.dart';
 
-/// Try running 'pub outdated' with a number of different sets of arguments.
-///
-/// Compare the stdout and stderr output to the file in goldens/$[name].
-Future<void> variations(String name, {Map<String, String> environment}) async {
-  final buffer = StringBuffer();
-  for (final args in [
-    ['outdated', '--json'],
-    ['outdated', '--no-color'],
-    ['outdated', '--no-color', '--no-transitive'],
-    ['outdated', '--no-color', '--up-to-date'],
-    ['outdated', '--no-color', '--prereleases'],
-    ['outdated', '--no-color', '--no-dev-dependencies'],
-    ['outdated', '--no-color', '--no-dependency-overrides'],
-    ['outdated', '--no-color', '--mode=null-safety'],
-    ['outdated', '--no-color', '--mode=null-safety', '--transitive'],
-    ['outdated', '--no-color', '--mode=null-safety', '--no-prereleases'],
-    ['outdated', '--json', '--mode=null-safety'],
-    ['outdated', '--json', '--no-dev-dependencies'],
-  ]) {
-    await runPubIntoBuffer(args, buffer, environment: environment);
+extension on GoldenTestContext {
+  /// Try running 'pub outdated' with a number of different sets of arguments.
+  /// And compare to results from test/testdata/goldens/...
+  Future<void> runOutdatedTests({
+    Map<String, String>? environment,
+    String? workingDirectory,
+  }) async {
+    const commands = [
+      ['outdated', '--json'],
+      ['outdated', '--no-color'],
+      ['outdated', '--no-color', '--no-transitive'],
+      ['outdated', '--no-color', '--up-to-date'],
+      ['outdated', '--no-color', '--prereleases'],
+      ['outdated', '--no-color', '--no-dev-dependencies'],
+      ['outdated', '--no-color', '--no-dependency-overrides'],
+      ['outdated', '--no-color', '--mode=null-safety'],
+      ['outdated', '--no-color', '--mode=null-safety', '--transitive'],
+      ['outdated', '--no-color', '--mode=null-safety', '--no-prereleases'],
+      ['outdated', '--json', '--mode=null-safety'],
+      ['outdated', '--json', '--no-dev-dependencies'],
+    ];
+    for (final args in commands) {
+      await run(
+        args,
+        environment: environment,
+        workingDirectory: workingDirectory,
+      );
+    }
   }
-  // The easiest way to update the golden files is to delete them and rerun the
-  // test.
-  expectMatchesGoldenFile(buffer.toString(), 'test/outdated/goldens/$name.txt');
 }
 
 Future<void> main() async {
-  test('help text', () async {
-    final buffer = StringBuffer();
-    await runPubIntoBuffer(
-      ['outdated', '--help'],
-      buffer,
-    );
-    expectMatchesGoldenFile(
-        buffer.toString(), 'test/outdated/goldens/helptext.txt');
-  });
-
-  test('no pubspec', () async {
+  testWithGolden('no pubspec', (ctx) async {
     await d.dir(appPath, []).create();
-    final buffer = StringBuffer();
-    await runPubIntoBuffer(['outdated'], buffer);
-    expectMatchesGoldenFile(
-        buffer.toString(), 'test/outdated/goldens/no_pubspec.txt');
+    await ctx.run(['outdated']);
   });
 
-  test('no lockfile', () async {
+  testWithGolden('no lockfile', (ctx) async {
     await d.appDir({'foo': '^1.0.0', 'bar': '^1.0.0'}).create();
-    await servePackages((builder) => builder
+    await servePackages()
       ..serve('foo', '1.2.3')
       ..serve('bar', '1.2.3')
-      ..serve('bar', '2.0.0'));
-    await variations('no_lockfile');
+      ..serve('bar', '2.0.0');
+
+    await ctx.runOutdatedTests();
   });
 
-  test('no dependencies', () async {
+  testWithGolden('no dependencies', (ctx) async {
     await d.appDir().create();
     await pubGet();
-    await variations('no_dependencies');
+
+    await ctx.runOutdatedTests();
   });
 
-  test('newer versions available', () async {
-    await servePackages((builder) => builder
+  testWithGolden('newer versions available', (ctx) async {
+    final builder = await servePackages();
+    builder
       ..serve('foo', '1.2.3', deps: {'transitive': '^1.0.0'})
       ..serve('bar', '1.0.0')
       ..serve('builder', '1.2.3', deps: {
@@ -78,7 +70,7 @@
         'dev_trans': '^1.0.0',
       })
       ..serve('transitive', '1.2.3')
-      ..serve('dev_trans', '1.0.0'));
+      ..serve('dev_trans', '1.0.0');
 
     await d.dir('local_package', [
       d.libDir('local_package'),
@@ -97,7 +89,7 @@
       })
     ]).create();
     await pubGet();
-    globalPackageServer.add((builder) => builder
+    builder
       ..serve('foo', '1.3.0', deps: {'transitive': '>=1.0.0<3.0.0'})
       ..serve('foo', '2.0.0',
           deps: {'transitive': '>=1.0.0<3.0.0', 'transitive2': '^1.0.0'})
@@ -113,14 +105,13 @@
       ..serve('transitive', '2.0.0')
       ..serve('transitive2', '1.0.0')
       ..serve('transitive3', '1.0.0')
-      ..serve('dev_trans', '2.0.0'));
-    await variations('newer_versions');
+      ..serve('dev_trans', '2.0.0');
+    await ctx.runOutdatedTests();
   });
 
-  test('circular dependency on root', () async {
-    await servePackages(
-      (builder) => builder..serve('foo', '1.2.3', deps: {'app': '^1.0.0'}),
-    );
+  testWithGolden('circular dependency on root', (ctx) async {
+    final server = await servePackages();
+    server.serve('foo', '1.2.3', deps: {'app': '^1.0.0'});
 
     await d.dir(appPath, [
       d.pubspec({
@@ -134,13 +125,11 @@
 
     await pubGet();
 
-    globalPackageServer.add(
-      (builder) => builder..serve('foo', '1.3.0', deps: {'app': '^1.0.1'}),
-    );
-    await variations('circular_dependencies');
+    server.serve('foo', '1.3.0', deps: {'app': '^1.0.1'});
+    await ctx.runOutdatedTests();
   });
 
-  test('mutually incompatible newer versions', () async {
+  testWithGolden('mutually incompatible newer versions', (ctx) async {
     await d.dir(appPath, [
       d.pubspec({
         'name': 'app',
@@ -152,17 +141,17 @@
       })
     ]).create();
 
-    await servePackages((builder) => builder
+    await servePackages()
       ..serve('foo', '1.0.0', deps: {'bar': '^1.0.0'})
       ..serve('bar', '1.0.0', deps: {'foo': '^1.0.0'})
       ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
-      ..serve('bar', '2.0.0', deps: {'foo': '^1.0.0'}));
+      ..serve('bar', '2.0.0', deps: {'foo': '^1.0.0'});
     await pubGet();
 
-    await variations('mutually_incompatible');
+    await ctx.runOutdatedTests();
   });
 
-  test('null safety compliance', () async {
+  testWithGolden('null safety compliance', (ctx) async {
     await d.dir(appPath, [
       d.pubspec({
         'name': 'app',
@@ -179,70 +168,69 @@
       }),
     ]).create();
 
-    await servePackages(
-      (builder) => builder
-        ..serve('foo', '1.0.0', deps: {
-          'bar': '^1.0.0'
-        }, pubspec: {
-          'environment': {'sdk': '>=2.9.0 < 3.0.0'}
-        })
-        ..serve('bar', '1.0.0', pubspec: {
-          'environment': {'sdk': '>=2.9.0 < 3.0.0'}
-        })
-        ..serve('foo', '2.0.0-nullsafety.0', deps: {
-          'bar': '^2.0.0'
-        }, pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'}
-        })
-        ..serve('foo', '2.0.0', deps: {
-          'bar': '^1.0.0'
-        }, pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'}
-        })
-        ..serve('bar', '2.0.0', pubspec: {
-          'environment': {'sdk': '>=2.13.0 < 3.0.0'}
-        })
-        ..serve('file_opts_out', '1.0.0', pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        }, contents: [
-          d.dir('lib', [d.file('main.dart', '// @dart = 2.9\n')])
-        ])
-        ..serve('file_opts_out', '2.0.0', pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        })
-        ..serve('fails_analysis', '1.0.0', pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        }, contents: [
-          d.dir('lib', [d.file('main.dart', 'syntax error\n')])
-        ])
-        ..serve('fails_analysis', '2.0.0', pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        })
-        ..serve('file_in_dependency_opts_out', '1.0.0', deps: {
-          'file_opts_out': '^1.0.0'
-        }, pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        })
-        ..serve('file_in_dependency_opts_out', '2.0.0', pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        })
-        ..serve('fails_analysis_in_dependency', '1.0.0', deps: {
-          'fails_analysis': '^1.0.0'
-        }, pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        })
-        ..serve('fails_analysis_in_dependency', '2.0.0', pubspec: {
-          'environment': {'sdk': '>=2.12.0 < 3.0.0'},
-        }),
-    );
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {
+        'bar': '^1.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.9.0 < 3.0.0'}
+      })
+      ..serve('bar', '1.0.0', pubspec: {
+        'environment': {'sdk': '>=2.9.0 < 3.0.0'}
+      })
+      ..serve('foo', '2.0.0-nullsafety.0', deps: {
+        'bar': '^2.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'}
+      })
+      ..serve('foo', '2.0.0', deps: {
+        'bar': '^1.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'}
+      })
+      ..serve('bar', '2.0.0', pubspec: {
+        'environment': {'sdk': '>=2.13.0 < 3.0.0'}
+      })
+      ..serve('file_opts_out', '1.0.0', pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      }, contents: [
+        d.dir('lib', [d.file('main.dart', '// @dart = 2.9\n')])
+      ])
+      ..serve('file_opts_out', '2.0.0', pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      })
+      ..serve('fails_analysis', '1.0.0', pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      }, contents: [
+        d.dir('lib', [d.file('main.dart', 'syntax error\n')])
+      ])
+      ..serve('fails_analysis', '2.0.0', pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      })
+      ..serve('file_in_dependency_opts_out', '1.0.0', deps: {
+        'file_opts_out': '^1.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      })
+      ..serve('file_in_dependency_opts_out', '2.0.0', pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      })
+      ..serve('fails_analysis_in_dependency', '1.0.0', deps: {
+        'fails_analysis': '^1.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      })
+      ..serve('fails_analysis_in_dependency', '2.0.0', pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      });
     await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
 
-    await variations('null_safety',
-        environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
+    await ctx.runOutdatedTests(environment: {
+      '_PUB_TEST_SDK_VERSION': '2.13.0',
+    });
   });
 
-  test('null-safety no resolution', () async {
-    await servePackages((builder) => builder
+  testWithGolden('null-safety no resolution', (ctx) async {
+    await servePackages()
       ..serve('foo', '1.0.0', pubspec: {
         'environment': {'sdk': '>=2.9.0 < 3.0.0'}
       })
@@ -258,7 +246,7 @@
         'foo': '^1.0.0'
       }, pubspec: {
         'environment': {'sdk': '>=2.12.0 < 3.0.0'}
-      }));
+      });
 
     await d.dir(appPath, [
       d.pubspec({
@@ -274,12 +262,13 @@
 
     await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
 
-    await variations('null_safety_no_resolution',
-        environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
+    await ctx.runOutdatedTests(environment: {
+      '_PUB_TEST_SDK_VERSION': '2.13.0',
+    });
   });
 
-  test('null-safety already migrated', () async {
-    await servePackages((builder) => builder
+  testWithGolden('null-safety already migrated', (ctx) async {
+    await servePackages()
       ..serve('foo', '1.0.0', pubspec: {
         'environment': {'sdk': '>=2.9.0 < 3.0.0'}
       })
@@ -296,7 +285,7 @@
       })
       ..serve('devTransitive', '1.0.0', pubspec: {
         'environment': {'sdk': '>=2.9.0 < 3.0.0'}
-      }));
+      });
 
     await d.dir(appPath, [
       d.pubspec({
@@ -314,21 +303,20 @@
 
     await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
 
-    await variations('null_safety_already_migrated',
-        environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
+    await ctx.runOutdatedTests(environment: {
+      '_PUB_TEST_SDK_VERSION': '2.13.0',
+    });
   });
 
-  test('overridden dependencies', () async {
+  testWithGolden('overridden dependencies', (ctx) async {
     ensureGit();
-    await servePackages(
-      (builder) => builder
-        ..serve('foo', '1.0.0')
-        ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
-        ..serve('bar', '1.0.0')
-        ..serve('bar', '2.0.0')
-        ..serve('baz', '1.0.0')
-        ..serve('baz', '2.0.0'),
-    );
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '2.0.0')
+      ..serve('baz', '1.0.0')
+      ..serve('baz', '2.0.0');
 
     await d.git('foo.git', [
       d.libPubspec('foo', '1.0.1'),
@@ -359,18 +347,16 @@
 
     await pubGet();
 
-    await variations('dependency_overrides');
+    await ctx.runOutdatedTests();
   });
 
-  test('overridden dependencies - no resolution', () async {
+  testWithGolden('overridden dependencies - no resolution', (ctx) async {
     ensureGit();
-    await servePackages(
-      (builder) => builder
-        ..serve('foo', '1.0.0', deps: {'bar': '^2.0.0'})
-        ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
-        ..serve('bar', '1.0.0', deps: {'foo': '^1.0.0'})
-        ..serve('bar', '2.0.0', deps: {'foo': '^2.0.0'}),
-    );
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '^2.0.0'})
+      ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
+      ..serve('bar', '1.0.0', deps: {'foo': '^1.0.0'})
+      ..serve('bar', '2.0.0', deps: {'foo': '^2.0.0'});
 
     await d.dir(appPath, [
       d.pubspec({
@@ -389,13 +375,13 @@
 
     await pubGet();
 
-    await variations('dependency_overrides_no_solution');
+    await ctx.runOutdatedTests();
   });
 
-  test(
+  testWithGolden(
       'latest version reported while locked on a prerelease can be a prerelease',
-      () async {
-    await servePackages((builder) => builder
+      (ctx) async {
+    await servePackages()
       ..serve('foo', '0.9.0')
       ..serve('foo', '1.0.0-dev.1')
       ..serve('foo', '1.0.0-dev.2')
@@ -404,7 +390,7 @@
       ..serve('bar', '1.0.0-dev.2')
       ..serve('mop', '0.10.0-dev')
       ..serve('mop', '0.10.0')
-      ..serve('mop', '1.0.0-dev'));
+      ..serve('mop', '1.0.0-dev');
     await d.dir(appPath, [
       d.pubspec({
         'name': 'app',
@@ -419,11 +405,11 @@
 
     await pubGet();
 
-    await variations('prereleases');
+    await ctx.runOutdatedTests();
   });
 
-  test('Handles SDK dependencies', () async {
-    await servePackages((builder) => builder
+  testWithGolden('Handles SDK dependencies', (ctx) async {
+    await servePackages()
       ..serve('foo', '1.0.0', pubspec: {
         'environment': {'sdk': '>=2.10.0 <3.0.0'}
       })
@@ -432,7 +418,7 @@
       })
       ..serve('foo', '2.0.0', pubspec: {
         'environment': {'sdk': '>=2.12.0 <3.0.0'}
-      }));
+      });
 
     await d.dir('flutter-root', [
       d.file('version', '1.2.3'),
@@ -471,7 +457,7 @@
       '_PUB_TEST_SDK_VERSION': '2.13.0'
     });
 
-    await variations('handles_sdk_dependencies', environment: {
+    await ctx.runOutdatedTests(environment: {
       'FLUTTER_ROOT': d.path('flutter-root'),
       '_PUB_TEST_SDK_VERSION': '2.13.0',
       // To test that the reproduction command is reflected correctly.
@@ -479,11 +465,8 @@
     });
   });
 
-  test("doesn't allow arguments. Handles bad flags", () async {
-    final sb = StringBuffer();
-    await runPubIntoBuffer(['outdated', 'random_argument'], sb);
-    await runPubIntoBuffer(['outdated', '--bad_flag'], sb);
-    expectMatchesGoldenFile(
-        sb.toString(), 'test/outdated/goldens/bad_arguments.txt');
+  testWithGolden('does not allow arguments - handles bad flags', (ctx) async {
+    await ctx.run(['outdated', 'random_argument']);
+    await ctx.run(['outdated', '--bad_flag']);
   });
 }
diff --git a/test/package_config_file_test.dart b/test/package_config_file_test.dart
index 8cb03f0..763ce42 100644
--- a/test/package_config_file_test.dart
+++ b/test/package_config_file_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
@@ -14,13 +12,12 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     test('package_config.json file is created', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3',
-            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])]);
-        builder.serve('bar', '3.2.1', contents: [d.dir('lib', [])]);
-        builder.serve('baz', '2.2.2',
+      await servePackages()
+        ..serve('foo', '1.2.3',
+            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])])
+        ..serve('bar', '3.2.1', contents: [d.dir('lib', [])])
+        ..serve('baz', '2.2.2',
             deps: {'bar': '3.2.1'}, contents: [d.dir('lib', [])]);
-      });
 
       await d.dir(appPath, [
         d.appPubspec({'foo': '1.2.3'}),
@@ -56,13 +53,12 @@
     });
 
     test('package_config.json file is overwritten', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3',
-            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])]);
-        builder.serve('bar', '3.2.1', contents: [d.dir('lib', [])]);
-        builder.serve('baz', '2.2.2',
+      await servePackages()
+        ..serve('foo', '1.2.3',
+            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])])
+        ..serve('bar', '3.2.1', contents: [d.dir('lib', [])])
+        ..serve('baz', '2.2.2',
             deps: {'bar': '3.2.1'}, contents: [d.dir('lib', [])]);
-      });
 
       await d.dir(appPath, [
         d.appPubspec({'foo': '1.2.3'}),
@@ -119,6 +115,8 @@
           args: ['--offline'], error: equalsIgnoringWhitespace("""
             Because myapp depends on foo any which doesn't exist (could not find
               package foo in cache), version solving failed.
+
+            Try again without --offline!
           """), exitCode: exit_codes.UNAVAILABLE);
 
       await d.dir(appPath, [
@@ -129,11 +127,10 @@
     test(
         '.dart_tool/package_config.json file has relative path to path dependency',
         () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3',
-            deps: {'baz': 'any'}, contents: [d.dir('lib', [])]);
-        builder.serve('baz', '9.9.9', deps: {}, contents: [d.dir('lib', [])]);
-      });
+      await servePackages()
+        ..serve('foo', '1.2.3',
+            deps: {'baz': 'any'}, contents: [d.dir('lib', [])])
+        ..serve('baz', '9.9.9', deps: {}, contents: [d.dir('lib', [])]);
 
       await d.dir('local_baz', [
         d.libDir('baz', 'baz 3.2.1'),
@@ -180,18 +177,17 @@
     });
 
     test('package_config.json has language version', () async {
-      await servePackages((builder) {
-        builder.serve(
-          'foo',
-          '1.2.3',
-          pubspec: {
-            'environment': {
-              'sdk': '>=0.0.1 <=0.2.2+2', // tests runs with '0.1.2+3'
-            },
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '1.2.3',
+        pubspec: {
+          'environment': {
+            'sdk': '>=0.0.1 <=0.2.2+2', // tests runs with '0.1.2+3'
           },
-          contents: [d.dir('lib', [])],
-        );
-      });
+        },
+        contents: [d.dir('lib', [])],
+      );
 
       await d.dir(appPath, [
         d.pubspec({
@@ -225,18 +221,17 @@
     });
 
     test('package_config.json has 2.7 default language version', () async {
-      await servePackages((builder) {
-        builder.serve(
-          'foo',
-          '1.2.3',
-          pubspec: {
-            'environment': {
-              'sdk': 'any',
-            },
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '1.2.3',
+        pubspec: {
+          'environment': {
+            'sdk': 'any',
           },
-          contents: [d.dir('lib', [])],
-        );
-      });
+        },
+        contents: [d.dir('lib', [])],
+      );
 
       await d.dir(appPath, [
         d.pubspec({
diff --git a/test/package_list_files_test.dart b/test/package_list_files_test.dart
index 7891270..091001b 100644
--- a/test/package_list_files_test.dart
+++ b/test/package_list_files_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -15,8 +13,8 @@
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
 
-String root;
-Entrypoint entrypoint;
+late String root;
+Entrypoint? entrypoint;
 
 void main() {
   test('lists files recursively', () async {
@@ -35,7 +33,7 @@
     createEntrypoint();
 
     expect(
-        entrypoint.root.listFiles(),
+        entrypoint!.root.listFiles(),
         unorderedEquals([
           p.join(root, 'pubspec.yaml'),
           p.join(root, 'file1.txt'),
@@ -71,7 +69,7 @@
     createEntrypoint();
 
     expect(
-      () => entrypoint.root.listFiles(),
+      () => entrypoint!.root.listFiles(),
       throwsA(
         isA<DataException>().having(
           (e) => e.message,
@@ -123,7 +121,7 @@
     createEntrypoint();
 
     expect(
-      () => entrypoint.root.listFiles(),
+      () => entrypoint!.root.listFiles(),
       throwsA(
         isA<DataException>().having(
           (e) => e.message,
@@ -151,7 +149,7 @@
     createEntrypoint();
 
     expect(
-      () => entrypoint.root.listFiles(),
+      () => entrypoint!.root.listFiles(),
       throwsA(
         isA<DataException>().having(
           (e) => e.message,
@@ -169,13 +167,13 @@
       d.file('.foo', ''),
     ]).create();
     createEntrypoint();
-    expect(entrypoint.root.listFiles(), {
+    expect(entrypoint!.root.listFiles(), {
       p.join(root, '.foo'),
       p.join(root, 'pubspec.yaml'),
     });
   });
   group('with git', () {
-    d.GitRepoDescriptor repo;
+    late d.GitRepoDescriptor repo;
     setUp(() async {
       ensureGit();
       repo = d.git(appPath, [d.appPubspec()]);
@@ -193,7 +191,7 @@
         ])
       ]).create();
 
-      expect(entrypoint.root.listFiles(), {
+      expect(entrypoint!.root.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file1.txt'),
         p.join(root, 'file2.txt'),
@@ -213,7 +211,7 @@
         ])
       ]).create();
 
-      expect(entrypoint.root.listFiles(), {
+      expect(entrypoint!.root.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file2.text'),
         p.join(root, 'subdir', 'subfile2.text')
@@ -246,7 +244,7 @@
 
       createEntrypoint(p.join(appPath, 'rep', 'sub'));
 
-      expect(entrypoint.root.listFiles(), {
+      expect(entrypoint!.root.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file2.text'),
         p.join(root, 'file4.gak'),
@@ -254,6 +252,23 @@
       });
     });
 
+    test("Don't ignore packages/ before the package root", () async {
+      await d.dir(appPath, [
+        d.dir('packages', [
+          d.dir('app', [
+            d.appPubspec(),
+            d.dir('packages', [d.file('a.txt')]),
+          ]),
+        ]),
+      ]).create();
+
+      createEntrypoint(p.join(appPath, 'packages', 'app'));
+
+      expect(entrypoint!.root.listFiles(), {
+        p.join(root, 'pubspec.yaml'),
+      });
+    });
+
     group('with a submodule', () {
       setUp(() async {
         await d.git('submodule', [
@@ -269,7 +284,7 @@
       });
 
       test('respects its .gitignore with useGitIgnore', () {
-        expect(entrypoint.root.listFiles(), {
+        expect(entrypoint!.root.listFiles(), {
           p.join(root, 'pubspec.yaml'),
           p.join(root, 'submodule', 'file2.text'),
         });
@@ -282,7 +297,7 @@
         d.dir('subdir', [d.file('pubspec.lock')])
       ]).create();
 
-      expect(entrypoint.root.listFiles(), {p.join(root, 'pubspec.yaml')});
+      expect(entrypoint!.root.listFiles(), {p.join(root, 'pubspec.yaml')});
     });
 
     test('ignores packages directories', () async {
@@ -293,7 +308,7 @@
         ])
       ]).create();
 
-      expect(entrypoint.root.listFiles(), {p.join(root, 'pubspec.yaml')});
+      expect(entrypoint!.root.listFiles(), {p.join(root, 'pubspec.yaml')});
     });
 
     test('allows pubspec.lock directories', () async {
@@ -303,7 +318,7 @@
         ])
       ]).create();
 
-      expect(entrypoint.root.listFiles(), {
+      expect(entrypoint!.root.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'pubspec.lock', 'file.txt')
       });
@@ -324,7 +339,7 @@
           ])
         ]).create();
 
-        expect(entrypoint.root.listFiles(beneath: 'subdir'), {
+        expect(entrypoint!.root.listFiles(beneath: 'subdir'), {
           p.join(root, 'subdir', 'subfile1.txt'),
           p.join(root, 'subdir', 'subfile2.txt'),
           p.join(root, 'subdir', 'subsubdir', 'subsubfile1.txt'),
@@ -343,7 +358,7 @@
         d.dir('lib', [d.file('not_ignored.dart', 'content')]),
       ]).create();
       createEntrypoint();
-      expect(entrypoint.root.listFiles(), {
+      expect(entrypoint!.root.listFiles(), {
         p.join(root, 'LICENSE'),
         p.join(root, 'CHANGELOG.md'),
         p.join(root, 'README.md'),
@@ -395,7 +410,7 @@
     ]).create();
 
     createEntrypoint();
-    expect(entrypoint.root.listFiles(), {
+    expect(entrypoint!.root.listFiles(), {
       p.join(root, 'pubspec.yaml'),
       p.join(root, 'not_ignored_by_gitignore.txt'),
       p.join(root, 'ignored_by_gitignore.txt'),
@@ -410,7 +425,7 @@
   });
 }
 
-void createEntrypoint([String path]) {
+void createEntrypoint([String? path]) {
   path ??= appPath;
   root = p.join(d.sandbox, path);
   entrypoint = Entrypoint(root, SystemCache(rootDir: root));
diff --git a/test/package_server.dart b/test/package_server.dart
index ec960ca..952c189 100644
--- a/test/package_server.dart
+++ b/test/package_server.dart
@@ -2,162 +2,158 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
+import 'dart:io';
 
 import 'package:path/path.dart' as p;
+import 'package:pub/src/third_party/tar/tar.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
 import 'package:test/test.dart';
 import 'package:test/test.dart' as test show expect;
 
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
 
-/// The current global [PackageServer].
-PackageServer get globalPackageServer => _globalPackageServer;
-PackageServer _globalPackageServer;
-
-/// Creates an HTTP server that replicates the structure of pub.dartlang.org and
-/// makes it the current [globalServer].
-///
-/// Calls [callback] with a [PackageServerBuilder] that's used to specify
-/// which packages to serve.
-Future servePackages([void Function(PackageServerBuilder) callback]) async {
-  _globalPackageServer = await PackageServer.start(callback ?? (_) {});
-  globalServer = _globalPackageServer._inner;
-
-  addTearDown(() {
-    _globalPackageServer = null;
-  });
-}
-
-/// Like [servePackages], but instead creates an empty server with no packages
-/// registered.
-///
-/// This will always replace a previous server.
-Future serveNoPackages() => servePackages((_) {});
-
-/// Sets up the global package server to report an error on any request.
-///
-/// If no server has been set up, an empty server will be started.
-Future serveErrors() async {
-  if (globalPackageServer == null) {
-    await serveNoPackages();
-  }
-  globalPackageServer.serveErrors();
-}
-
 class PackageServer {
   /// The inner [DescriptorServer] that this uses to serve its descriptors.
-  final DescriptorServer _inner;
+  final shelf.Server _inner;
 
-  /// The [d.DirectoryDescriptor] describing the server layout of
-  /// `/api/packages` on the test server.
-  ///
-  /// This contains metadata for packages that are being served via
-  /// [servePackages].
-  final _servedApiPackageDir = d.dir('packages', []);
+  /// Handlers of requests. Last matching handler will be used.
+  final List<_PatternAndHandler> _handlers = [];
 
-  /// The [d.DirectoryDescriptor] describing the server layout of `/packages` on
-  /// the test server.
-  ///
-  /// This contains the tarballs for packages that are being served via
-  /// [servePackages].
-  final _servedPackageDir = d.dir('packages', []);
+  // A list of all the requests recieved up till now.
+  final List<String> requestedPaths = <String>[];
 
-  /// The current [PackageServerBuilder] that a user uses to specify which
-  /// package to serve.
-  ///
-  /// This is preserved so that additional packages can be added.
-  PackageServerBuilder _builder;
+  PackageServer._(this._inner) {
+    _inner.mount((request) {
+      final path = request.url.path;
+      requestedPaths.add(path);
 
-  /// The port used for the server.
-  int get port => _inner.port;
+      final pathWithInitialSlash = '/$path';
+      for (final entry in _handlers.reversed) {
+        final match = entry.pattern.matchAsPrefix(pathWithInitialSlash);
+        if (match != null && match.end == pathWithInitialSlash.length) {
+          final a = entry.handler(request);
+          return a;
+        }
+      }
+      return shelf.Response.notFound('Could not find ${request.url}');
+    });
+  }
 
-  /// The URL for the server.
-  String get url => 'http://localhost:$port';
+  static final _versionInfoPattern = RegExp(r'/api/packages/([a-zA-Z_0-9]*)');
+  static final _downloadPattern =
+      RegExp(r'/packages/([^/]*)/versions/([^/]*).tar.gz');
 
-  /// Handlers for requests not easily described as packages.
-  Map<Pattern, shelf.Handler> get extraHandlers => _inner.extraHandlers;
+  static Future<PackageServer> start() async {
+    final server =
+        PackageServer._(await shelf_io.IOServer.bind('localhost', 0));
+    server.handle(
+      _versionInfoPattern,
+      (shelf.Request request) {
+        final parts = request.url.pathSegments;
+        assert(parts[0] == 'api');
+        assert(parts[1] == 'packages');
+        final name = parts[2];
 
-  /// From now on report errors on any request.
-  void serveErrors() => extraHandlers
-    ..clear()
-    ..[RegExp('.*')] = (request) {
-      fail('The HTTP server received an unexpected request:\n'
-          '${request.method} ${request.requestedUri}');
-    };
+        final package = server._packages[name];
+        if (package == null) {
+          return shelf.Response.notFound('No package named $name');
+        }
+        return shelf.Response.ok(jsonEncode({
+          'name': name,
+          'uploaders': ['nweiz@google.com'],
+          'versions': package.versions.values
+              .map((version) => packageVersionApiMap(
+                    server._inner.url.toString(),
+                    version.pubspec,
+                    retracted: version.isRetracted,
+                  ))
+              .toList(),
+          if (package.isDiscontinued) 'isDiscontinued': true,
+          if (package.discontinuedReplacementText != null)
+            'replacedBy': package.discontinuedReplacementText,
+        }));
+      },
+    );
 
-  /// Creates an HTTP server that replicates the structure of pub.dartlang.org.
-  ///
-  /// Calls [callback] with a [PackageServerBuilder] that's used to specify
-  /// which packages to serve.
-  static Future<PackageServer> start(
-      void Function(PackageServerBuilder) callback) async {
-    var descriptorServer = await DescriptorServer.start();
-    var server = PackageServer._(descriptorServer);
-    descriptorServer.contents
-      ..add(d.dir('api', [server._servedApiPackageDir]))
-      ..add(server._servedPackageDir);
-    server.add(callback);
+    server.handle(
+      _downloadPattern,
+      (shelf.Request request) {
+        final parts = request.url.pathSegments;
+        assert(parts[0] == 'packages');
+        final name = parts[1];
+        assert(parts[2] == 'versions');
+        final package = server._packages[name];
+        if (package == null) {
+          return shelf.Response.notFound('No package $name');
+        }
+
+        final version = Version.parse(
+            parts[3].substring(0, parts[3].length - '.tar.gz'.length));
+        assert(parts[3].endsWith('.tar.gz'));
+
+        for (final packageVersion in package.versions.values) {
+          if (packageVersion.version == version) {
+            return shelf.Response.ok(packageVersion.contents());
+          }
+        }
+        return shelf.Response.notFound('No version $version of $name');
+      },
+    );
     return server;
   }
 
-  PackageServer._(this._inner) {
-    _builder = PackageServerBuilder._(this);
+  Future<void> close() async {
+    await _inner.close();
   }
 
-  /// Add to the current set of packages that are being served.
-  void add(void Function(PackageServerBuilder) callback) {
-    callback(_builder);
+  /// The port used for the server.
+  int get port => _inner.url.port;
 
-    _servedApiPackageDir.contents.clear();
-    _servedPackageDir.contents.clear();
+  /// The URL for the server.
+  String get url => _inner.url.toString();
 
-    _builder._packages.forEach((name, package) {
-      _servedApiPackageDir.contents.addAll([
-        d.file(
-            name,
-            jsonEncode({
-              'name': name,
-              'uploaders': ['nweiz@google.com'],
-              'versions': package.versions.values
-                  .map((version) => packageVersionApiMap(url, version.pubspec,
-                      retracted: version.isRetracted))
-                  .toList(),
-              if (package.isDiscontinued) 'isDiscontinued': true,
-              if (package.discontinuedReplacementText != null)
-                'replacedBy': package.discontinuedReplacementText,
-            })),
-        d.dir(name, [
-          d.dir('versions', package.versions.values.map((version) {
-            return d.file(
-                version.version.toString(),
-                jsonEncode(packageVersionApiMap(url, version.pubspec,
-                    retracted: version.isRetracted, full: true)));
-          }))
-        ])
-      ]);
+  /// From now on report errors on any request.
+  void serveErrors() => _handlers
+    ..clear()
+    ..add(
+      _PatternAndHandler(
+        RegExp('.*'),
+        (request) {
+          fail('The HTTP server received an unexpected request:\n'
+              '${request.method} ${request.requestedUri}');
+        },
+      ),
+    );
 
-      _servedPackageDir.contents.add(d.dir(name, [
-        d.dir(
-            'versions',
-            package.versions.values.map((version) =>
-                d.tar('${version.version}.tar.gz', version.contents)))
-      ]));
-    });
+  void handle(Pattern pattern, shelf.Handler handler) {
+    _handlers.add(
+      _PatternAndHandler(
+        pattern,
+        handler,
+      ),
+    );
   }
 
   // Installs a handler at [pattern] that expects to be called exactly once with
   // the given [method].
+  //
+  // The handler is installed as the start to give it priority over more general
+  // handlers.
   void expect(String method, Pattern pattern, shelf.Handler handler) {
-    extraHandlers[pattern] = expectAsync1((request) {
-      test.expect(request.method, method);
-      return handler(request);
-    });
+    handle(
+      pattern,
+      expectAsync1(
+        (request) {
+          test.expect(request.method, method);
+          return handler(request);
+        },
+      ),
+    );
   }
 
   /// Returns the path of [package] at [version], installed from this server, in
@@ -169,26 +165,9 @@
   String get cachingPath =>
       p.join(d.sandbox, cachePath, 'hosted', 'localhost%58$port');
 
-  /// Replace the current set of packages that are being served.
-  void replace(void Function(PackageServerBuilder) callback) {
-    _builder._clear();
-    add(callback);
-  }
-}
-
-/// A builder for specifying which packages should be served by [servePackages].
-class PackageServerBuilder {
   /// A map from package names to the concrete packages to serve.
   final _packages = <String, _ServedPackage>{};
 
-  /// The package server that this builder is associated with.
-  final PackageServer _server;
-
-  /// The URL for the server that this builder is associated with.
-  String get serverUrl => _server.url;
-
-  PackageServerBuilder._(this._server);
-
   /// Specifies that a package named [name] with [version] should be served.
   ///
   /// If [deps] is passed, it's used as the "dependencies" field of the pubspec.
@@ -197,9 +176,9 @@
   /// If [contents] is passed, it's used as the contents of the package. By
   /// default, a package just contains a dummy lib directory.
   void serve(String name, String version,
-      {Map<String, dynamic> deps,
-      Map<String, dynamic> pubspec,
-      Iterable<d.Descriptor> contents}) {
+      {Map<String, dynamic>? deps,
+      Map<String, dynamic>? pubspec,
+      List<d.Descriptor>? contents}) {
     var pubspecFields = <String, dynamic>{'name': name, 'version': version};
     if (pubspec != null) pubspecFields.addAll(pubspec);
     if (deps != null) pubspecFields['dependencies'] = deps;
@@ -208,40 +187,84 @@
     contents = [d.file('pubspec.yaml', yaml(pubspecFields)), ...contents];
 
     var package = _packages.putIfAbsent(name, () => _ServedPackage());
-    package.versions[version] = _ServedPackageVersion(pubspecFields, contents);
+    package.versions[version] = _ServedPackageVersion(
+      pubspecFields,
+      contents: () {
+        final entries = <TarEntry>[];
+
+        void addDescriptor(d.Descriptor descriptor, String path) {
+          if (descriptor is d.DirectoryDescriptor) {
+            for (final e in descriptor.contents) {
+              addDescriptor(e, p.posix.join(path, descriptor.name));
+            }
+          } else {
+            entries.add(
+              TarEntry(
+                TarHeader(
+                  // Ensure paths in tar files use forward slashes
+                  name: p.posix.join(path, descriptor.name),
+                  // We want to keep executable bits, but otherwise use the default
+                  // file mode
+                  mode: 420,
+                  // size: 100,
+                  modified: DateTime.now(),
+                  userName: 'pub',
+                  groupName: 'pub',
+                ),
+                (descriptor as d.FileDescriptor).readAsBytes(),
+              ),
+            );
+          }
+        }
+
+        for (final e in contents ?? <d.Descriptor>[]) {
+          addDescriptor(e, '');
+        }
+        return Stream.fromIterable(entries)
+            .transform(tarWriterWith(format: OutputFormat.gnuLongName))
+            .transform(gzip.encoder);
+      },
+    );
   }
 
   // Mark a package discontinued.
   void discontinue(String name,
-      {bool isDiscontinued = true, String replacementText}) {
-    _packages[name]
+      {bool isDiscontinued = true, String? replacementText}) {
+    _packages[name]!
       ..isDiscontinued = isDiscontinued
       ..discontinuedReplacementText = replacementText;
   }
 
   /// Clears all existing packages from this builder.
-  void _clear() {
+  void clearPackages() {
     _packages.clear();
   }
 
   void retractPackageVersion(String name, String version) {
-    _packages[name].versions[version].isRetracted = true;
+    _packages[name]!.versions[version]!.isRetracted = true;
   }
 }
 
 class _ServedPackage {
   final versions = <String, _ServedPackageVersion>{};
   bool isDiscontinued = false;
-  String discontinuedReplacementText;
+  String? discontinuedReplacementText;
 }
 
 /// A package that's intended to be served.
 class _ServedPackageVersion {
   final Map pubspec;
-  final List<d.Descriptor> contents;
+  final Stream<List<int>> Function() contents;
   bool isRetracted = false;
 
   Version get version => Version.parse(pubspec['version']);
 
-  _ServedPackageVersion(this.pubspec, this.contents);
+  _ServedPackageVersion(this.pubspec, {required this.contents});
+}
+
+class _PatternAndHandler {
+  Pattern pattern;
+  shelf.Handler handler;
+
+  _PatternAndHandler(this.pattern, this.handler);
 }
diff --git a/test/packages_file_test.dart b/test/packages_file_test.dart
index ce87d7c..c245a9a 100644
--- a/test/packages_file_test.dart
+++ b/test/packages_file_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
@@ -13,21 +11,20 @@
 
 void main() {
   forBothPubGetAndUpgrade((command) {
-    test('.packages file is created', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3',
-            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])]);
-        builder.serve('bar', '3.2.1', contents: [d.dir('lib', [])]);
-        builder.serve('baz', '2.2.2',
+    test('.packages file is created with flag', () async {
+      await servePackages()
+        ..serve('foo', '1.2.3',
+            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])])
+        ..serve('bar', '3.2.1', contents: [d.dir('lib', [])])
+        ..serve('baz', '2.2.2',
             deps: {'bar': '3.2.1'}, contents: [d.dir('lib', [])]);
-      });
 
       await d.dir(appPath, [
         d.appPubspec({'foo': '1.2.3'}),
         d.dir('lib')
       ]).create();
 
-      await pubCommand(command);
+      await pubCommand(command, args: ['--legacy-packages-file']);
 
       await d.dir(appPath, [
         d.packagesFile(
@@ -35,14 +32,13 @@
       ]).validate();
     });
 
-    test('.packages file is overwritten', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3',
-            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])]);
-        builder.serve('bar', '3.2.1', contents: [d.dir('lib', [])]);
-        builder.serve('baz', '2.2.2',
+    test('.packages file is overwritten with flag', () async {
+      await servePackages()
+        ..serve('foo', '1.2.3',
+            deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])])
+        ..serve('bar', '3.2.1', contents: [d.dir('lib', [])])
+        ..serve('baz', '2.2.2',
             deps: {'bar': '3.2.1'}, contents: [d.dir('lib', [])]);
-      });
 
       await d.dir(appPath, [
         d.appPubspec({'foo': '1.2.3'}),
@@ -55,7 +51,7 @@
       await oldFile.create();
       await oldFile.validate(); // Sanity-check that file was created correctly.
 
-      await pubCommand(command);
+      await pubCommand(command, args: ['--legacy-packages-file']);
 
       await d.dir(appPath, [
         d.packagesFile(
@@ -63,27 +59,32 @@
       ]).validate();
     });
 
-    test('.packages file is not created if pub command fails', () async {
+    test('.packages file is not created if pub command fails with flag',
+        () async {
       await d.dir(appPath, [
         d.appPubspec({'foo': '1.2.3'}),
         d.dir('lib')
       ]).create();
 
       await pubCommand(command,
-          args: ['--offline'], error: equalsIgnoringWhitespace("""
+          args: ['--offline', '--legacy-packages-file'],
+          error: equalsIgnoringWhitespace("""
             Because myapp depends on foo any which doesn't exist (could not find
               package foo in cache), version solving failed.
-          """), exitCode: exit_codes.UNAVAILABLE);
+
+            Try again without --offline!
+          """),
+          exitCode: exit_codes.UNAVAILABLE);
 
       await d.dir(appPath, [d.nothing('.packages')]).validate();
     });
 
-    test('.packages file has relative path to path dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3',
-            deps: {'baz': 'any'}, contents: [d.dir('lib', [])]);
-        builder.serve('baz', '9.9.9', deps: {}, contents: [d.dir('lib', [])]);
-      });
+    test('.packages file has relative path to path dependency with flag',
+        () async {
+      await servePackages()
+        ..serve('foo', '1.2.3',
+            deps: {'baz': 'any'}, contents: [d.dir('lib', [])])
+        ..serve('baz', '9.9.9', deps: {}, contents: [d.dir('lib', [])]);
 
       await d.dir('local_baz', [
         d.libDir('baz', 'baz 3.2.1'),
@@ -103,7 +104,7 @@
         d.dir('lib')
       ]).create();
 
-      await pubCommand(command);
+      await pubCommand(command, args: ['--legacy-packages-file']);
 
       await d.dir(appPath, [
         d.packagesFile({'myapp': '.', 'baz': '../local_baz', 'foo': '1.2.3'}),
diff --git a/test/pub_get_and_upgrade_test.dart b/test/pub_get_and_upgrade_test.dart
index b93d10f..f64f93b 100644
--- a/test/pub_get_and_upgrade_test.dart
+++ b/test/pub_get_and_upgrade_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 
 import 'package:test/test.dart';
@@ -47,7 +45,8 @@
       await pubCommand(command);
 
       await d.dir('myapp', [
-        d.packagesFile({'myapp_name': '.'})
+        d.packageConfigFile(
+            [d.packageConfigEntry(name: 'myapp_name', path: '.')]),
       ]).validate();
     });
 
diff --git a/test/pub_uploader_test.dart b/test/pub_uploader_test.dart
index 98bd2fe..47d50d3 100644
--- a/test/pub_uploader_test.dart
+++ b/test/pub_uploader_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -15,19 +13,6 @@
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
 
-const _usageString = '''
-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.
-''';
-
 Future<TestProcess> startPubUploader(PackageServer server, List<String> args) {
   var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
   var allArgs = ['uploader', ...args];
@@ -40,33 +25,26 @@
 void main() {
   group('displays usage', () {
     test('when run with no arguments', () {
-      return runPub(
-          args: ['uploader'], output: _usageString, exitCode: exit_codes.USAGE);
+      return runPub(args: ['uploader'], exitCode: exit_codes.USAGE);
     });
 
     test('when run with only a command', () {
-      return runPub(
-          args: ['uploader', 'add'],
-          output: _usageString,
-          exitCode: exit_codes.USAGE);
+      return runPub(args: ['uploader', 'add'], exitCode: exit_codes.USAGE);
     });
 
     test('when run with an invalid command', () {
       return runPub(
-          args: ['uploader', 'foo', 'email'],
-          output: _usageString,
-          exitCode: exit_codes.USAGE);
+          args: ['uploader', 'foo', 'email'], exitCode: exit_codes.USAGE);
     });
   });
 
   test('adds an uploader', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
+    await d.credentialsFile(globalServer, 'access token').create();
     var pub = await startPubUploader(
-        globalPackageServer, ['--package', 'pkg', 'add', 'email']);
+        globalServer, ['--package', 'pkg', 'add', 'email']);
 
-    globalPackageServer.expect('POST', '/api/packages/pkg/uploaders',
-        (request) {
+    globalServer.expect('POST', '/api/packages/pkg/uploaders', (request) {
       return request.readAsString().then((body) {
         expect(body, equals('email=email'));
 
@@ -84,11 +62,11 @@
 
   test('removes an uploader', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
+    await d.credentialsFile(globalServer, 'access token').create();
     var pub = await startPubUploader(
-        globalPackageServer, ['--package', 'pkg', 'remove', 'email']);
+        globalServer, ['--package', 'pkg', 'remove', 'email']);
 
-    globalPackageServer.expect('DELETE', '/api/packages/pkg/uploaders/email',
+    globalServer.expect('DELETE', '/api/packages/pkg/uploaders/email',
         (request) {
       return shelf.Response.ok(
           jsonEncode({
@@ -105,11 +83,10 @@
     await d.validPackage.create();
 
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
-    var pub = await startPubUploader(globalPackageServer, ['add', 'email']);
+    await d.credentialsFile(globalServer, 'access token').create();
+    var pub = await startPubUploader(globalServer, ['add', 'email']);
 
-    globalPackageServer.expect('POST', '/api/packages/test_pkg/uploaders',
-        (request) {
+    globalServer.expect('POST', '/api/packages/test_pkg/uploaders', (request) {
       return shelf.Response.ok(
           jsonEncode({
             'success': {'message': 'Good job!'}
@@ -123,12 +100,11 @@
 
   test('add provides an error', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
+    await d.credentialsFile(globalServer, 'access token').create();
     var pub = await startPubUploader(
-        globalPackageServer, ['--package', 'pkg', 'add', 'email']);
+        globalServer, ['--package', 'pkg', 'add', 'email']);
 
-    globalPackageServer.expect('POST', '/api/packages/pkg/uploaders',
-        (request) {
+    globalServer.expect('POST', '/api/packages/pkg/uploaders', (request) {
       return shelf.Response(400,
           body: jsonEncode({
             'error': {'message': 'Bad job!'}
@@ -142,11 +118,11 @@
 
   test('remove provides an error', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
+    await d.credentialsFile(globalServer, 'access token').create();
     var pub = await startPubUploader(
-        globalPackageServer, ['--package', 'pkg', 'remove', 'e/mail']);
+        globalServer, ['--package', 'pkg', 'remove', 'e/mail']);
 
-    globalPackageServer.expect('DELETE', '/api/packages/pkg/uploaders/e%2Fmail',
+    globalServer.expect('DELETE', '/api/packages/pkg/uploaders/e%2Fmail',
         (request) {
       return shelf.Response(400,
           body: jsonEncode({
@@ -161,11 +137,11 @@
 
   test('add provides invalid JSON', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
+    await d.credentialsFile(globalServer, 'access token').create();
     var pub = await startPubUploader(
-        globalPackageServer, ['--package', 'pkg', 'add', 'email']);
+        globalServer, ['--package', 'pkg', 'add', 'email']);
 
-    globalPackageServer.expect('POST', '/api/packages/pkg/uploaders',
+    globalServer.expect('POST', '/api/packages/pkg/uploaders',
         (request) => shelf.Response.ok('{not json'));
 
     expect(
@@ -177,11 +153,11 @@
 
   test('remove provides invalid JSON', () async {
     await servePackages();
-    await d.credentialsFile(globalPackageServer, 'access token').create();
+    await d.credentialsFile(globalServer, 'access token').create();
     var pub = await startPubUploader(
-        globalPackageServer, ['--package', 'pkg', 'remove', 'email']);
+        globalServer, ['--package', 'pkg', 'remove', 'email']);
 
-    globalPackageServer.expect('DELETE', '/api/packages/pkg/uploaders/email',
+    globalServer.expect('DELETE', '/api/packages/pkg/uploaders/email',
         (request) => shelf.Response.ok('{not json'));
 
     expect(
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index 5785b16..62f77c4 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -2,8 +2,7 @@
 // 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.
 
-// @dart=2.10
-
+import 'package:pub/src/language_version.dart';
 import 'package:pub/src/package_name.dart';
 import 'package:pub/src/pubspec.dart';
 import 'package:pub/src/sdk.dart';
@@ -22,14 +21,15 @@
       throw UnsupportedError('Cannot download fake packages.');
 
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description,
+      {String? containingPath, LanguageVersion? languageVersion}) {
     if (description != 'ok') throw FormatException('Bad');
     return PackageRef(name, this, description);
   }
 
   @override
   PackageId parseId(String name, Version version, description,
-          {String containingPath}) =>
+          {String? containingPath}) =>
       PackageId(name, this, version, description);
 
   @override
@@ -50,7 +50,7 @@
     var throwsPubspecException = throwsA(const TypeMatcher<PubspecException>());
 
     void expectPubspecException(String contents, void Function(Pubspec) fn,
-        [String expectedContains]) {
+        [String? expectedContains]) {
       var expectation = const TypeMatcher<PubspecException>();
       if (expectedContains != null) {
         expectation = expectation.having(
@@ -88,7 +88,7 @@
     version: ">=1.2.3 <3.4.5"
 ''', sources);
 
-      var foo = pubspec.dependencies['foo'];
+      var foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.allows(Version(1, 2, 3)), isTrue);
       expect(foo.constraint.allows(Version(1, 2, 5)), isTrue);
@@ -103,7 +103,7 @@
     version: ">=1.2.3 <0.0.0"
 ''', sources);
 
-      var foo = pubspec.dependencies['foo'];
+      var foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.isEmpty, isTrue);
     });
@@ -124,7 +124,7 @@
     version: ">=1.2.3 <3.4.5"
 ''', sources);
 
-      var foo = pubspec.devDependencies['foo'];
+      var foo = pubspec.devDependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.allows(Version(1, 2, 3)), isTrue);
       expect(foo.constraint.allows(Version(1, 2, 5)), isTrue);
@@ -147,7 +147,7 @@
     version: ">=1.2.3 <3.4.5"
 ''', sources);
 
-      var foo = pubspec.dependencyOverrides['foo'];
+      var foo = pubspec.dependencyOverrides['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.allows(Version(1, 2, 3)), isTrue);
       expect(foo.constraint.allows(Version(1, 2, 5)), isTrue);
@@ -169,7 +169,7 @@
     unknown: blah
 ''', sources);
 
-      var foo = pubspec.dependencies['foo'];
+      var foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.source, equals(sources['unknown']));
     });
@@ -181,7 +181,7 @@
     version: 1.2.3
 ''', sources);
 
-      var foo = pubspec.dependencies['foo'];
+      var foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.source, equals(sources['hosted']));
     });
@@ -294,6 +294,170 @@
               'local pubspec.');
     });
 
+    group('source dependencies', () {
+      test('with url and name', () {
+        var pubspec = Pubspec.parse(
+          '''
+name: pkg
+dependencies:
+  foo:
+    hosted:
+      url: https://example.org/pub/
+      name: bar
+''',
+          sources,
+        );
+
+        var foo = pubspec.dependencies['foo']!;
+        expect(foo.name, equals('foo'));
+        expect(foo.source!.name, 'hosted');
+        expect(foo.source!.serializeDescription('', foo.description), {
+          'url': 'https://example.org/pub/',
+          'name': 'bar',
+        });
+      });
+
+      test('with url only', () {
+        var pubspec = Pubspec.parse(
+          '''
+name: pkg
+environment:
+  sdk: ^2.15.0
+dependencies:
+  foo:
+    hosted:
+      url: https://example.org/pub/
+''',
+          sources,
+        );
+
+        var foo = pubspec.dependencies['foo']!;
+        expect(foo.name, equals('foo'));
+        expect(foo.source!.name, 'hosted');
+        expect(foo.source!.serializeDescription('', foo.description), {
+          'url': 'https://example.org/pub/',
+          'name': 'foo',
+        });
+      });
+
+      test('with url as string', () {
+        var pubspec = Pubspec.parse(
+          '''
+name: pkg
+environment:
+  sdk: ^2.15.0
+dependencies:
+  foo:
+    hosted: https://example.org/pub/
+''',
+          sources,
+        );
+
+        var foo = pubspec.dependencies['foo']!;
+        expect(foo.name, equals('foo'));
+        expect(foo.source!.name, 'hosted');
+        expect(foo.source!.serializeDescription('', foo.description), {
+          'url': 'https://example.org/pub/',
+          'name': 'foo',
+        });
+      });
+
+      test('interprets string description as name for older versions', () {
+        var pubspec = Pubspec.parse(
+          '''
+name: pkg
+environment:
+  sdk: ^2.14.0
+dependencies:
+  foo:
+    hosted: bar
+''',
+          sources,
+        );
+
+        var foo = pubspec.dependencies['foo']!;
+        expect(foo.name, equals('foo'));
+        expect(foo.source!.name, 'hosted');
+        expect(foo.source!.serializeDescription('', foo.description), {
+          'url': 'https://pub.dartlang.org',
+          'name': 'bar',
+        });
+      });
+
+      test(
+        'reports helpful span when using new syntax with invalid environment',
+        () {
+          var pubspec = Pubspec.parse('''
+name: pkg
+environment:
+  sdk: invalid value
+dependencies:
+  foo:
+    hosted: https://example.org/pub/
+''', sources);
+
+          expect(
+            () => pubspec.dependencies,
+            throwsA(
+              isA<PubspecException>()
+                  .having((e) => e.span!.text, 'span.text', 'invalid value'),
+            ),
+          );
+        },
+      );
+
+      test('without a description', () {
+        var pubspec = Pubspec.parse(
+          '''
+name: pkg
+dependencies:
+  foo:
+''',
+          sources,
+        );
+
+        var foo = pubspec.dependencies['foo']!;
+        expect(foo.name, equals('foo'));
+        expect(foo.source!.name, 'hosted');
+        expect(foo.source!.serializeDescription('', foo.description), {
+          'url': 'https://pub.dartlang.org',
+          'name': 'foo',
+        });
+      });
+
+      group('throws without a min SDK constraint', () {
+        test('and without a name', () {
+          expectPubspecException(
+              '''
+name: pkg
+dependencies:
+  foo:
+    hosted:
+      url: https://example.org/pub/
+''',
+              (pubspec) => pubspec.dependencies,
+              "The 'name' key must have a string value without a minimum Dart "
+                  'SDK constraint of 2.15.');
+        });
+
+        test(
+          'and a hosted: <value> syntax that looks like an URI was meant',
+          () {
+            expectPubspecException(
+              '''
+name: pkg
+dependencies:
+  foo:
+    hosted: http://pub.example.org
+''',
+              (pubspec) => pubspec.dependencies,
+              'Using `hosted: <url>` is only supported with a minimum SDK constraint of 2.15.',
+            );
+          },
+        );
+      });
+    });
+
     group('git dependencies', () {
       test('path must be a string', () {
         expectPubspecException('''
@@ -548,7 +712,7 @@
 ''', sources);
         expect(pubspec.features, contains('foobar'));
 
-        var feature = pubspec.features['foobar'];
+        var feature = pubspec.features['foobar']!;
         expect(feature.name, equals('foobar'));
         expect(feature.onByDefault, isTrue);
         expect(feature.dependencies, isEmpty);
@@ -585,7 +749,7 @@
 
         expect(pubspec.features, contains('foobar'));
 
-        var feature = pubspec.features['foobar'];
+        var feature = pubspec.features['foobar']!;
         expect(feature.sdkConstraints,
             containsPair('dart', VersionConstraint.parse('^1.0.0')));
         expect(feature.sdkConstraints,
@@ -604,7 +768,7 @@
             Pubspec.parse('features: {foobar: {default: false}}', sources);
 
         expect(pubspec.features, contains('foobar'));
-        expect(pubspec.features['foobar'].onByDefault, isFalse);
+        expect(pubspec.features['foobar']!.onByDefault, isFalse);
       });
 
       test('parses valid dependency specifications', () {
@@ -618,7 +782,7 @@
 
         expect(pubspec.features, contains('foobar'));
 
-        var feature = pubspec.features['foobar'];
+        var feature = pubspec.features['foobar']!;
         expect(feature.name, equals('foobar'));
         expect(feature.onByDefault, isTrue);
         expect(feature.dependencies, hasLength(2));
@@ -634,7 +798,7 @@
         test('can be null', () {
           var pubspec =
               Pubspec.parse('features: {foobar: {requires: null}}', sources);
-          expect(pubspec.features['foobar'].requires, isEmpty);
+          expect(pubspec.features['foobar']!.requires, isEmpty);
         });
 
         test('must be a list', () {
diff --git a/test/pubspec_utils_test.dart b/test/pubspec_utils_test.dart
index 60863f4..6622c1c 100644
--- a/test/pubspec_utils_test.dart
+++ b/test/pubspec_utils_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/pubspec_utils.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
@@ -47,8 +45,10 @@
     });
 
     test('works on compatible version union', () {
-      final constraint1 = VersionConstraint.parse('>=1.2.3 <2.0.0');
-      final constraint2 = VersionConstraint.parse('>2.2.3 <=4.0.0');
+      final constraint1 =
+          VersionConstraint.parse('>=1.2.3 <2.0.0') as VersionRange;
+      final constraint2 =
+          VersionConstraint.parse('>2.2.3 <=4.0.0') as VersionRange;
       final constraint = VersionUnion.fromRanges([constraint1, constraint2]);
 
       final removedUpperBound = stripUpperBound(constraint) as VersionRange;
diff --git a/test/rate_limited_scheduler_test.dart b/test/rate_limited_scheduler_test.dart
index 632efbf..8562b07 100644
--- a/test/rate_limited_scheduler_test.dart
+++ b/test/rate_limited_scheduler_test.dart
@@ -2,11 +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.
 
-// @dart=2.10
-
 import 'dart:async';
 
-import 'package:pedantic/pedantic.dart';
 import 'package:pub/src/rate_limited_scheduler.dart';
 import 'package:test/test.dart';
 
@@ -19,8 +16,8 @@
     final isBeingProcessed = threeCompleters();
 
     Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return i.toUpperCase();
     }
 
@@ -30,11 +27,11 @@
       preschedule('b');
       preschedule('c');
       await Future.wait(
-          [isBeingProcessed['a'].future, isBeingProcessed['b'].future]);
-      expect(isBeingProcessed['c'].isCompleted, isFalse);
-      completers['a'].complete();
-      await isBeingProcessed['c'].future;
-      completers['c'].complete();
+          [isBeingProcessed['a']!.future, isBeingProcessed['b']!.future]);
+      expect(isBeingProcessed['c']!.isCompleted, isFalse);
+      completers['a']!.complete();
+      await isBeingProcessed['c']!.future;
+      completers['c']!.complete();
       expect(await scheduler.schedule('c'), 'C');
     });
   });
@@ -45,8 +42,8 @@
     final isBeingProcessed = threeCompleters();
 
     Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return i.toUpperCase();
     }
 
@@ -57,18 +54,18 @@
         preschedule1('a');
         preschedule2('b');
         preschedule1('c');
-        await isBeingProcessed['a'].future;
+        await isBeingProcessed['a']!.future;
         // b, c should not start processing due to rate-limiting.
-        expect(isBeingProcessed['b'].isCompleted, isFalse);
-        expect(isBeingProcessed['c'].isCompleted, isFalse);
+        expect(isBeingProcessed['b']!.isCompleted, isFalse);
+        expect(isBeingProcessed['c']!.isCompleted, isFalse);
       });
-      completers['a'].complete();
+      completers['a']!.complete();
       // b is removed from the queue, now c should start processing.
-      await isBeingProcessed['c'].future;
-      completers['c'].complete();
+      await isBeingProcessed['c']!.future;
+      completers['c']!.complete();
       expect(await scheduler.schedule('c'), 'C');
       // b is not on the queue anymore.
-      expect(isBeingProcessed['b'].isCompleted, isFalse);
+      expect(isBeingProcessed['b']!.isCompleted, isFalse);
     });
   });
 
@@ -78,27 +75,27 @@
     final isBeingProcessed = threeCompleters();
 
     Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return i.toUpperCase();
     }
 
     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 1);
 
-    Future b;
+    Future? b;
     await scheduler.withPrescheduling((preschedule) async {
       preschedule('a');
       preschedule('b');
-      await isBeingProcessed['a'].future;
+      await isBeingProcessed['a']!.future;
       // b should not start processing due to rate-limiting.
-      expect(isBeingProcessed['b'].isCompleted, isFalse);
+      expect(isBeingProcessed['b']!.isCompleted, isFalse);
       b = scheduler.schedule('b');
     });
-    completers['a'].complete();
+    completers['a']!.complete();
     expect(await scheduler.schedule('a'), 'A');
     // b was scheduled, so it should get processed now
-    await isBeingProcessed['b'].future;
-    completers['b'].complete();
+    await isBeingProcessed['b']!.future;
+    completers['b']!.complete();
     expect(await b, 'B');
   });
 
@@ -107,14 +104,14 @@
     final isBeingProcessed = threeCompleters();
 
     Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return i.toUpperCase();
     }
 
     final scheduler = RateLimitedScheduler(f, maxConcurrentOperations: 2);
 
-    completers['a'].complete();
+    completers['a']!.complete();
     expect(await scheduler.schedule('a'), 'A');
     // Would fail if isBeingProcessed['a'] was completed twice
     expect(await scheduler.schedule('a'), 'A');
@@ -125,8 +122,8 @@
     final isBeingProcessed = threeCompleters();
 
     Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return i.toUpperCase();
     }
 
@@ -134,12 +131,12 @@
     await scheduler.withPrescheduling((preschedule) async {
       preschedule('a');
       preschedule('b');
-      await isBeingProcessed['a'].future;
+      await isBeingProcessed['a']!.future;
       final cResult = scheduler.schedule('c');
-      expect(isBeingProcessed['b'].isCompleted, isFalse);
-      completers['a'].complete();
-      completers['c'].complete();
-      await isBeingProcessed['c'].future;
+      expect(isBeingProcessed['b']!.isCompleted, isFalse);
+      completers['a']!.complete();
+      completers['c']!.complete();
+      await isBeingProcessed['c']!.future;
       // 'c' is done before we allow 'b' to finish processing
       expect(await cResult, 'C');
     });
@@ -150,8 +147,8 @@
     final isBeingProcessed = threeCompleters();
 
     Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return i.toUpperCase();
     }
 
@@ -161,14 +158,15 @@
       preschedule('a');
       preschedule('b');
       preschedule('c');
-      await isBeingProcessed['a'].future;
-      await isBeingProcessed['b'].future;
-      expect(isBeingProcessed['c'].isCompleted, isFalse);
-      unawaited(completers['c'].future.catchError((_) {}));
-      completers['c'].completeError('errorC');
-      completers['a'].completeError('errorA');
-      await isBeingProcessed['c'].future;
-      completers['b'].completeError('errorB');
+
+      await isBeingProcessed['a']!.future;
+      await isBeingProcessed['b']!.future;
+      expect(isBeingProcessed['c']!.isCompleted, isFalse);
+      unawaited(completers['c']!.future.catchError((_) {}));
+      completers['c']!.completeError('errorC');
+      completers['a']!.completeError('errorA');
+      await isBeingProcessed['c']!.future;
+      completers['b']!.completeError('errorB');
       expect(() async => await scheduler.schedule('a'), throwsA('errorA'));
       expect(() async => await scheduler.schedule('b'), throwsA('errorB'));
       expect(() async => await scheduler.schedule('c'), throwsA('errorC'));
@@ -179,9 +177,9 @@
     final completers = threeCompleters();
     final isBeingProcessed = threeCompleters();
 
-    Future<String> f(String i) async {
-      isBeingProcessed[i].complete();
-      await completers[i].future;
+    Future<String?> f(String i) async {
+      isBeingProcessed[i]!.complete();
+      await completers[i]!.future;
       return Zone.current['zoneValue'];
     }
 
@@ -198,16 +196,16 @@
       }, zoneValues: {'zoneValue': 'C'});
 
       await runZoned(() async {
-        await isBeingProcessed['a'].future;
-        await isBeingProcessed['b'].future;
+        await isBeingProcessed['a']!.future;
+        await isBeingProcessed['b']!.future;
         // This will put 'c' in front of the queue, but in a zone with zoneValue
         // bound to S.
         final f = expectLater(scheduler.schedule('c'), completion('S'));
-        completers['a'].complete();
-        completers['b'].complete();
+        completers['a']!.complete();
+        completers['b']!.complete();
         expect(await scheduler.schedule('a'), 'A');
         expect(await scheduler.schedule('b'), 'B');
-        completers['c'].complete();
+        completers['c']!.complete();
         await f;
       }, zoneValues: {'zoneValue': 'S'});
     });
diff --git a/test/real_version_test.dart b/test/real_version_test.dart
index 484a7f6..80afab7 100644
--- a/test/real_version_test.dart
+++ b/test/real_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
diff --git a/test/reformat_ranges_test.dart b/test/reformat_ranges_test.dart
new file mode 100644
index 0000000..35b085f
--- /dev/null
+++ b/test/reformat_ranges_test.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, 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:pub/src/package_name.dart';
+import 'package:pub/src/solver/reformat_ranges.dart';
+import 'package:pub/src/utils.dart';
+import 'package:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('reformatMax when max has a build identifier', () {
+    expect(
+      reformatMax(
+        [PackageId('abc', null, Version.parse('1.2.3'), null)],
+        VersionRange(
+          min: Version.parse('0.2.4'),
+          max: Version.parse('1.2.4'),
+          alwaysIncludeMaxPreRelease: true,
+        ),
+      ),
+      equals(
+        Pair(
+          Version.parse('1.2.4-0'),
+          false,
+        ),
+      ),
+    );
+    expect(
+      reformatMax(
+        [PackageId('abc', null, Version.parse('1.2.4-3'), null)],
+        VersionRange(
+          min: Version.parse('0.2.4'),
+          max: Version.parse('1.2.4'),
+          alwaysIncludeMaxPreRelease: true,
+        ),
+      ),
+      equals(
+        Pair(
+          Version.parse('1.2.4-3'),
+          true,
+        ),
+      ),
+    );
+    expect(
+        reformatMax(
+          [],
+          VersionRange(
+            max: Version.parse('1.2.4+1'),
+            alwaysIncludeMaxPreRelease: true,
+          ),
+        ),
+        equals(null));
+  });
+}
diff --git a/test/remove/remove_test.dart b/test/remove/remove_test.dart
index 777d41b..8c82527 100644
--- a/test/remove/remove_test.dart
+++ b/test/remove/remove_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:io' show File;
 
 import 'package:path/path.dart' as p;
@@ -14,7 +12,8 @@
 
 void main() {
   test('removes a package from dependencies', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({'foo': '1.2.3'}).create();
     await pubGet();
@@ -22,17 +21,16 @@
     await pubRemove(args: ['foo']);
 
     await d.cacheDir({}).validate();
-    await d.appPackagesFile({}).validate();
+    await d.appPackageConfigFile([]).validate();
     await d.appDir().validate();
   });
 
   test('removing a package from dependencies does not affect dev_dependencies',
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-      builder.serve('foo', '1.2.2');
-      builder.serve('bar', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.3')
+      ..serve('foo', '1.2.2')
+      ..serve('bar', '2.0.0');
 
     await d.dir(appPath, [
       d.file('pubspec.yaml', '''
@@ -51,7 +49,9 @@
     await pubRemove(args: ['foo']);
 
     await d.cacheDir({'bar': '2.0.0'}).validate();
-    await d.appPackagesFile({'bar': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+    ]).validate();
 
     await d.dir(appPath, [
       d.pubspec({
@@ -62,7 +62,8 @@
   });
 
   test('dry-run does not actually remove dependency', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.appDir({'foo': '1.2.3'}).create();
     await pubGet();
@@ -100,7 +101,8 @@
   });
 
   test('removes a package from dev_dependencies', () async {
-    await servePackages((builder) => builder.serve('foo', '1.2.3'));
+    final server = await servePackages();
+    server.serve('foo', '1.2.3');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -113,7 +115,7 @@
     await pubRemove(args: ['foo']);
 
     await d.cacheDir({}).validate();
-    await d.appPackagesFile({}).validate();
+    await d.appPackageConfigFile([]).validate();
 
     await d.dir(appPath, [
       d.pubspec({'name': 'myapp'})
@@ -122,12 +124,11 @@
 
   test('removes multiple packages from dependencies and dev_dependencies',
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3');
-      builder.serve('bar', '2.3.4');
-      builder.serve('baz', '3.2.1');
-      builder.serve('jfj', '0.2.1');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.3')
+      ..serve('bar', '2.3.4')
+      ..serve('baz', '3.2.1')
+      ..serve('jfj', '0.2.1');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -141,7 +142,10 @@
     await pubRemove(args: ['foo', 'bar', 'baz']);
 
     await d.cacheDir({'jfj': '0.2.1'}).validate();
-    await d.appPackagesFile({'jfj': '0.2.1'}).validate();
+
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'jfj', version: '0.2.1'),
+    ]).validate();
 
     await d.dir(appPath, [
       d.pubspec({
@@ -152,7 +156,8 @@
   });
 
   test('removes git dependencies', () async {
-    await servePackages((builder) => builder.serve('bar', '1.2.3'));
+    final server = await servePackages();
+    server.serve('bar', '1.2.3');
 
     ensureGit();
     final repo = d.git('foo.git', [
@@ -170,12 +175,16 @@
     await pubGet();
 
     await pubRemove(args: ['foo']);
-    await d.appPackagesFile({'bar': '1.2.3'}).validate();
+
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'bar': '1.2.3'}).validate();
   });
 
   test('removes path dependencies', () async {
-    await servePackages((builder) => builder.serve('bar', '1.2.3'));
+    final server = await servePackages();
+    server.serve('bar', '1.2.3');
     await d
         .dir('foo', [d.libDir('foo'), d.libPubspec('foo', '0.0.1')]).create();
 
@@ -187,21 +196,23 @@
     await pubGet();
 
     await pubRemove(args: ['foo']);
-    await d.appPackagesFile({'bar': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'bar': '1.2.3'}).validate();
   });
 
   test('removes hosted dependencies', () async {
-    await servePackages((builder) => builder.serve('bar', '2.0.1'));
+    final server = await servePackages();
+    server.serve('bar', '2.0.1');
 
-    var server = await PackageServer.start((builder) {
-      builder.serve('foo', '1.2.3');
-    });
+    var custom = await startPackageServer();
+    custom.serve('foo', '1.2.3');
 
     await d.appDir({
       'foo': {
         'version': '1.2.3',
-        'hosted': {'name': 'foo', 'url': 'http://localhost:${server.port}'}
+        'hosted': {'name': 'foo', 'url': 'http://localhost:${custom.port}'}
       },
       'bar': '2.0.1'
     }).create();
@@ -209,15 +220,16 @@
     await pubGet();
 
     await pubRemove(args: ['foo']);
-    await d.appPackagesFile({'bar': '2.0.1'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '2.0.1'),
+    ]).validate();
     await d.appDir({'bar': '2.0.1'}).validate();
   });
 
   test('preserves comments', () async {
-    await servePackages((builder) {
-      builder.serve('bar', '1.0.0');
-      builder.serve('foo', '1.0.0');
-    });
+    await servePackages()
+      ..serve('bar', '1.0.0')
+      ..serve('foo', '1.0.0');
 
     await d.dir(appPath, [
       d.file('pubspec.yaml', '''
diff --git a/test/run/allows_dart_extension_test.dart b/test/run/allows_dart_extension_test.dart
index f88e9bd..962270f 100644
--- a/test/run/allows_dart_extension_test.dart
+++ b/test/run/allows_dart_extension_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-const SCRIPT = """
+const _script = """
 import 'dart:io';
 
 main() {
@@ -23,7 +21,7 @@
   test('allows a ".dart" extension on the argument', () async {
     await d.dir(appPath, [
       d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', SCRIPT)])
+      d.dir('bin', [d.file('script.dart', _script)])
     ]).create();
 
     await pubGet();
diff --git a/test/run/app_can_read_from_stdin_test.dart b/test/run/app_can_read_from_stdin_test.dart
index a0c3983..20f9b32 100644
--- a/test/run/app_can_read_from_stdin_test.dart
+++ b/test/run/app_can_read_from_stdin_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/dartdev/app_can_read_from_stdin_test.dart b/test/run/dartdev/app_can_read_from_stdin_test.dart
deleted file mode 100644
index 933b3ad..0000000
--- a/test/run/dartdev/app_can_read_from_stdin_test.dart
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('the spawned application can read line-by-line from stdin', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [
-        d.file('script.dart', """
-          import 'dart:io';
-
-          main() {
-            print("started");
-            var line1 = stdin.readLineSync();
-            print("between");
-            var line2 = stdin.readLineSync();
-            print(line1);
-            print(line2);
-          }
-        """)
-      ])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['myapp:script']);
-
-    await expectLater(pub.stdout, emitsThrough('started'));
-    pub.stdin.writeln('first');
-    await expectLater(pub.stdout, emits('between'));
-    pub.stdin.writeln('second');
-    expect(pub.stdout, emits('first'));
-    expect(pub.stdout, emits('second'));
-    await pub.shouldExit(0);
-  });
-
-  test('the spawned application can read streamed from stdin', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [
-        d.file('script.dart', """
-          import 'dart:io';
-
-          main() {
-            print("started");
-            stdin.listen(stdout.add);
-          }
-        """)
-      ])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['myapp:script']);
-
-    await expectLater(pub.stdout, emitsThrough('started'));
-    pub.stdin.writeln('first');
-    await expectLater(pub.stdout, emits('first'));
-    pub.stdin.writeln('second');
-    await expectLater(pub.stdout, emits('second'));
-    pub.stdin.writeln('third');
-    await expectLater(pub.stdout, emits('third'));
-    await pub.stdin.close();
-    await pub.shouldExit(0);
-  });
-}
diff --git a/test/run/dartdev/errors_if_only_transitive_dependency_test.dart b/test/run/dartdev/errors_if_only_transitive_dependency_test.dart
deleted file mode 100644
index 529eeb2..0000000
--- a/test/run/dartdev/errors_if_only_transitive_dependency_test.dart
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:pub/src/exit_codes.dart' as exit_codes;
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('Errors if the script is in a non-immediate dependency.', () async {
-    await d.dir('foo', [
-      d.libPubspec('foo', '1.0.0'),
-      d.dir('bin', [d.file('bar.dart', "main() => print('foobar');")])
-    ]).create();
-
-    await d.dir('bar', [
-      d.libPubspec('bar', '1.0.0', deps: {
-        'foo': {'path': '../foo'}
-      })
-    ]).create();
-
-    await d.dir(appPath, [
-      d.appPubspec({
-        'bar': {'path': '../bar'}
-      })
-    ]).create();
-
-    await pubGet();
-
-    var pub = await pubRunFromDartDev(args: ['foo:script']);
-    expect(pub.stderr, emits('Package "foo" is not an immediate dependency.'));
-    expect(pub.stderr,
-        emits('Cannot run executables in transitive dependencies.'));
-    await pub.shouldExit(exit_codes.DATA);
-  });
-}
diff --git a/test/run/dartdev/errors_if_path_in_dependency_test.dart b/test/run/dartdev/errors_if_path_in_dependency_test.dart
deleted file mode 100644
index ddf553a..0000000
--- a/test/run/dartdev/errors_if_path_in_dependency_test.dart
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:pub/src/exit_codes.dart' as exit_codes;
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test(
-      'Errors if the executable is in a subdirectory in a '
-      'dependency.', () async {
-    await d.dir('foo', [d.libPubspec('foo', '1.0.0')]).create();
-
-    await d.dir(appPath, [
-      d.appPubspec({
-        'foo': {'path': '../foo'}
-      })
-    ]).create();
-
-    await runPub(args: ['run', 'foo:sub/dir'], error: '''
-Cannot run an executable in a subdirectory of a dependency.
-
-Usage: pub run <executable> [arguments...]
--h, --help                              Print this usage information.
-    --[no-]enable-asserts               Enable assert statements.
-    --enable-experiment=<experiment>    Runs the executable in a VM with the
-                                        given experiments enabled.
-                                        (Will disable snapshotting, resulting in
-                                        slower startup).
-    --[no-]sound-null-safety            Override the default null safety
-                                        execution mode.
--C, --directory=<dir>                   Run this in the directory<dir>.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
-''', exitCode: exit_codes.USAGE);
-  });
-}
diff --git a/test/run/dartdev/forwards_signal_posix_test.dart b/test/run/dartdev/forwards_signal_posix_test.dart
deleted file mode 100644
index 6044de6..0000000
--- a/test/run/dartdev/forwards_signal_posix_test.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-// Windows doesn't support sending signals.
-@TestOn('!windows')
-import 'dart:io';
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-const _catchableSignals = [
-  ProcessSignal.sighup,
-  ProcessSignal.sigterm,
-  ProcessSignal.sigusr1,
-  ProcessSignal.sigusr2,
-  ProcessSignal.sigwinch,
-];
-
-const SCRIPT = """
-import 'dart:io';
-
-main() {
-  ProcessSignal.SIGHUP.watch().first.then(print);
-  ProcessSignal.SIGTERM.watch().first.then(print);
-  ProcessSignal.SIGUSR1.watch().first.then(print);
-  ProcessSignal.SIGUSR2.watch().first.then(print);
-  ProcessSignal.SIGWINCH.watch().first.then(print);
-
-  print("ready");
-}
-""";
-
-void main() {
-  test('forwards signals to the inner script', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', SCRIPT)])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['myapp:script']);
-
-    await expectLater(pub.stdout, emitsThrough('ready'));
-    for (var signal in _catchableSignals) {
-      pub.signal(signal);
-      await expectLater(pub.stdout, emits(signal.toString()));
-    }
-
-    await pub.kill();
-  });
-}
diff --git a/test/run/dartdev/loads_package_imports_in_a_dependency_test.dart b/test/run/dartdev/loads_package_imports_in_a_dependency_test.dart
deleted file mode 100644
index f6d84c1..0000000
--- a/test/run/dartdev/loads_package_imports_in_a_dependency_test.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('loads package imports in a dependency', () async {
-    await d.dir('foo', [
-      d.libPubspec('foo', '1.0.0'),
-      d.dir('lib', [d.file('foo.dart', "final value = 'foobar';")]),
-      d.dir('bin', [
-        d.file('bar.dart', '''
-import "package:foo/foo.dart";
-
-main() => print(value);
-''')
-      ])
-    ]).create();
-
-    await d.dir(appPath, [
-      d.appPubspec({
-        'foo': {'path': '../foo'}
-      })
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['foo:bar']);
-    expect(pub.stdout, emitsThrough('foobar'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/nonexistent_dependency_test.dart b/test/run/dartdev/nonexistent_dependency_test.dart
deleted file mode 100644
index 695d147..0000000
--- a/test/run/dartdev/nonexistent_dependency_test.dart
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:pub/src/exit_codes.dart' as exit_codes;
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('Errors if the script is in an unknown package.', () async {
-    await d.dir(appPath, [d.appPubspec()]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['foo:script']);
-    expect(
-        pub.stderr,
-        emits('Could not find package "foo". Did you forget to add a '
-            'dependency?'));
-    await pub.shouldExit(exit_codes.DATA);
-  });
-}
diff --git a/test/run/dartdev/nonexistent_script_in_dependency_test.dart b/test/run/dartdev/nonexistent_script_in_dependency_test.dart
deleted file mode 100644
index 5cf982f..0000000
--- a/test/run/dartdev/nonexistent_script_in_dependency_test.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:path/path.dart' as p;
-import 'package:pub/src/exit_codes.dart' as exit_codes;
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('Errors if the script in a dependency does not exist.', () async {
-    await d.dir('foo', [d.libPubspec('foo', '1.0.0')]).create();
-
-    await d.dir(appPath, [
-      d.appPubspec({
-        'foo': {'path': '../foo'}
-      })
-    ]).create();
-
-    await pubGet();
-
-    var pub = await pubRunFromDartDev(args: ['foo:script']);
-    expect(
-        pub.stderr,
-        emits(
-            "Could not find ${p.join("bin", "script.dart")} in package foo."));
-    await pub.shouldExit(exit_codes.NO_INPUT);
-  });
-}
diff --git a/test/run/dartdev/package_api_test.dart b/test/run/dartdev/package_api_test.dart
deleted file mode 100644
index b4c4f80..0000000
--- a/test/run/dartdev/package_api_test.dart
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:path/path.dart' as p;
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-const _script = """
-  import 'dart:isolate';
-
-  main() async {
-    print(await Isolate.packageRoot);
-    print(await Isolate.packageConfig);
-    print(await Isolate.resolvePackageUri(
-        Uri.parse('package:myapp/resource.txt')));
-    print(await Isolate.resolvePackageUri(
-        Uri.parse('package:foo/resource.txt')));
-  }
-""";
-
-void main() {
-  test('an untransformed application sees a file: package config', () async {
-    await d.dir('foo', [d.libPubspec('foo', '1.0.0')]).create();
-
-    await d.dir(appPath, [
-      d.appPubspec({
-        'foo': {'path': '../foo'}
-      }),
-      d.dir('bin', [d.file('script.dart', _script)])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['myapp:script']);
-
-    expect(pub.stdout, emitsThrough('null'));
-    expect(
-        pub.stdout,
-        emits(p
-            .toUri(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json'))
-            .toString()));
-    expect(pub.stdout,
-        emits(p.toUri(p.join(d.sandbox, 'myapp/lib/resource.txt')).toString()));
-    expect(pub.stdout,
-        emits(p.toUri(p.join(d.sandbox, 'foo/lib/resource.txt')).toString()));
-    await pub.shouldExit(0);
-  });
-
-  test('a snapshotted application sees a file: package root', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', _script)])
-      ]);
-    });
-
-    await d.dir(appPath, [
-      d.appPubspec({'foo': 'any'})
-    ]).create();
-
-    await pubGet();
-
-    var pub = await pubRunFromDartDev(args: ['foo:script']);
-
-    expect(pub.stdout, emits('Building package executable...'));
-    expect(pub.stdout, emits('Built foo:script.'));
-    expect(pub.stdout, emits('null'));
-    expect(
-        pub.stdout,
-        emits(p
-            .toUri(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json'))
-            .toString()));
-    expect(pub.stdout,
-        emits(p.toUri(p.join(d.sandbox, 'myapp/lib/resource.txt')).toString()));
-    var fooResourcePath = p.join(
-        globalPackageServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
-    expect(pub.stdout, emits(p.toUri(fooResourcePath).toString()));
-    await pub.shouldExit(0);
-  });
-}
diff --git a/test/run/dartdev/passes_along_arguments_test.dart b/test/run/dartdev/passes_along_arguments_test.dart
deleted file mode 100644
index 00d05fc..0000000
--- a/test/run/dartdev/passes_along_arguments_test.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-const SCRIPT = '''
-main(List<String> args) {
-  print(args.join(" "));
-}
-''';
-
-void main() {
-  test('passes arguments to the spawned script', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [d.file('args.dart', SCRIPT)])
-    ]).create();
-
-    await pubGet();
-
-    // Use some args that would trip up pub's arg parser to ensure that it
-    // isn't trying to look at them.
-    var pub = await pubRunFromDartDev(
-        args: ['myapp:args', '--verbose', '-m', '--', 'help']);
-
-    expect(pub.stdout, emitsThrough('--verbose -m -- help'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/runs_default_app_without_arguments.dart b/test/run/dartdev/runs_default_app_without_arguments.dart
deleted file mode 100644
index 3b9e119..0000000
--- a/test/run/dartdev/runs_default_app_without_arguments.dart
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('runs default Dart application without arguments', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [d.file('myapp.dart', "main() => print('foobar');")])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: []);
-    expect(pub.stdout, emits('foobar'));
-    await pub.shouldExit();
-  });
-
-  test('runs main.dart Dart application without arguments', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [d.file('main.dart', "main() => print('foobar');")])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: []);
-    expect(pub.stdout, emits('foobar'));
-    await pub.shouldExit();
-  });
-
-  test('prefers default Dart application without arguments', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [
-        d.file('myapp.dart', "main() => print('foobar');"),
-        d.file('main.dart', "main() => print('-');"),
-      ])
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: []);
-    expect(pub.stdout, emits('foobar'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/runs_from_a_dependency_override_after_dependency_test.dart b/test/run/dartdev/runs_from_a_dependency_override_after_dependency_test.dart
deleted file mode 100644
index 9bb84d5..0000000
--- a/test/run/dartdev/runs_from_a_dependency_override_after_dependency_test.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  // Regression test for issue 23113
-  test('runs a named Dart application in a dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'name': 'foo',
-        'version': '1.0.0'
-      }, contents: [
-        d.dir('bin', [d.file('bar.dart', "main() => print('foobar');")])
-      ]);
-    });
-
-    await d.dir(appPath, [
-      d.appPubspec({'foo': null})
-    ]).create();
-
-    await pubGet(args: ['--precompile']);
-
-    var pub = await pubRunFromDartDev(args: ['foo:bar']);
-    expect(pub.stdout, emitsThrough('foobar'));
-    await pub.shouldExit();
-
-    await d.dir('foo', [
-      d.libPubspec('foo', '2.0.0'),
-      d.dir('bin', [d.file('bar.dart', "main() => print('different');")])
-    ]).create();
-
-    await d.dir(appPath, [
-      d.pubspec({
-        'name': 'myapp',
-        'dependencies': {
-          'foo': {'path': '../foo'}
-        }
-      })
-    ]).create();
-
-    await pubGet();
-
-    pub = await pubRunFromDartDev(args: ['foo:bar']);
-    expect(pub.stdout, emitsThrough('different'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/runs_named_app_in_dependency_test.dart b/test/run/dartdev/runs_named_app_in_dependency_test.dart
deleted file mode 100644
index b7e3124..0000000
--- a/test/run/dartdev/runs_named_app_in_dependency_test.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('runs a named Dart application in a dependency', () async {
-    await d.dir('foo', [
-      d.libPubspec('foo', '1.0.0'),
-      d.dir('bin', [d.file('bar.dart', "main() => print('foobar');")])
-    ]).create();
-
-    await d.dir(appPath, [
-      d.appPubspec({
-        'foo': {'path': '../foo'}
-      })
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['foo:bar']);
-    expect(pub.stdout, emitsThrough('foobar'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/runs_named_app_in_dev_dependency_test.dart b/test/run/dartdev/runs_named_app_in_dev_dependency_test.dart
deleted file mode 100644
index 990d360..0000000
--- a/test/run/dartdev/runs_named_app_in_dev_dependency_test.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('runs a named Dart application in a dev dependency', () async {
-    await d.dir('foo', [
-      d.libPubspec('foo', '1.0.0'),
-      d.dir('bin', [d.file('bar.dart', "main() => print('foobar');")])
-    ]).create();
-
-    await d.dir(appPath, [
-      d.pubspec({
-        'name': 'myapp',
-        'dev_dependencies': {
-          'foo': {'path': '../foo'}
-        }
-      })
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['foo:bar']);
-    expect(pub.stdout, emitsThrough('foobar'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/runs_shorthand_app_in_dependency_test.dart b/test/run/dartdev/runs_shorthand_app_in_dependency_test.dart
deleted file mode 100644
index 5cf7865..0000000
--- a/test/run/dartdev/runs_shorthand_app_in_dependency_test.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('runs a shorthand Dart application in a dependency', () async {
-    await d.dir('foo', [
-      d.libPubspec('foo', '1.0.0'),
-      d.dir('bin', [d.file('foo.dart', "main() => print('foo');")])
-    ]).create();
-
-    await d.dir(appPath, [
-      d.pubspec({
-        'name': 'myapp',
-        'dependencies': {
-          'foo': {'path': '../foo'}
-        }
-      })
-    ]).create();
-
-    await pubGet();
-    var pub = await pubRunFromDartDev(args: ['foo']);
-    expect(pub.stdout, emitsThrough('foo'));
-    await pub.shouldExit();
-  });
-}
diff --git a/test/run/dartdev/runs_the_script_in_checked_mode_test.dart b/test/run/dartdev/runs_the_script_in_checked_mode_test.dart
deleted file mode 100644
index 002406f..0000000
--- a/test/run/dartdev/runs_the_script_in_checked_mode_test.dart
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('runs the script with assertions enabled', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', 'main() { assert(false); }')])
-    ]).create();
-
-    await pubGet();
-    await runPub(
-        args: ['run', '--enable-asserts', 'myapp:script'],
-        error: contains('Failed assertion'),
-        exitCode: 255);
-  });
-}
diff --git a/test/run/dartdev/runs_the_script_in_unchecked_mode_test.dart b/test/run/dartdev/runs_the_script_in_unchecked_mode_test.dart
deleted file mode 100644
index bc85e54..0000000
--- a/test/run/dartdev/runs_the_script_in_unchecked_mode_test.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart=2.10
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-const SCRIPT = '''
-main() {
-  assert(false);
-  print("no checks");
-}
-''';
-
-void main() {
-  test('runs the script without assertions by default', () async {
-    await d.dir(appPath, [
-      d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', SCRIPT)])
-    ]).create();
-
-    await pubGet();
-    await runPub(args: ['run', 'myapp:script'], output: contains('no checks'));
-  });
-}
diff --git a/test/run/enable_experiments_test.dart b/test/run/enable_experiments_test.dart
index 95468f0..dd6512b 100644
--- a/test/run/enable_experiments_test.dart
+++ b/test/run/enable_experiments_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/errors_if_no_executable_is_given_test.dart b/test/run/errors_if_no_executable_is_given_test.dart
index 2bec1a3..3e5abd6 100644
--- a/test/run/errors_if_no_executable_is_given_test.dart
+++ b/test/run/errors_if_no_executable_is_given_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -14,22 +12,10 @@
   test('Errors if the executable does not exist.', () async {
     await d.dir(appPath, [d.appPubspec()]).create();
 
-    await runPub(args: ['run'], error: '''
-Must specify an executable to run.
-
-Usage: pub run <executable> [arguments...]
--h, --help                              Print this usage information.
-    --[no-]enable-asserts               Enable assert statements.
-    --enable-experiment=<experiment>    Runs the executable in a VM with the
-                                        given experiments enabled.
-                                        (Will disable snapshotting, resulting in
-                                        slower startup).
-    --[no-]sound-null-safety            Override the default null safety
-                                        execution mode.
--C, --directory=<dir>                   Run this in the directory<dir>.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
-''', exitCode: exit_codes.USAGE);
+    await runPub(
+      args: ['run'],
+      error: contains('Must specify an executable to run.'),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/run/errors_if_only_transitive_dependency_test.dart b/test/run/errors_if_only_transitive_dependency_test.dart
index d5d1601..71fcd3d 100644
--- a/test/run/errors_if_only_transitive_dependency_test.dart
+++ b/test/run/errors_if_only_transitive_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/run/errors_if_path_in_dependency_test.dart b/test/run/errors_if_path_in_dependency_test.dart
index 5625c4e..329650b 100644
--- a/test/run/errors_if_path_in_dependency_test.dart
+++ b/test/run/errors_if_path_in_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -22,22 +20,12 @@
       })
     ]).create();
 
-    await runPub(args: ['run', 'foo:sub/dir'], error: '''
-Cannot run an executable in a subdirectory of a dependency.
-
-Usage: pub run <executable> [arguments...]
--h, --help                              Print this usage information.
-    --[no-]enable-asserts               Enable assert statements.
-    --enable-experiment=<experiment>    Runs the executable in a VM with the
-                                        given experiments enabled.
-                                        (Will disable snapshotting, resulting in
-                                        slower startup).
-    --[no-]sound-null-safety            Override the default null safety
-                                        execution mode.
--C, --directory=<dir>                   Run this in the directory<dir>.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
-''', exitCode: exit_codes.USAGE);
+    await runPub(
+      args: ['run', 'foo:sub/dir'],
+      error: contains(
+        'Cannot run an executable in a subdirectory of a dependency.',
+      ),
+      exitCode: exit_codes.USAGE,
+    );
   });
 }
diff --git a/test/run/forwards_signal_posix_test.dart b/test/run/forwards_signal_posix_test.dart
index 17fb0ea..5205145 100644
--- a/test/run/forwards_signal_posix_test.dart
+++ b/test/run/forwards_signal_posix_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 // Windows doesn't support sending signals.
 // TODO(sigurdm): Test this when vm-args are provided.
 // This test doesn't work when we subprocess instead of an isolate
@@ -25,7 +23,7 @@
   ProcessSignal.sigwinch,
 ];
 
-const SCRIPT = """
+const _script = """
 import 'dart:io';
 
 main() {
@@ -43,7 +41,7 @@
   test('forwards signals to the inner script', () async {
     await d.dir(appPath, [
       d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', SCRIPT)])
+      d.dir('bin', [d.file('script.dart', _script)])
     ]).create();
 
     await pubGet();
diff --git a/test/run/includes_parent_directories_of_entrypoint_test.dart b/test/run/includes_parent_directories_of_entrypoint_test.dart
index f17b548..6bc4e27 100644
--- a/test/run/includes_parent_directories_of_entrypoint_test.dart
+++ b/test/run/includes_parent_directories_of_entrypoint_test.dart
@@ -2,15 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-const SCRIPT = r"""
+const _script = r"""
 import '../../a.dart';
 import '../b.dart';
 main() {
@@ -28,7 +26,7 @@
         d.file('a.dart', "var a = 'a';"),
         d.dir('a', [
           d.file('b.dart', "var b = 'b';"),
-          d.dir('b', [d.file('app.dart', SCRIPT)])
+          d.dir('b', [d.file('app.dart', _script)])
         ])
       ])
     ]).create();
diff --git a/test/run/loads_package_imports_in_a_dependency_test.dart b/test/run/loads_package_imports_in_a_dependency_test.dart
index 66c261d..7bfb047 100644
--- a/test/run/loads_package_imports_in_a_dependency_test.dart
+++ b/test/run/loads_package_imports_in_a_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/nonexistent_dependency_test.dart b/test/run/nonexistent_dependency_test.dart
index 5b0f941..2051149 100644
--- a/test/run/nonexistent_dependency_test.dart
+++ b/test/run/nonexistent_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/run/nonexistent_script_in_dependency_test.dart b/test/run/nonexistent_script_in_dependency_test.dart
index 5153c25..62816df 100644
--- a/test/run/nonexistent_script_in_dependency_test.dart
+++ b/test/run/nonexistent_script_in_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
diff --git a/test/run/nonexistent_script_test.dart b/test/run/nonexistent_script_test.dart
index 9930430..0e12ee1 100644
--- a/test/run/nonexistent_script_test.dart
+++ b/test/run/nonexistent_script_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
diff --git a/test/run/package_api_test.dart b/test/run/package_api_test.dart
index e7b814f..4d05408 100644
--- a/test/run/package_api_test.dart
+++ b/test/run/package_api_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
@@ -51,11 +49,10 @@
   });
 
   test('a snapshotted application sees a file: package root', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', contents: [
-        d.dir('bin', [d.file('script.dart', _script)])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', contents: [
+      d.dir('bin', [d.file('script.dart', _script)])
+    ]);
 
     await d.dir(appPath, [
       d.appPubspec({'foo': 'any'})
@@ -75,8 +72,8 @@
             .toString()));
     expect(pub.stdout,
         emits(p.toUri(p.join(d.sandbox, 'myapp/lib/resource.txt')).toString()));
-    var fooResourcePath = p.join(
-        globalPackageServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
+    var fooResourcePath =
+        p.join(globalServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(fooResourcePath).toString()));
     await pub.shouldExit(0);
   });
diff --git a/test/run/passes_along_arguments_test.dart b/test/run/passes_along_arguments_test.dart
index d2beebf..624d9f2 100644
--- a/test/run/passes_along_arguments_test.dart
+++ b/test/run/passes_along_arguments_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-const SCRIPT = '''
+const _script = '''
 main(List<String> args) {
   print(args.join(" "));
 }
@@ -19,7 +17,7 @@
   test('passes arguments to the spawned script', () async {
     await d.dir(appPath, [
       d.appPubspec(),
-      d.dir('bin', [d.file('args.dart', SCRIPT)])
+      d.dir('bin', [d.file('args.dart', _script)])
     ]).create();
 
     await pubGet();
diff --git a/test/run/precompile_test.dart b/test/run/precompile_test.dart
index ac663d5..2d70b4c 100644
--- a/test/run/precompile_test.dart
+++ b/test/run/precompile_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-const SCRIPT = r'''
+const _script = r'''
 import 'dart:io';
 
 main(List<String> args) {
@@ -23,11 +21,11 @@
       d.appPubspec({'test': '1.0.0'}),
     ]).create();
 
-    await servePackages((server) => server
-      ..serve('test', '1.0.0', contents: [
-        d.dir('bin',
-            [d.file('test.dart', 'main(List<String> args) => print("hello");')])
-      ]));
+    final server = await servePackages();
+    server.serve('test', '1.0.0', contents: [
+      d.dir('bin',
+          [d.file('test.dart', 'main(List<String> args) => print("hello");')])
+    ]);
 
     await pubGet(args: ['--no-precompile']);
   }
@@ -59,10 +57,10 @@
       d.appPubspec({'test': '1.0.0'}),
     ]).create();
 
-    await servePackages((server) => server
-      ..serve('test', '1.0.0', contents: [
-        d.dir('bin', [d.file('test.dart', SCRIPT)])
-      ]));
+    final server = await servePackages();
+    server.serve('test', '1.0.0', contents: [
+      d.dir('bin', [d.file('test.dart', _script)])
+    ]);
 
     await pubGet(
         args: ['--no-precompile'], environment: {'PUB_CACHE': '.pub_cache'});
@@ -82,10 +80,10 @@
       d.appPubspec({'test': '1.0.0'}),
     ]).create();
 
-    await servePackages((server) => server
-      ..serve('test', '1.0.0', contents: [
-        d.dir('bin', [d.file('test.dart', SCRIPT)])
-      ]));
+    final server = await servePackages();
+    server.serve('test', '1.0.0', contents: [
+      d.dir('bin', [d.file('test.dart', _script)])
+    ]);
 
     await pubGet(
         args: ['--precompile'],
@@ -106,10 +104,10 @@
       d.appPubspec({'test': '1.0.0'}),
     ]).create();
 
-    await servePackages((server) => server
-      ..serve('test', '1.0.0', contents: [
-        d.dir('bin', [d.file('test.dart', SCRIPT)])
-      ]));
+    final server = await servePackages();
+    server.serve('test', '1.0.0', contents: [
+      d.dir('bin', [d.file('test.dart', _script)])
+    ]);
 
     await pubGet(
         args: ['--precompile'],
diff --git a/test/run/runs_app_in_directory_in_entrypoint_test.dart b/test/run/runs_app_in_directory_in_entrypoint_test.dart
index ef720c5..ac8af0d 100644
--- a/test/run/runs_app_in_directory_in_entrypoint_test.dart
+++ b/test/run/runs_app_in_directory_in_entrypoint_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
diff --git a/test/run/runs_app_in_entrypoint_test.dart b/test/run/runs_app_in_entrypoint_test.dart
index 3f3ed38..6cb932a 100644
--- a/test/run/runs_app_in_entrypoint_test.dart
+++ b/test/run/runs_app_in_entrypoint_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-const SCRIPT = """
+const _script = """
 import 'dart:io';
 
 main() {
@@ -23,7 +21,7 @@
   test('runs a Dart application in the entrypoint package', () async {
     await d.dir(appPath, [
       d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', SCRIPT)])
+      d.dir('bin', [d.file('script.dart', _script)])
     ]).create();
 
     await pubGet();
diff --git a/test/run/runs_from_a_dependency_override_after_dependency_test.dart b/test/run/runs_from_a_dependency_override_after_dependency_test.dart
index c3cde77..c11bacb 100644
--- a/test/run/runs_from_a_dependency_override_after_dependency_test.dart
+++ b/test/run/runs_from_a_dependency_override_after_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -12,14 +10,13 @@
 void main() {
   // Regression test for issue 23113
   test('runs a named Dart application in a dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'name': 'foo',
-        'version': '1.0.0'
-      }, contents: [
-        d.dir('bin', [d.file('bar.dart', "main() => print('foobar');")])
-      ]);
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'name': 'foo',
+      'version': '1.0.0'
+    }, contents: [
+      d.dir('bin', [d.file('bar.dart', "main() => print('foobar');")])
+    ]);
 
     await d.dir(appPath, [
       d.appPubspec({'foo': null})
diff --git a/test/run/runs_named_app_in_dependency_test.dart b/test/run/runs_named_app_in_dependency_test.dart
index d554eb4..8f3d73d 100644
--- a/test/run/runs_named_app_in_dependency_test.dart
+++ b/test/run/runs_named_app_in_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/runs_named_app_in_dev_dependency_test.dart b/test/run/runs_named_app_in_dev_dependency_test.dart
index f1493d4..287f05b 100644
--- a/test/run/runs_named_app_in_dev_dependency_test.dart
+++ b/test/run/runs_named_app_in_dev_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/runs_shorthand_app_in_dependency_test.dart b/test/run/runs_shorthand_app_in_dependency_test.dart
index cd3c66a..455e049 100644
--- a/test/run/runs_shorthand_app_in_dependency_test.dart
+++ b/test/run/runs_shorthand_app_in_dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/runs_the_script_in_checked_mode_test.dart b/test/run/runs_the_script_in_checked_mode_test.dart
index 8b8e214..8b4010a 100644
--- a/test/run/runs_the_script_in_checked_mode_test.dart
+++ b/test/run/runs_the_script_in_checked_mode_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
diff --git a/test/run/runs_the_script_in_unchecked_mode_test.dart b/test/run/runs_the_script_in_unchecked_mode_test.dart
index da86989..026a748 100644
--- a/test/run/runs_the_script_in_unchecked_mode_test.dart
+++ b/test/run/runs_the_script_in_unchecked_mode_test.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-const SCRIPT = '''
+const _script = '''
 main() {
   assert(false);
   print("no checks");
@@ -20,7 +18,7 @@
   test('runs the script without assertions by default', () async {
     await d.dir(appPath, [
       d.appPubspec(),
-      d.dir('bin', [d.file('script.dart', SCRIPT)])
+      d.dir('bin', [d.file('script.dart', _script)])
     ]).create();
 
     await pubGet();
diff --git a/test/sdk_test.dart b/test/sdk_test.dart
index 93ebdac..6165cd5 100644
--- a/test/sdk_test.dart
+++ b/test/sdk_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
@@ -15,9 +13,8 @@
 void main() {
   forBothPubGetAndUpgrade((command) {
     setUp(() async {
-      await servePackages((builder) {
-        builder.serve('bar', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('bar', '1.0.0');
 
       await d.dir('flutter', [
         d.dir('packages', [
@@ -40,13 +37,10 @@
       }).create();
       await pubCommand(command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
-
-      await d.dir(appPath, [
-        d.packagesFile({
-          'myapp': '.',
-          'foo': p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-          'bar': '1.0.0'
-        })
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(
+            name: 'foo', path: p.join(d.sandbox, 'flutter', 'packages', 'foo')),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
       ]).validate();
     });
 
@@ -57,11 +51,10 @@
       await pubCommand(command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
 
-      await d.dir(appPath, [
-        d.packagesFile({
-          'myapp': '.',
-          'baz': p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz')
-        })
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(
+            name: 'baz',
+            path: p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz')),
       ]).validate();
     });
 
@@ -96,10 +89,7 @@
       deleteEntry(p.join(d.sandbox, 'flutter', 'version'));
       await pubCommand(command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
-
-      await d.dir(appPath, [
-        d.packagesFile({'myapp': '.'})
-      ]).validate();
+      await d.appPackageConfigFile([]).validate();
     });
 
     group('fails if', () {
@@ -120,7 +110,7 @@
           'foo': {'sdk': 'unknown'}
         }).create();
         await pubCommand(command, error: equalsIgnoringWhitespace("""
-              Because myapp depends on foo any from sdk which doesn't exist
+              Because myapp depends on foo from sdk which doesn't exist
                 (unknown SDK "unknown"), version solving failed.
             """), exitCode: exit_codes.UNAVAILABLE);
       });
@@ -130,7 +120,7 @@
           'foo': {'sdk': 'flutter'}
         }).create();
         await pubCommand(command, error: equalsIgnoringWhitespace("""
-              Because myapp depends on foo any from sdk which doesn't exist (the
+              Because myapp depends on foo from sdk which doesn't exist (the
                 Flutter SDK is not available), version solving failed.
 
               Flutter users should run `flutter pub get` instead of `dart pub
@@ -145,7 +135,7 @@
         await pubCommand(command,
             environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
             error: equalsIgnoringWhitespace("""
-              Because myapp depends on bar any from sdk which doesn't exist
+              Because myapp depends on bar from sdk which doesn't exist
                 (could not find package bar in the Flutter SDK), version solving
                 failed.
             """),
@@ -157,7 +147,7 @@
           'bar': {'sdk': 'dart'}
         }).create();
         await pubCommand(command, error: equalsIgnoringWhitespace("""
-              Because myapp depends on bar any from sdk which doesn't exist
+              Because myapp depends on bar from sdk which doesn't exist
                 (could not find package bar in the Dart SDK), version solving
                 failed.
             """), exitCode: exit_codes.UNAVAILABLE);
@@ -172,13 +162,10 @@
       }).create();
       await pubCommand(command,
           environment: {'FUCHSIA_DART_SDK_ROOT': p.join(d.sandbox, 'fuchsia')});
-
-      await d.dir(appPath, [
-        d.packagesFile({
-          'myapp': '.',
-          'foo': p.join(d.sandbox, 'fuchsia', 'packages', 'foo'),
-          'bar': '1.0.0'
-        })
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(
+            name: 'foo', path: p.join(d.sandbox, 'fuchsia', 'packages', 'foo')),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
       ]).validate();
     });
   });
diff --git a/test/snapshot_test.dart b/test/snapshot_test.dart
index 6ad44f6..8143f7e 100644
--- a/test/snapshot_test.dart
+++ b/test/snapshot_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
@@ -13,17 +11,15 @@
 void main() {
   group('creates a snapshot', () {
     test('for an immediate dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3', contents: [
-          d.dir('bin', [
-            d.file('hello.dart', "void main() => print('hello!');"),
-            d.file('goodbye.dart', "void main() => print('goodbye!');"),
-            d.file('shell.sh', 'echo shell'),
-            d.dir(
-                'subdir', [d.file('sub.dart', "void main() => print('sub!');")])
-          ])
-        ]);
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.2.3', contents: [
+        d.dir('bin', [
+          d.file('hello.dart', "void main() => print('hello!');"),
+          d.file('goodbye.dart', "void main() => print('goodbye!');"),
+          d.file('shell.sh', 'echo shell'),
+          d.dir('subdir', [d.file('sub.dart', "void main() => print('sub!');")])
+        ])
+      ]);
 
       await d.appDir({'foo': '1.2.3'}).create();
 
@@ -51,8 +47,8 @@
     });
 
     test("for an immediate dependency that's also transitive", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.2.3', contents: [
+      await servePackages()
+        ..serve('foo', '1.2.3', contents: [
           d.dir('bin', [
             d.file('hello.dart', "void main() => print('hello!');"),
             d.file('goodbye.dart', "void main() => print('goodbye!');"),
@@ -60,9 +56,8 @@
             d.dir(
                 'subdir', [d.file('sub.dart', "void main() => print('sub!');")])
           ])
-        ]);
-        builder.serve('bar', '1.2.3', deps: {'foo': '1.2.3'});
-      });
+        ])
+        ..serve('bar', '1.2.3', deps: {'foo': '1.2.3'});
 
       await d.appDir({'foo': '1.2.3'}).create();
 
@@ -91,12 +86,11 @@
 
     group('again if', () {
       test('its package is updated', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3', contents: [
-            d.dir('bin',
-                [d.file('hello.dart', "void main() => print('hello!');")])
-          ]);
-        });
+        final server = await servePackages();
+        server.serve('foo', '1.2.3', contents: [
+          d.dir(
+              'bin', [d.file('hello.dart', "void main() => print('hello!');")])
+        ]);
 
         await d.appDir({'foo': 'any'}).create();
 
@@ -107,12 +101,10 @@
           d.file('hello.dart-$versionSuffix.snapshot', contains('hello!'))
         ]).validate();
 
-        globalPackageServer.add((builder) {
-          builder.serve('foo', '1.2.4', contents: [
-            d.dir('bin',
-                [d.file('hello.dart', "void main() => print('hello 2!');")])
-          ]);
-        });
+        server.serve('foo', '1.2.4', contents: [
+          d.dir('bin',
+              [d.file('hello.dart', "void main() => print('hello 2!');")])
+        ]);
 
         await pubUpgrade(
             args: ['--precompile'], output: contains('Built foo:hello.'));
@@ -127,22 +119,22 @@
       });
 
       test('a dependency of its package is updated', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.2.3', pubspec: {
-            'dependencies': {'bar': 'any'}
-          }, contents: [
-            d.dir('bin', [
-              d.file('hello.dart', """
+        final server = await servePackages();
+
+        server.serve('foo', '1.2.3', pubspec: {
+          'dependencies': {'bar': 'any'}
+        }, contents: [
+          d.dir('bin', [
+            d.file('hello.dart', """
             import 'package:bar/bar.dart';
 
             void main() => print(message);
           """)
-            ])
-          ]);
-          builder.serve('bar', '1.2.3', contents: [
-            d.dir('lib', [d.file('bar.dart', "final message = 'hello!';")])
-          ]);
-        });
+          ])
+        ]);
+        server.serve('bar', '1.2.3', contents: [
+          d.dir('lib', [d.file('bar.dart', "final message = 'hello!';")])
+        ]);
 
         await d.appDir({'foo': 'any'}).create();
 
@@ -153,11 +145,9 @@
           d.file('hello.dart-$versionSuffix.snapshot', contains('hello!'))
         ]).validate();
 
-        globalPackageServer.add((builder) {
-          builder.serve('bar', '1.2.4', contents: [
-            d.dir('lib', [d.file('bar.dart', "final message = 'hello 2!';")]),
-          ]);
-        });
+        server.serve('bar', '1.2.4', contents: [
+          d.dir('lib', [d.file('bar.dart', "final message = 'hello 2!';")]),
+        ]);
 
         await pubUpgrade(
             args: ['--precompile'], output: contains('Built foo:hello.'));
@@ -209,12 +199,11 @@
       });
 
       test('the SDK is out of date', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '5.6.7', contents: [
-            d.dir('bin',
-                [d.file('hello.dart', "void main() => print('hello!');")])
-          ]);
-        });
+        final server = await servePackages();
+        server.serve('foo', '5.6.7', contents: [
+          d.dir(
+              'bin', [d.file('hello.dart', "void main() => print('hello!');")])
+        ]);
 
         await d.appDir({'foo': '5.6.7'}).create();
 
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 1351914..be2825e 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -2,15 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 /// Test infrastructure for testing pub.
 ///
 /// Unlike typical unit tests, most pub tests are integration tests that stage
 /// some stuff on the file system, run pub, and then validate the results. This
 /// library provides an API to build tests like that.
-import 'dart:async';
 import 'dart:convert';
+import 'dart:core';
 import 'dart:io';
 import 'dart:isolate';
 import 'dart:math';
@@ -28,7 +26,6 @@
 import 'package:pub/src/io.dart';
 import 'package:pub/src/lock_file.dart';
 import 'package:pub/src/log.dart' as log;
-import 'package:pub/src/sdk.dart';
 import 'package:pub/src/source_registry.dart';
 import 'package:pub/src/system_cache.dart';
 import 'package:pub/src/utils.dart';
@@ -39,11 +36,9 @@
 import 'package:test_process/test_process.dart';
 
 import 'descriptor.dart' as d;
-import 'descriptor_server.dart';
 import 'package_server.dart';
 
-export 'descriptor_server.dart';
-export 'package_server.dart';
+export 'package_server.dart' show PackageServer;
 
 /// A [Matcher] that matches JavaScript generated by dart2js with minification
 /// enabled.
@@ -82,7 +77,7 @@
         orElse: () => null) as Map<String, dynamic>;
 
 /// The suffix appended to a built snapshot.
-final versionSuffix = testVersion ?? sdk.version;
+final versionSuffix = testVersion;
 
 /// Enum identifying a pub command that can be run with a well-defined success
 /// output.
@@ -130,14 +125,15 @@
 // TODO(rnystrom): Clean up other tests to call this when possible.
 Future<void> pubCommand(
   RunCommand command, {
-  Iterable<String> args,
-  output,
-  error,
-  silent,
-  warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  Iterable<String>? args,
+  Object? output,
+  Object? error,
+  Object? silent,
+  Object? warning,
+  int? exitCode,
+  Map<String, String?>? environment,
+  String? workingDirectory,
+  includeParentEnvironment = true,
 }) async {
   if (error != null && warning != null) {
     throw ArgumentError("Cannot pass both 'error' and 'warning'.");
@@ -161,17 +157,18 @@
       silent: silent,
       exitCode: exitCode,
       environment: environment,
-      workingDirectory: workingDirectory);
+      workingDirectory: workingDirectory,
+      includeParentEnvironment: includeParentEnvironment);
 }
 
 Future<void> pubAdd({
-  Iterable<String> args,
-  output,
-  error,
-  warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  Iterable<String>? args,
+  Object? output,
+  Object? error,
+  Object? warning,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async =>
     await pubCommand(
       RunCommand.add,
@@ -185,13 +182,14 @@
     );
 
 Future<void> pubGet({
-  Iterable<String> args,
-  output,
-  error,
-  warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  Iterable<String>? args,
+  Object? output,
+  Object? error,
+  Object? warning,
+  int? exitCode,
+  Map<String, String?>? environment,
+  String? workingDirectory,
+  bool includeParentEnvironment = true,
 }) async =>
     await pubCommand(
       RunCommand.get,
@@ -202,16 +200,17 @@
       exitCode: exitCode,
       environment: environment,
       workingDirectory: workingDirectory,
+      includeParentEnvironment: includeParentEnvironment,
     );
 
 Future<void> pubUpgrade(
-        {Iterable<String> args,
-        output,
-        error,
-        warning,
-        int exitCode,
-        Map<String, String> environment,
-        String workingDirectory}) async =>
+        {Iterable<String>? args,
+        Object? output,
+        Object? error,
+        Object? warning,
+        int? exitCode,
+        Map<String, String>? environment,
+        String? workingDirectory}) async =>
     await pubCommand(
       RunCommand.upgrade,
       args: args,
@@ -224,13 +223,13 @@
     );
 
 Future<void> pubDowngrade({
-  Iterable<String> args,
-  output,
-  error,
-  warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  Iterable<String>? args,
+  Object? output,
+  Object? error,
+  Object? warning,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async =>
     await pubCommand(
       RunCommand.downgrade,
@@ -244,13 +243,13 @@
     );
 
 Future<void> pubRemove({
-  Iterable<String> args,
-  output,
-  error,
-  warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  Iterable<String>? args,
+  Object? output,
+  Object? error,
+  Object? warning,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async =>
     await pubCommand(
       RunCommand.remove,
@@ -272,8 +271,8 @@
 /// Returns the `pub run` process.
 Future<PubProcess> pubRun(
     {bool global = false,
-    Iterable<String> args,
-    Map<String, String> environment,
+    required Iterable<String> args,
+    Map<String, String>? environment,
     bool verbose = true}) async {
   var pubArgs = global ? ['global', 'run'] : ['run'];
   pubArgs.addAll(args);
@@ -290,20 +289,6 @@
   return pub;
 }
 
-/// Schedules starting the "pub run --v2" process and validates the
-/// expected startup output.
-///
-/// Returns the `pub run` process.
-Future<PubProcess> pubRunFromDartDev({Iterable<String> args}) async {
-  final pub = await startPub(args: ['run', '--dart-dev-run', ...args]);
-
-  // Loading sources and transformers isn't normally printed, but the pub test
-  // infrastructure runs pub in verbose mode, which enables this.
-  expect(pub.stdout, mayEmitMultiple(startsWith('Loading')));
-
-  return pub;
-}
-
 /// Schedules renaming (moving) the directory at [from] to [to], both of which
 /// are assumed to be relative to [d.sandbox].
 void renameInSandbox(String from, String to) {
@@ -330,23 +315,27 @@
 ///
 /// If [environment] is given, any keys in it will override the environment
 /// variables passed to the spawned process.
-Future<void> runPub({
-  List<String> args,
-  output,
-  error,
-  outputJson,
-  silent,
-  int exitCode,
-  String workingDirectory,
-  Map<String, String> environment,
-  List<String> input,
-}) async {
+Future<void> runPub(
+    {List<String>? args,
+    Object? output,
+    Object? error,
+    Object? outputJson,
+    Object? silent,
+    int? exitCode,
+    String? workingDirectory,
+    Map<String, String?>? environment,
+    List<String>? input,
+    includeParentEnvironment = true}) async {
   exitCode ??= exit_codes.SUCCESS;
   // Cannot pass both output and outputJson.
   assert(output == null || outputJson == null);
 
   var pub = await startPub(
-      args: args, workingDirectory: workingDirectory, environment: environment);
+    args: args,
+    workingDirectory: workingDirectory,
+    environment: environment,
+    includeParentEnvironment: includeParentEnvironment,
+  );
 
   if (input != null) {
     input.forEach(pub.stdin.writeln);
@@ -379,14 +368,20 @@
 /// package server.
 ///
 /// Any futures in [args] will be resolved before the process is started.
-Future<PubProcess> startPublish(PackageServer server,
-    {List<String> args}) async {
+Future<PubProcess> startPublish(
+  PackageServer server, {
+  List<String>? args,
+  String authMethod = 'oauth2',
+  Map<String, String>? environment,
+  String path = '',
+}) async {
   var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
   args = ['lish', ...?args];
-  return await startPub(
-      args: args,
-      tokenEndpoint: tokenEndpoint,
-      environment: {'PUB_HOSTED_URL': server.url});
+  return await startPub(args: args, tokenEndpoint: tokenEndpoint, environment: {
+    'PUB_HOSTED_URL': server.url + path,
+    '_PUB_TEST_AUTH_METHOD': authMethod,
+    if (environment != null) ...environment,
+  });
 }
 
 /// Handles the beginning confirmation process for uploading a packages.
@@ -416,28 +411,19 @@
 String testVersion = '0.1.2+3';
 
 /// Gets the environment variables used to run pub in a test context.
-Map<String, String> getPubTestEnvironment([String tokenEndpoint]) {
-  var environment = {
-    'CI': 'false', // unless explicitly given tests don't run pub in CI mode
-    '_PUB_TESTING': 'true',
-    '_PUB_TEST_CONFIG_DIR': _pathInSandbox(configPath),
-    'PUB_CACHE': _pathInSandbox(cachePath),
-    'PUB_ENVIRONMENT': 'test-environment',
+Map<String, String> getPubTestEnvironment([String? tokenEndpoint]) => {
+      'CI': 'false', // unless explicitly given tests don't run pub in CI mode
+      '_PUB_TESTING': 'true',
+      '_PUB_TEST_CONFIG_DIR': _pathInSandbox(configPath),
+      'PUB_CACHE': _pathInSandbox(cachePath),
+      'PUB_ENVIRONMENT': 'test-environment',
 
-    // Ensure a known SDK version is set for the tests that rely on that.
-    '_PUB_TEST_SDK_VERSION': testVersion
-  };
-
-  if (tokenEndpoint != null) {
-    environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint;
-  }
-
-  if (globalServer != null) {
-    environment['PUB_HOSTED_URL'] = 'http://localhost:${globalServer.port}';
-  }
-
-  return environment;
-}
+      // Ensure a known SDK version is set for the tests that rely on that.
+      '_PUB_TEST_SDK_VERSION': testVersion,
+      if (tokenEndpoint != null) '_PUB_TEST_TOKEN_ENDPOINT': tokenEndpoint,
+      if (_globalServer?.port != null)
+        'PUB_HOSTED_URL': 'http://localhost:${_globalServer?.port}'
+    };
 
 /// The path to the root of pub's sources in the pub repo.
 final String _pubRoot = (() {
@@ -456,25 +442,16 @@
 /// If [environment] is given, any keys in it will override the environment
 /// variables passed to the spawned process.
 Future<PubProcess> startPub(
-    {Iterable<String> args,
-    String tokenEndpoint,
-    String workingDirectory,
-    Map<String, String> environment,
-    bool verbose = true}) async {
+    {Iterable<String>? args,
+    String? tokenEndpoint,
+    String? workingDirectory,
+    Map<String, String?>? environment,
+    bool verbose = true,
+    includeParentEnvironment = true}) async {
   args ??= [];
 
   ensureDir(_pathInSandbox(appPath));
 
-  // Find a Dart executable we can use to spawn. Use the same one that was
-  // used to run this script itself.
-  var dartBin = Platform.executable;
-
-  // If the executable looks like a path, get its full path. That way we
-  // can still find it when we spawn it with a different working directory.
-  if (dartBin.contains(Platform.pathSeparator)) {
-    dartBin = p.absolute(dartBin);
-  }
-
   // If there's a snapshot for "pub" available we use it. If the snapshot is
   // out-of-date local source the tests will be useless, therefore it is
   // recommended to use a temporary file with a unique name for each test run.
@@ -489,37 +466,47 @@
 
   var dartArgs = ['--packages=$dotPackagesPath', '--enable-asserts'];
   dartArgs
-    ..addAll([pubPath, if (verbose) '--verbose'])
+    ..addAll([pubPath, if (!verbose) '--verbosity=normal'])
     ..addAll(args);
 
-  return await PubProcess.start(dartBin, dartArgs,
-      environment: getPubTestEnvironment(tokenEndpoint)
-        ..addAll(environment ?? {}),
+  final mergedEnvironment = getPubTestEnvironment(tokenEndpoint);
+  for (final e in (environment ?? {}).entries) {
+    var value = e.value;
+    if (value == null) {
+      mergedEnvironment.remove(e.key);
+    } else {
+      mergedEnvironment[e.key] = value;
+    }
+  }
+
+  return await PubProcess.start(Platform.resolvedExecutable, dartArgs,
+      environment: mergedEnvironment,
       workingDirectory: workingDirectory ?? _pathInSandbox(appPath),
-      description: args.isEmpty ? 'pub' : 'pub ${args.first}');
+      description: args.isEmpty ? 'pub' : 'pub ${args.first}',
+      includeParentEnvironment: includeParentEnvironment);
 }
 
 /// A subclass of [TestProcess] that parses pub's verbose logging output and
 /// makes [stdout] and [stderr] work as though pub weren't running in verbose
 /// mode.
 class PubProcess extends TestProcess {
-  StreamSplitter<Pair<log.Level, String>> get _logSplitter {
-    __logSplitter ??= StreamSplitter(StreamGroup.merge([
-      _outputToLog(super.stdoutStream(), log.Level.MESSAGE),
-      _outputToLog(super.stderrStream(), log.Level.ERROR)
+  late final StreamSplitter<Pair<log.Level, String>> _logSplitter =
+      createLogSplitter();
+
+  StreamSplitter<Pair<log.Level, String>> createLogSplitter() {
+    return StreamSplitter(StreamGroup.merge([
+      _outputToLog(super.stdoutStream(), log.Level.message),
+      _outputToLog(super.stderrStream(), log.Level.error)
     ]));
-    return __logSplitter;
   }
 
-  StreamSplitter<Pair<log.Level, String>> __logSplitter;
-
   static Future<PubProcess> start(String executable, Iterable<String> arguments,
-      {String workingDirectory,
-      Map<String, String> environment,
+      {String? workingDirectory,
+      Map<String, String>? environment,
       bool includeParentEnvironment = true,
       bool runInShell = false,
-      String description,
-      Encoding encoding,
+      String? description,
+      Encoding encoding = utf8,
       bool forwardStdio = false}) async {
     var process = await Process.start(executable, arguments.toList(),
         workingDirectory: workingDirectory,
@@ -534,25 +521,24 @@
       description = '$humanExecutable ${arguments.join(' ')}';
     }
 
-    encoding ??= utf8;
     return PubProcess(process, description,
         encoding: encoding, forwardStdio: forwardStdio);
   }
 
   /// This is protected.
   PubProcess(process, description,
-      {Encoding encoding, bool forwardStdio = false})
+      {Encoding encoding = utf8, bool forwardStdio = false})
       : super(process, description,
             encoding: encoding, forwardStdio: forwardStdio);
 
   final _logLineRegExp = RegExp(r'^([A-Z ]{4})[:|] (.*)$');
   final Map<String, log.Level> _logLevels = [
-    log.Level.ERROR,
-    log.Level.WARNING,
-    log.Level.MESSAGE,
-    log.Level.IO,
-    log.Level.SOLVER,
-    log.Level.FINE
+    log.Level.error,
+    log.Level.warning,
+    log.Level.message,
+    log.Level.io,
+    log.Level.solver,
+    log.Level.fine
   ].fold({}, (levels, level) {
     levels[level.name] = level;
     return levels;
@@ -560,21 +546,21 @@
 
   Stream<Pair<log.Level, String>> _outputToLog(
       Stream<String> stream, log.Level defaultLevel) {
-    log.Level lastLevel;
+    late log.Level lastLevel;
     return stream.map((line) {
       var match = _logLineRegExp.firstMatch(line);
       if (match == null) return Pair<log.Level, String>(defaultLevel, line);
 
       var level = _logLevels[match[1]] ?? lastLevel;
       lastLevel = level;
-      return Pair<log.Level, String>(level, match[2]);
+      return Pair<log.Level, String>(level, match[2]!);
     });
   }
 
   @override
   Stream<String> stdoutStream() {
     return _logSplitter.split().expand((entry) {
-      if (entry.first != log.Level.MESSAGE) return [];
+      if (entry.first != log.Level.message) return [];
       return [entry.last];
     });
   }
@@ -582,7 +568,7 @@
   @override
   Stream<String> stderrStream() {
     return _logSplitter.split().expand((entry) {
-      if (entry.first != log.Level.ERROR && entry.first != log.Level.WARNING) {
+      if (entry.first != log.Level.error && entry.first != log.Level.warning) {
         return [];
       }
       return [entry.last];
@@ -592,9 +578,9 @@
   /// A stream of log messages that are silent by default.
   Stream<String> silentStream() {
     return _logSplitter.split().expand((entry) {
-      if (entry.first == log.Level.MESSAGE) return [];
-      if (entry.first == log.Level.ERROR) return [];
-      if (entry.first == log.Level.WARNING) return [];
+      if (entry.first == log.Level.message) return [];
+      if (entry.first == log.Level.error) return [];
+      if (entry.first == log.Level.warning) return [];
       return [entry.last];
     });
   }
@@ -617,15 +603,15 @@
 /// [hosted] is a list of package names to version strings for dependencies on
 /// hosted packages.
 Future<void> createLockFile(String package,
-    {Iterable<String> dependenciesInSandBox,
-    Map<String, String> hosted}) async {
+    {Iterable<String>? dependenciesInSandBox,
+    Map<String, String>? hosted}) async {
   var cache = SystemCache(rootDir: _pathInSandbox(cachePath));
 
   var lockFile = _createLockFile(cache.sources,
       sandbox: dependenciesInSandBox, hosted: hosted);
 
   await d.dir(package, [
-    d.file('pubspec.lock', lockFile.serialize(null)),
+    d.file('pubspec.lock', lockFile.serialize(p.join(d.sandbox, package))),
     d.file(
       '.packages',
       lockFile.packagesFile(
@@ -640,8 +626,8 @@
 /// Like [createLockFile], but creates only a `.packages` file without a
 /// lockfile.
 Future<void> createPackagesFile(String package,
-    {Iterable<String> dependenciesInSandBox,
-    Map<String, String> hosted}) async {
+    {Iterable<String>? dependenciesInSandBox,
+    Map<String, String>? hosted}) async {
   var cache = SystemCache(rootDir: _pathInSandbox(cachePath));
   var lockFile = _createLockFile(cache.sources,
       sandbox: dependenciesInSandBox, hosted: hosted);
@@ -666,7 +652,7 @@
 /// [hosted] is a list of package names to version strings for dependencies on
 /// hosted packages.
 LockFile _createLockFile(SourceRegistry sources,
-    {Iterable<String> sandbox, Map<String, String> hosted}) {
+    {Iterable<String>? sandbox, Map<String, String>? hosted}) {
   var dependencies = {};
 
   if (sandbox != null) {
@@ -677,7 +663,9 @@
 
   var packages = dependencies.keys.map((name) {
     var dependencyPath = dependencies[name];
-    return sources.path.idFor(name, Version(0, 0, 0), dependencyPath);
+    return sources.path.parseId(
+        name, Version(0, 0, 0), {'path': dependencyPath, 'relative': true},
+        containingPath: p.join(d.sandbox, appPath));
   }).toList();
 
   if (hosted != null) {
@@ -704,14 +692,14 @@
 
 /// Describes a map representing a library package with the given [name],
 /// [version], and [dependencies].
-Map packageMap(
+Map<String, Object> packageMap(
   String name,
   String version, [
-  Map dependencies,
-  Map devDependencies,
-  Map environment,
+  Map? dependencies,
+  Map? devDependencies,
+  Map? environment,
 ]) {
-  var package = <String, dynamic>{
+  var package = <String, Object>{
     'name': name,
     'version': version,
     'homepage': 'http://pub.dartlang.org',
@@ -833,7 +821,7 @@
 /// which may be a literal JSON object, or any other [Matcher].
 void _validateOutputJson(
     List<String> failures, String pipe, expected, String actualText) {
-  Map actual;
+  late Map actual;
   try {
     actual = jsonDecode(actualText);
   } on FormatException {
@@ -914,8 +902,9 @@
     line = line
         .replaceAll(d.sandbox, r'$SANDBOX')
         .replaceAll(Platform.pathSeparator, '/');
-    if (globalPackageServer != null) {
-      line = line.replaceAll(globalPackageServer.port.toString(), '\$PORT');
+    var port = _globalServer?.port;
+    if (port != null) {
+      line = line.replaceAll(port.toString(), '\$PORT');
     }
     return line;
   });
@@ -925,9 +914,9 @@
 Future<void> runPubIntoBuffer(
   List<String> args,
   StringBuffer buffer, {
-  Map<String, String> environment,
-  String workingDirectory,
-  String stdin,
+  Map<String, String>? environment,
+  String? workingDirectory,
+  String? stdin,
 }) async {
   final process = await startPub(
     args: args,
@@ -941,15 +930,49 @@
   }
   final exitCode = await process.exitCode;
 
+  // TODO(jonasfj): Clean out temporary directory names from env vars...
+  // if (workingDirectory != null) {
+  //   buffer.writeln('\$ cd $workingDirectory');
+  // }
+  // if (environment != null && environment.isNotEmpty) {
+  //   buffer.writeln(environment.entries
+  //       .map((e) => '\$ export ${e.key}=${e.value}')
+  //       .join('\n'));
+  // }
   buffer.writeln(_filter([
     '\$ pub ${args.join(' ')}',
     ...await process.stdout.rest.toList(),
   ]).join('\n'));
   for (final line in _filter(await process.stderr.rest.toList())) {
-    buffer.writeln('[ERR] $line');
+    buffer.writeln('[STDERR] $line');
   }
   if (exitCode != 0) {
-    buffer.writeln('[Exit code] $exitCode');
+    buffer.writeln('[EXIT CODE] $exitCode');
   }
   buffer.write('\n');
 }
+
+/// The current global [PackageServer].
+PackageServer get globalServer => _globalServer!;
+PackageServer? _globalServer;
+
+/// Creates an HTTP server that replicates the structure of pub.dartlang.org and
+/// makes it the current [globalServer].
+Future<PackageServer> servePackages() async {
+  final server = await startPackageServer();
+  _globalServer = server;
+
+  addTearDown(() {
+    _globalServer = null;
+  });
+  return server;
+}
+
+Future<PackageServer> startPackageServer() async {
+  final server = await PackageServer.start();
+
+  addTearDown(() async {
+    await server.close();
+  });
+  return server;
+}
diff --git a/test/testdata/README.md b/test/testdata/README.md
new file mode 100644
index 0000000..ad4a817
--- /dev/null
+++ b/test/testdata/README.md
@@ -0,0 +1,25 @@
+# Test Data
+
+Data used in tests is called _test data_ and is located in this folder, or
+sub-folders thereof. This is not for test files, this folder should not contain
+test code, only data used in tests.
+
+## Golden Test
+
+The `test` wrapper `testWithGolden('<name>', (ctx) async {` will register a
+test case, and create a file:
+  `test/testdata/goldens/path/to/myfile_test/<name>.txt`
+, where `path/to/myfile_test.dart` is the name of the file containing the test
+case, and `<name>` is the name of the test case.
+
+Any calls to `ctx.run` will run `pub` and compare the output to a section in the
+golden file. If the file does not exist, it is created and the
+test is marked as skipped.
+Thus, it is safe to delete all files in `test/testdata/goldens` and recreate
+them -- just carefully review the changes before committing.
+
+**Maintaining goldens**:
+ 1. Delete `test/testdata/goldens/`.
+ 2. Re-run tests to re-create files in `test/testdata/goldens/`.
+ 3. Compare changes, using `git diff test/testdata/goldens/`.
+
diff --git a/test/dependency_services/goldens/dependency_report_adding_transitive.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt
similarity index 83%
rename from test/dependency_services/goldens/dependency_report_adding_transitive.txt
rename to test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt
index 2a7fe2a..659163c 100644
--- a/test/dependency_services/goldens/dependency_report_adding_transitive.txt
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt
@@ -1,4 +1,6 @@
-$ pub __experimental-dependency-services list
+# GENERATED BY: test/dependency_services/dependency_services_test.dart
+
+$ bin/dependency_services.dart list
 {
   "dependencies": [
     {
@@ -10,7 +12,7 @@
   ]
 }
 
-$ pub __experimental-dependency-services report
+$ bin/dependency_services.dart report
 {
   "dependencies": [
     {
@@ -52,10 +54,8 @@
   ]
 }
 
-$ pub __experimental-dependency-services apply
-Resolving dependencies...
-+ transitive 1.0.0
-Would change 1 dependency.
+$ bin/dependency_services.dart apply
+{"dependencies":[]}
 
 myapp/pubspec.yaml:
 {"name":"app","dependencies":{"foo":^2.2.3},"environment":{"sdk":">=0.1.2 <1.0.0"}}
@@ -67,7 +67,7 @@
     dependency: "direct main"
     description:
       name: foo
-      url: "http://localhost:34801"
+      url: "http://localhost:<port>"
     source: hosted
     version: 2.2.3
 sdks:
diff --git a/test/dependency_services/goldens/dependency_report_removing_transitive.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt
similarity index 80%
rename from test/dependency_services/goldens/dependency_report_removing_transitive.txt
rename to test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt
index c9bdee8..8f8cd13 100644
--- a/test/dependency_services/goldens/dependency_report_removing_transitive.txt
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt
@@ -1,4 +1,6 @@
-$ pub __experimental-dependency-services list
+# GENERATED BY: test/dependency_services/dependency_services_test.dart
+
+$ bin/dependency_services.dart list
 {
   "dependencies": [
     {
@@ -16,7 +18,7 @@
   ]
 }
 
-$ pub __experimental-dependency-services report
+$ bin/dependency_services.dart report
 {
   "dependencies": [
     {
@@ -36,7 +38,7 @@
         {
           "name": "transitive",
           "version": null,
-          "kind": null,
+          "kind": "transitive",
           "constraint": null
         }
       ],
@@ -50,7 +52,7 @@
         {
           "name": "transitive",
           "version": null,
-          "kind": null,
+          "kind": "transitive",
           "constraint": null
         }
       ]
@@ -67,11 +69,8 @@
   ]
 }
 
-$ pub __experimental-dependency-services apply
-Resolving dependencies...
-These packages are no longer being depended on:
-- transitive 1.0.0
-Would change 1 dependency.
+$ bin/dependency_services.dart apply
+{"dependencies":[]}
 
 myapp/pubspec.yaml:
 {"name":"app","dependencies":{"foo":^2.2.3},"environment":{"sdk":">=0.1.2 <1.0.0"}}
@@ -83,14 +82,14 @@
     dependency: "direct main"
     description:
       name: foo
-      url: "http://localhost:39343"
+      url: "http://localhost:<port>"
     source: hosted
     version: 2.2.3
   transitive:
     dependency: transitive
     description:
       name: transitive
-      url: "http://localhost:39343"
+      url: "http://localhost:<port>"
     source: hosted
     version: "1.0.0"
 sdks:
diff --git a/test/deps/goldens/formatting.txt b/test/testdata/goldens/deps/executables_test/applies formatting before printing executables.txt
similarity index 71%
rename from test/deps/goldens/formatting.txt
rename to test/testdata/goldens/deps/executables_test/applies formatting before printing executables.txt
index ff5aec9..d8448df 100644
--- a/test/deps/goldens/formatting.txt
+++ b/test/testdata/goldens/deps/executables_test/applies formatting before printing executables.txt
@@ -1,3 +1,7 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
 |-- bar
 |   |-- bin
 |   |   '-- qux.dart
@@ -10,18 +14,28 @@
 '-- myapp
     |-- bin
     |   '-- myapp.dart
+    |-- pubspec.lock
     '-- pubspec.yaml
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub deps --executables
 myapp
 foo: foo, baz
 bar:qux
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub deps --executables --dev
 myapp
 foo: foo, baz
 bar:qux
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub deps --json
 {
   "root": "myapp",
diff --git a/test/deps/goldens/dev_dependencies.txt b/test/testdata/goldens/deps/executables_test/dev dependencies.txt
similarity index 62%
rename from test/deps/goldens/dev_dependencies.txt
rename to test/testdata/goldens/deps/executables_test/dev dependencies.txt
index 3fb677c..2d72b44 100644
--- a/test/deps/goldens/dev_dependencies.txt
+++ b/test/testdata/goldens/deps/executables_test/dev dependencies.txt
@@ -1,16 +1,30 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
 |-- foo
 |   |-- bin
 |   |   '-- bar.dart
 |   '-- pubspec.yaml
 '-- myapp
+    |-- pubspec.lock
     '-- pubspec.yaml
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub deps --executables
 foo:bar
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub deps --executables --dev
 foo:bar
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub deps --json
 {
   "root": "myapp",
diff --git a/test/testdata/goldens/deps/executables_test/lists Dart executables, without entrypoints.txt b/test/testdata/goldens/deps/executables_test/lists Dart executables, without entrypoints.txt
new file mode 100644
index 0000000..a12d363
--- /dev/null
+++ b/test/testdata/goldens/deps/executables_test/lists Dart executables, without entrypoints.txt
@@ -0,0 +1,50 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
+'-- myapp
+    |-- bin
+    |   |-- bar.dart
+    |   '-- foo.dart
+    |-- pubspec.lock
+    '-- pubspec.yaml
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub deps --executables
+myapp: bar, foo
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
+$ pub deps --executables --dev
+myapp: bar, foo
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
+$ pub deps --json
+{
+  "root": "myapp",
+  "packages": [
+    {
+      "name": "myapp",
+      "version": "0.0.0",
+      "kind": "root",
+      "source": "root",
+      "dependencies": []
+    }
+  ],
+  "sdks": [
+    {
+      "name": "Dart",
+      "version": "0.1.2+3"
+    }
+  ],
+  "executables": [
+    ":bar",
+    ":foo"
+  ]
+}
+
diff --git a/test/deps/goldens/only_immediate.txt b/test/testdata/goldens/deps/executables_test/lists executables from a dependency.txt
similarity index 62%
copy from test/deps/goldens/only_immediate.txt
copy to test/testdata/goldens/deps/executables_test/lists executables from a dependency.txt
index 5e42925..75a1b33 100644
--- a/test/deps/goldens/only_immediate.txt
+++ b/test/testdata/goldens/deps/executables_test/lists executables from a dependency.txt
@@ -1,20 +1,30 @@
-|-- baz
-|   |-- bin
-|   |   '-- qux.dart
-|   '-- pubspec.yaml
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
 |-- foo
 |   |-- bin
 |   |   '-- bar.dart
 |   '-- pubspec.yaml
 '-- myapp
+    |-- pubspec.lock
     '-- pubspec.yaml
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub deps --executables
 foo:bar
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub deps --executables --dev
 foo:bar
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub deps --json
 {
   "root": "myapp",
@@ -33,15 +43,6 @@
       "version": "1.0.0",
       "kind": "direct",
       "source": "path",
-      "dependencies": [
-        "baz"
-      ]
-    },
-    {
-      "name": "baz",
-      "version": "1.0.0",
-      "kind": "transitive",
-      "source": "path",
       "dependencies": []
     }
   ],
diff --git a/test/deps/goldens/only_immediate.txt b/test/testdata/goldens/deps/executables_test/lists executables only from immediate dependencies.txt
similarity index 68%
rename from test/deps/goldens/only_immediate.txt
rename to test/testdata/goldens/deps/executables_test/lists executables only from immediate dependencies.txt
index 5e42925..de70643 100644
--- a/test/deps/goldens/only_immediate.txt
+++ b/test/testdata/goldens/deps/executables_test/lists executables only from immediate dependencies.txt
@@ -1,3 +1,7 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
 |-- baz
 |   |-- bin
 |   |   '-- qux.dart
@@ -7,14 +11,24 @@
 |   |   '-- bar.dart
 |   '-- pubspec.yaml
 '-- myapp
+    |-- pubspec.lock
     '-- pubspec.yaml
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub deps --executables
 foo:bar
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub deps --executables --dev
 foo:bar
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub deps --json
 {
   "root": "myapp",
diff --git a/test/deps/goldens/overrides.txt b/test/testdata/goldens/deps/executables_test/overriden dependencies executables.txt
similarity index 66%
rename from test/deps/goldens/overrides.txt
rename to test/testdata/goldens/deps/executables_test/overriden dependencies executables.txt
index ab76e79..910210e 100644
--- a/test/deps/goldens/overrides.txt
+++ b/test/testdata/goldens/deps/executables_test/overriden dependencies executables.txt
@@ -1,3 +1,7 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
 |-- foo-1.0
 |   |-- bin
 |   |   '-- bar.dart
@@ -8,14 +12,24 @@
 |   |   '-- baz.dart
 |   '-- pubspec.yaml
 '-- myapp
+    |-- pubspec.lock
     '-- pubspec.yaml
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub deps --executables
 foo: bar, baz
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub deps --executables --dev
 foo: bar, baz
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub deps --json
 {
   "root": "myapp",
diff --git a/test/testdata/goldens/deps/executables_test/skips executables in sub directories.txt b/test/testdata/goldens/deps/executables_test/skips executables in sub directories.txt
new file mode 100644
index 0000000..289f7c8
--- /dev/null
+++ b/test/testdata/goldens/deps/executables_test/skips executables in sub directories.txt
@@ -0,0 +1,50 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
+'-- myapp
+    |-- bin
+    |   |-- foo.dart
+    |   '-- sub
+    |       '-- bar.dart
+    |-- pubspec.lock
+    '-- pubspec.yaml
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub deps --executables
+myapp:foo
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
+$ pub deps --executables --dev
+myapp:foo
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
+$ pub deps --json
+{
+  "root": "myapp",
+  "packages": [
+    {
+      "name": "myapp",
+      "version": "0.0.0",
+      "kind": "root",
+      "source": "root",
+      "dependencies": []
+    }
+  ],
+  "sdks": [
+    {
+      "name": "Dart",
+      "version": "0.1.2+3"
+    }
+  ],
+  "executables": [
+    ":foo"
+  ]
+}
+
diff --git a/test/testdata/goldens/deps/executables_test/skips non-Dart executables.txt b/test/testdata/goldens/deps/executables_test/skips non-Dart executables.txt
new file mode 100644
index 0000000..d4a2db3
--- /dev/null
+++ b/test/testdata/goldens/deps/executables_test/skips non-Dart executables.txt
@@ -0,0 +1,45 @@
+# GENERATED BY: test/deps/executables_test.dart
+
+## Section 0
+$ tree
+'-- myapp
+    |-- bin
+    |   |-- bar.sh
+    |   '-- foo.py
+    |-- pubspec.lock
+    '-- pubspec.yaml
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub deps --executables
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
+$ pub deps --executables --dev
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
+$ pub deps --json
+{
+  "root": "myapp",
+  "packages": [
+    {
+      "name": "myapp",
+      "version": "0.0.0",
+      "kind": "root",
+      "source": "root",
+      "dependencies": []
+    }
+  ],
+  "sdks": [
+    {
+      "name": "Dart",
+      "version": "0.1.2+3"
+    }
+  ],
+  "executables": []
+}
+
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
new file mode 100644
index 0000000..237b3b5
--- /dev/null
+++ b/test/testdata/goldens/directory_option_test/commands taking a --directory~-C parameter work.txt
@@ -0,0 +1,126 @@
+# GENERATED BY: test/directory_option_test.dart
+
+## Section 0
+$ pub add --directory=myapp foo
+Resolving dependencies in myapp...
++ foo 1.0.0
+Changed 1 dependency in myapp!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub -C myapp add bar
+Resolving dependencies in myapp...
++ bar 1.2.3
+Changed 1 dependency in myapp!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
+$ pub -C myapp/example get --directory=myapp bar
+Resolving dependencies in myapp...
+Got dependencies in myapp!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
+$ pub remove bar -C myapp
+Resolving dependencies in myapp...
+These packages are no longer being depended on:
+- bar 1.2.3
+Changed 1 dependency in myapp!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
+$ pub get bar -C myapp
+Resolving dependencies in myapp...
+Got dependencies in myapp!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
+$ pub get bar -C myapp/example
+Resolving dependencies in myapp/example...
++ foo 1.0.0
++ test_pkg 1.0.0 from path myapp
+Changed 2 dependencies in myapp/example!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
+$ pub get bar -C myapp/example2
+Resolving dependencies in myapp/example2...
+[STDERR] Error on line 1, column 9 of myapp/pubspec.yaml: "name" field doesn't match expected name "myapp".
+[STDERR]   â•·
+[STDERR] 1 │ {"name":"test_pkg","version":"1.0.0","homepage":"http://pub.dartlang.org","description":"A package, I guess.","environment":{"sdk":">=1.8.0 <=2.0.0"}, dependencies: { foo: ^1.0.0}}
+[STDERR]   │         ^^^^^^^^^^
+[STDERR]   ╵
+[EXIT CODE] 65
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
+$ pub get bar -C myapp/broken_dir
+[STDERR] Could not find a file named "pubspec.yaml" in "$SANDBOX/myapp/broken_dir".
+[EXIT CODE] 66
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
+$ pub downgrade -C myapp
+Resolving dependencies in myapp...
+  foo 1.0.0
+No dependencies changed in myapp.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
+$ pub upgrade bar -C myapp
+Resolving dependencies in myapp...
+  foo 1.0.0
+No dependencies changed in myapp.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
+$ pub run -C myapp bin/app.dart
+Building package executable...
+Built test_pkg:app.
+Hi
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
+$ pub publish -C myapp --dry-run
+Publishing test_pkg 1.0.0 to http://localhost:$PORT:
+|-- CHANGELOG.md
+|-- LICENSE
+|-- README.md
+|-- bin
+|   '-- app.dart
+|-- example
+|   '-- pubspec.yaml
+|-- example2
+|   '-- pubspec.yaml
+|-- lib
+|   '-- test_pkg.dart
+'-- pubspec.yaml
+The server may enforce additional checks.
+[STDERR] 
+[STDERR] Package has 0 warnings.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 12
+$ pub uploader -C myapp add sigurdm@google.com
+Good job!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 13
+$ pub deps -C myapp
+Dart SDK 1.12.0
+test_pkg 1.0.0
+'-- foo 1.0.0
+
diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
new file mode 100644
index 0000000..fc69b03
--- /dev/null
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -0,0 +1,308 @@
+# GENERATED BY: test/embedding/embedding_test.dart
+
+$ tool/test-bin/pub_command_runner.dart pub --verbose get
+MSG : Resolving dependencies...
+MSG : + foo 1.0.0
+MSG : Downloading foo 1.0.0...
+MSG : Changed 1 dependency!
+MSG : Logs written to $SANDBOX/cache/log/pub_log.txt.
+[E] FINE: Pub 0.1.2+3
+[E] SLVR: fact: myapp is 0.0.0
+[E] SLVR: derived: myapp
+[E] SLVR: fact: myapp depends on foo any
+[E] SLVR:   selecting myapp
+[E] SLVR:   derived: foo any
+[E] IO  : Get versions from http://localhost:$PORT/api/packages/foo.
+[E] IO  : HTTP GET http://localhost:$PORT/api/packages/foo
+[E]    | Accept: application/vnd.pub.v2+json
+[E]    | X-Pub-OS: $OS
+[E]    | X-Pub-Command: get
+[E]    | X-Pub-Session-ID: $ID
+[E]    | X-Pub-Environment: test-environment
+[E]    | X-Pub-Reason: direct
+[E]    | user-agent: Dart pub 0.1.2+3
+[E] IO  : HTTP response 200 OK for GET http://localhost:$PORT/api/packages/foo
+[E]    | took: $TIME
+[E]    | date: $TIME
+[E]    | content-length: 197
+[E]    | x-frame-options: SAMEORIGIN
+[E]    | content-type: text/plain; charset=utf-8
+[E]    | x-xss-protection: 1; mode=block
+[E]    | x-content-type-options: nosniff
+[E]    | server: dart:io with Shelf
+[E] IO  : Writing $N characters to text file $SANDBOX/cache/hosted/localhost%58$PORT/.cache/foo-versions.json.
+[E] FINE: Contents:
+[E]    | {"name":"foo","uploaders":["nweiz@google.com"],"versions":[{"pubspec":{"name":"foo","version":"1.0.0"},"version":"1.0.0","archive_url":"http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz"}],"_fetchedAt": "$TIME"}
+[E] SLVR:   selecting foo 1.0.0
+[E] SLVR: Version solving took: $TIME
+[E]    | Tried 1 solutions.
+[E] FINE: Resolving dependencies finished ($TIME)
+[E] IO  : Get package from http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz.
+[E] IO  : Created temp directory $DIR
+[E] IO  : HTTP GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+[E]    | X-Pub-OS: $OS
+[E]    | X-Pub-Command: get
+[E]    | X-Pub-Session-ID: $ID
+[E]    | X-Pub-Environment: test-environment
+[E]    | X-Pub-Reason: direct
+[E]    | user-agent: Dart pub 0.1.2+3
+[E] IO  : HTTP response 200 OK for GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+[E]    | took: $TIME
+[E]    | transfer-encoding: chunked
+[E]    | date: $TIME
+[E]    | x-frame-options: SAMEORIGIN
+[E]    | content-type: text/plain; charset=utf-8
+[E]    | x-xss-protection: 1; mode=block
+[E]    | x-content-type-options: nosniff
+[E]    | server: dart:io with Shelf
+[E] IO  : Creating $FILE from stream
+[E] FINE: Created $FILE from stream
+[E] IO  : Created temp directory $DIR
+[E] IO  : Reading binary file $FILE.
+[E] FINE: Extracting .tar.gz stream to $DIR
+[E] IO  : Creating $FILE from stream
+[E] FINE: Created $FILE from stream
+[E] IO  : Creating $FILE from stream
+[E] FINE: Created $FILE from stream
+[E] FINE: Extracted .tar.gz to $DIR
+[E] IO  : Renaming directory $A to $B
+[E] IO  : Deleting directory $DIR
+[E] IO  : Writing $N characters to text file pubspec.lock.
+[E] FINE: Contents:
+[E]    | # Generated by pub
+[E]    | # See https://dart.dev/tools/pub/glossary#lockfile
+[E]    | packages:
+[E]    |   foo:
+[E]    |   dependency: "direct main"
+[E]    |   description:
+[E]    |   name: foo
+[E]    |   url: "http://localhost:$PORT"
+[E]    |   source: hosted
+[E]    |   version: "1.0.0"
+[E]    | sdks:
+[E]    |   dart: ">=0.1.2 <1.0.0"
+[E] IO  : Writing $N characters to text file .dart_tool/package_config.json.
+[E] FINE: Contents:
+[E]    | {
+[E]    |   "configVersion": 2,
+[E]    |   "packages": [
+[E]    |   {
+[E]    |   "name": "foo",
+[E]    |   "rootUri": "file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0",
+[E]    |   "packageUri": "lib/",
+[E]    |   "languageVersion": "2.7"
+[E]    |   },
+[E]    |   {
+[E]    |   "name": "myapp",
+[E]    |   "rootUri": "../",
+[E]    |   "packageUri": "lib/",
+[E]    |   "languageVersion": "0.1"
+[E]    |   }
+[E]    |   ],
+[E]    |   "generated": "$TIME",
+[E]    |   "generator": "pub",
+[E]    |   "generatorVersion": "0.1.2+3"
+[E]    | }
+[E] IO  : Writing $N characters to text file $SANDBOX/cache/log/pub_log.txt.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+Information about the latest pub run.
+
+If you believe something is not working right, you can go to 
+https://github.com/dart-lang/pub/issues/new to post a new issue and attach this file.
+
+Before making this file public, make sure to remove any sensitive information!
+
+Pub version: 0.1.2+3
+Created: $TIME
+FLUTTER_ROOT: <not set>
+PUB_HOSTED_URL: http://localhost:$PORT
+PUB_CACHE: "$SANDBOX/cache"
+Command: dart pub --verbose get
+Platform: $OS
+
+---- $SANDBOX/myapp/pubspec.yaml ----
+{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":"any"}}
+---- End pubspec.yaml ----
+---- $SANDBOX/myapp/pubspec.lock ----
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  foo:
+   dependency: "direct main"
+   description:
+   name: foo
+   url: "http://localhost:$PORT"
+   source: hosted
+   version: "1.0.0"
+sdks:
+  dart: ">=0.1.2 <1.0.0"
+
+---- End pubspec.lock ----
+---- Log transcript ----
+FINE: Pub 0.1.2+3
+MSG : Resolving dependencies...
+SLVR: fact: myapp is 0.0.0
+SLVR: derived: myapp
+SLVR: fact: myapp depends on foo any
+SLVR:   selecting myapp
+SLVR:   derived: foo any
+IO  : Get versions from http://localhost:$PORT/api/packages/foo.
+IO  : HTTP GET http://localhost:$PORT/api/packages/foo
+   | Accept: application/vnd.pub.v2+json
+   | X-Pub-OS: $OS
+   | X-Pub-Command: get
+   | X-Pub-Session-ID: $ID
+   | X-Pub-Environment: test-environment
+   | X-Pub-Reason: direct
+   | user-agent: Dart pub 0.1.2+3
+IO  : HTTP response 200 OK for GET http://localhost:$PORT/api/packages/foo
+   | took: $TIME
+   | date: $TIME
+   | content-length: 197
+   | x-frame-options: SAMEORIGIN
+   | content-type: text/plain; charset=utf-8
+   | x-xss-protection: 1; mode=block
+   | x-content-type-options: nosniff
+   | server: dart:io with Shelf
+IO  : Writing $N characters to text file $SANDBOX/cache/hosted/localhost%58$PORT/.cache/foo-versions.json.
+FINE: Contents:
+   | {"name":"foo","uploaders":["nweiz@google.com"],"versions":[{"pubspec":{"name":"foo","version":"1.0.0"},"version":"1.0.0","archive_url":"http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz"}],"_fetchedAt": "$TIME"}
+SLVR:   selecting foo 1.0.0
+SLVR: Version solving took: $TIME
+   | Tried 1 solutions.
+FINE: Resolving dependencies finished ($TIME)
+MSG : + foo 1.0.0
+IO  : Get package from http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz.
+MSG : Downloading foo 1.0.0...
+IO  : Created temp directory $DIR
+IO  : HTTP GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+   | X-Pub-OS: $OS
+   | X-Pub-Command: get
+   | X-Pub-Session-ID: $ID
+   | X-Pub-Environment: test-environment
+   | X-Pub-Reason: direct
+   | user-agent: Dart pub 0.1.2+3
+IO  : HTTP response 200 OK for GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+   | took: $TIME
+   | transfer-encoding: chunked
+   | date: $TIME
+   | x-frame-options: SAMEORIGIN
+   | content-type: text/plain; charset=utf-8
+   | x-xss-protection: 1; mode=block
+   | x-content-type-options: nosniff
+   | server: dart:io with Shelf
+IO  : Creating $FILE from stream
+FINE: Created $FILE from stream
+IO  : Created temp directory $DIR
+IO  : Reading binary file $FILE.
+FINE: Extracting .tar.gz stream to $DIR
+IO  : Creating $FILE from stream
+FINE: Created $FILE from stream
+IO  : Creating $FILE from stream
+FINE: Created $FILE from stream
+FINE: Extracted .tar.gz to $DIR
+IO  : Renaming directory $A to $B
+IO  : Deleting directory $DIR
+IO  : Writing $N characters to text file pubspec.lock.
+FINE: Contents:
+   | # Generated by pub
+   | # See https://dart.dev/tools/pub/glossary#lockfile
+   | packages:
+   |   foo:
+   |   dependency: "direct main"
+   |   description:
+   |   name: foo
+   |   url: "http://localhost:$PORT"
+   |   source: hosted
+   |   version: "1.0.0"
+   | sdks:
+   |   dart: ">=0.1.2 <1.0.0"
+MSG : Changed 1 dependency!
+IO  : Writing $N characters to text file .dart_tool/package_config.json.
+FINE: Contents:
+   | {
+   |   "configVersion": 2,
+   |   "packages": [
+   |   {
+   |   "name": "foo",
+   |   "rootUri": "file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0",
+   |   "packageUri": "lib/",
+   |   "languageVersion": "2.7"
+   |   },
+   |   {
+   |   "name": "myapp",
+   |   "rootUri": "../",
+   |   "packageUri": "lib/",
+   |   "languageVersion": "0.1"
+   |   }
+   |   ],
+   |   "generated": "$TIME",
+   |   "generator": "pub",
+   |   "generatorVersion": "0.1.2+3"
+   | }
+---- End log transcript ----
+-------------------------------- END OF OUTPUT ---------------------------------
+
+$ tool/test-bin/pub_command_runner.dart pub fail
+[E] Bad state: Pub has crashed
+[E]  tool/test-bin/pub_command_runner.dart $LINE:$COL   ThrowingCommand.runProtected
+[E] package:pub/src/command.dart $LINE:$COL   PubCommand.run.<fn>
+[E] package:pub/src/command.dart $LINE:$COL   PubCommand.run.<fn>
+[E] dart:async   new Future.sync
+[E] package:pub/src/utils.dart $LINE:$COL   captureErrors.wrappedCallback
+[E] dart:async   runZonedGuarded
+[E] package:pub/src/utils.dart $LINE:$COL   captureErrors
+[E] package:pub/src/command.dart $LINE:$COL   PubCommand.run
+[E] package:args/command_runner.dart $LINE:$COL   CommandRunner.runCommand
+[E]  tool/test-bin/pub_command_runner.dart $LINE:$COL  Runner.runCommand
+[E]  tool/test-bin/pub_command_runner.dart $LINE:$COL  Runner.run
+[E]  tool/test-bin/pub_command_runner.dart $LINE:$COL  main
+[E] This is an unexpected error. The full log and other details are collected in:
+[E] 
+[E]    $SANDBOX/cache/log/pub_log.txt
+[E] 
+[E] Consider creating an issue on https://github.com/dart-lang/pub/issues/new
+[E] and attaching the relevant parts of that log file.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+Information about the latest pub run.
+
+If you believe something is not working right, you can go to 
+https://github.com/dart-lang/pub/issues/new to post a new issue and attach this file.
+
+Before making this file public, make sure to remove any sensitive information!
+
+Pub version: 0.1.2+3
+Created: $TIME
+FLUTTER_ROOT: <not set>
+PUB_HOSTED_URL: http://localhost:$PORT
+PUB_CACHE: "$SANDBOX/cache"
+Command: dart pub fail
+Platform: $OS
+
+---- Log transcript ----
+FINE: Pub 0.1.2+3
+ERR : Bad state: Pub has crashed
+FINE: Exception type: StateError
+ERR : tool/test-bin/pub_command_runner.dart $LINE:$COL   ThrowingCommand.runProtected
+   | package:pub/src/command.dart $LINE:$COL   PubCommand.run.<fn>
+   | package:pub/src/command.dart $LINE:$COL   PubCommand.run.<fn>
+   | dart:async   new Future.sync
+   | package:pub/src/utils.dart $LINE:$COL   captureErrors.wrappedCallback
+   | dart:async   runZonedGuarded
+   | package:pub/src/utils.dart $LINE:$COL   captureErrors
+   | package:pub/src/command.dart $LINE:$COL   PubCommand.run
+   | package:args/command_runner.dart $LINE:$COL   CommandRunner.runCommand
+   | tool/test-bin/pub_command_runner.dart $LINE:$COL  Runner.runCommand
+   | tool/test-bin/pub_command_runner.dart $LINE:$COL  Runner.run
+   | tool/test-bin/pub_command_runner.dart $LINE:$COL  main
+ERR : This is an unexpected error. The full log and other details are collected in:
+   | 
+   |   $SANDBOX/cache/log/pub_log.txt
+   | 
+   | Consider creating an issue on https://github.com/dart-lang/pub/issues/new
+   | and attaching the relevant parts of that log file.
+---- End log transcript ----
diff --git a/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt b/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt
new file mode 100644
index 0000000..214712a
--- /dev/null
+++ b/test/testdata/goldens/embedding/embedding_test/run works, though hidden.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/embedding/embedding_test.dart
+
+$ tool/test-bin/pub_command_runner.dart pub get
+Resolving dependencies...
+Got dependencies!
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+$ tool/test-bin/pub_command_runner.dart pub run bin/main.dart
+Hi
+
diff --git a/test/testdata/goldens/help_test/pub add --help.txt b/test/testdata/goldens/help_test/pub add --help.txt
new file mode 100644
index 0000000..3d1f429
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub add --help.txt
@@ -0,0 +1,27 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub add --help
+Add dependencies to pubspec.yaml.
+
+Usage: pub add <package>[:<constraint>] [<package2>[:<constraint2>]...] [options]
+-h, --help                    Print this usage information.
+-d, --dev                     Adds to the development dependencies instead.
+    --git-url                 Git URL of the package
+    --git-ref                 Git branch or commit to be retrieved
+    --git-path                Path of git package in repository
+    --hosted-url              URL of package host server
+    --path                    Add package from local path
+    --sdk=<[flutter]>         add package from SDK source
+                              [flutter]
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Build executables in immediate dependencies.
+-C, --directory=<dir>         Run this in the directory <dir>.
+    --legacy-packages-file    Generate the legacy ".packages" file
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-add for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub cache --help.txt b/test/testdata/goldens/help_test/pub cache --help.txt
new file mode 100644
index 0000000..fdb0b2c
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub cache --help.txt
@@ -0,0 +1,17 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub cache --help
+Work with the system cache.
+
+Usage: pub cache [arguments...]
+-h, --help    Print this usage information.
+
+Available subcommands:
+  add      Install a package.
+  clean    Clears the global PUB_CACHE.
+  repair   Reinstall cached packages.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub cache add --help.txt b/test/testdata/goldens/help_test/pub cache add --help.txt
new file mode 100644
index 0000000..05068f0
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub cache add --help.txt
@@ -0,0 +1,14 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub cache add --help
+Install a package.
+
+Usage: pub cache add <package> [--version <constraint>] [--all]
+-h, --help       Print this usage information.
+    --all        Install all matching versions.
+-v, --version    Version constraint.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub cache clean --help.txt b/test/testdata/goldens/help_test/pub cache clean --help.txt
new file mode 100644
index 0000000..71dd1d6
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub cache clean --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub cache clean --help
+Clears the global PUB_CACHE.
+
+Usage: pub cache clean <subcommand> [arguments...]
+-h, --help     Print this usage information.
+-f, --force    Don't ask for confirmation.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub cache repair --help.txt b/test/testdata/goldens/help_test/pub cache repair --help.txt
new file mode 100644
index 0000000..ac09115
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub cache repair --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub cache repair --help
+Reinstall cached packages.
+
+Usage: pub cache repair <subcommand> [arguments...]
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-cache for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub deps --help.txt b/test/testdata/goldens/help_test/pub deps --help.txt
new file mode 100644
index 0000000..475353f
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub deps --help.txt
@@ -0,0 +1,19 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub deps --help
+Print package dependencies.
+
+Usage: pub deps [arguments...]
+-h, --help               Print this usage information.
+-s, --style              How output should be displayed.
+                         [compact, tree (default), list]
+    --[no-]dev           Whether to include dev dependencies.
+                         (defaults to on)
+    --executables        List all available executables.
+    --json               Output dependency information in a json format.
+-C, --directory=<dir>    Run this in the directory<dir>.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-deps for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub downgrade --help.txt b/test/testdata/goldens/help_test/pub downgrade --help.txt
new file mode 100644
index 0000000..ddf71e7
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub downgrade --help.txt
@@ -0,0 +1,20 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub downgrade --help
+Downgrade the current package's dependencies to oldest versions.
+
+
+
+Usage: pub downgrade [dependencies...]
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+-C, --directory=<dir>         Run this in the directory<dir>.
+    --legacy-packages-file    Generate the legacy ".packages" file
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-downgrade for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub get --help.txt b/test/testdata/goldens/help_test/pub get --help.txt
new file mode 100644
index 0000000..104f69c
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub get --help.txt
@@ -0,0 +1,19 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub get --help
+Get the current package's dependencies.
+
+Usage: pub get <subcommand> [arguments...]
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Build executables in immediate dependencies.
+    --legacy-packages-file    Generate the legacy ".packages" file
+-C, --directory=<dir>         Run this in the directory<dir>.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-get for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub global --help.txt b/test/testdata/goldens/help_test/pub global --help.txt
new file mode 100644
index 0000000..6aa723c
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub global --help.txt
@@ -0,0 +1,18 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub global --help
+Work with global packages.
+
+Usage: pub global [arguments...]
+-h, --help    Print this usage information.
+
+Available subcommands:
+  activate     Make a package's executables globally available.
+  deactivate   Remove a previously activated package.
+  list         List globally activated packages.
+  run          Run an executable from a globally activated package.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub global activate --help.txt b/test/testdata/goldens/help_test/pub global activate --help.txt
new file mode 100644
index 0000000..5341266
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub global activate --help.txt
@@ -0,0 +1,19 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub global activate --help
+Make a package's executables globally available.
+
+Usage: pub global activate <package> [version-constraint]
+-h, --help              Print this usage information.
+-s, --source            The source used to find the package.
+                        [git, hosted (default), path]
+    --no-executables    Do not put executables on PATH.
+-x, --executable        Executable(s) to place on PATH.
+    --overwrite         Overwrite executables from other packages with the same
+                        name.
+-u, --hosted-url        A custom pub server URL for the package. Only applies
+                        when using the `hosted` source.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub global deactivate --help.txt b/test/testdata/goldens/help_test/pub global deactivate --help.txt
new file mode 100644
index 0000000..8b8178b
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub global deactivate --help.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub global deactivate --help
+Remove a previously activated package.
+
+Usage: pub global deactivate <package>
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub global list --help.txt b/test/testdata/goldens/help_test/pub global list --help.txt
new file mode 100644
index 0000000..12244be
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub global list --help.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub global list --help
+List globally activated packages.
+
+Usage: pub global list <subcommand> [arguments...]
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub global run --help.txt b/test/testdata/goldens/help_test/pub global run --help.txt
new file mode 100644
index 0000000..f829064
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub global run --help.txt
@@ -0,0 +1,18 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub global run --help
+Run an executable from a globally activated package.
+
+Usage: pub global run <package>:<executable> [args...]
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled. (Will disable
+                                        snapshotting, resulting in slower
+                                        startup).
+    --[no-]sound-null-safety            Override the default null safety
+                                        execution mode.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub login --help.txt b/test/testdata/goldens/help_test/pub login --help.txt
new file mode 100644
index 0000000..ce7233b
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub login --help.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub login --help
+Log into pub.dev.
+
+Usage: pub login
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub logout --help.txt b/test/testdata/goldens/help_test/pub logout --help.txt
new file mode 100644
index 0000000..3937ef7
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub logout --help.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub logout --help
+Log out of pub.dev.
+
+Usage: pub logout <subcommand> [arguments...]
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+
diff --git a/test/outdated/goldens/helptext.txt b/test/testdata/goldens/help_test/pub outdated --help.txt
similarity index 96%
rename from test/outdated/goldens/helptext.txt
rename to test/testdata/goldens/help_test/pub outdated --help.txt
index 20a4ecc..9a7b1bc 100644
--- a/test/outdated/goldens/helptext.txt
+++ b/test/testdata/goldens/help_test/pub outdated --help.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
 $ pub outdated --help
 Analyze your dependencies to find which ones can be upgraded.
 
diff --git a/test/testdata/goldens/help_test/pub publish --help.txt b/test/testdata/goldens/help_test/pub publish --help.txt
new file mode 100644
index 0000000..2977db0
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub publish --help.txt
@@ -0,0 +1,15 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub publish --help
+Publish the current package to pub.dartlang.org.
+
+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.
+-C, --directory=<dir>    Run this in the directory<dir>.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-lish for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub remove --help.txt b/test/testdata/goldens/help_test/pub remove --help.txt
new file mode 100644
index 0000000..b82f9b6
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub remove --help.txt
@@ -0,0 +1,19 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub remove --help
+Removes a dependency from the current package.
+
+Usage: pub remove <package>
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Precompile executables in immediate dependencies.
+-C, --directory=<dir>         Run this in the directory<dir>.
+    --legacy-packages-file    Generate the legacy ".packages" file
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-remove for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub run --help.txt b/test/testdata/goldens/help_test/pub run --help.txt
new file mode 100644
index 0000000..f2844f3
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub run --help.txt
@@ -0,0 +1,20 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub run --help
+Run an executable from a package.
+
+Usage: pub run <executable> [arguments...]
+-h, --help                              Print this usage information.
+    --[no-]enable-asserts               Enable assert statements.
+    --enable-experiment=<experiment>    Runs the executable in a VM with the
+                                        given experiments enabled.
+                                        (Will disable snapshotting, resulting in
+                                        slower startup).
+    --[no-]sound-null-safety            Override the default null safety
+                                        execution mode.
+-C, --directory=<dir>                   Run this in the directory<dir>.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-run for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub token --help.txt b/test/testdata/goldens/help_test/pub token --help.txt
new file mode 100644
index 0000000..fa164c5
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub token --help.txt
@@ -0,0 +1,16 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub token --help
+Manage authentication tokens for hosted pub repositories.
+
+Usage: pub token [arguments...]
+-h, --help    Print this usage information.
+
+Available subcommands:
+  add      Add authentication tokens for a package repository.
+  list     List servers for which a token exists.
+  remove   Remove secret token for package repository.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub token add --help.txt b/test/testdata/goldens/help_test/pub token add --help.txt
new file mode 100644
index 0000000..7068a5c
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub token add --help.txt
@@ -0,0 +1,13 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub token add --help
+Add authentication tokens for a package repository.
+
+Usage: pub token add
+-h, --help       Print this usage information.
+    --env-var    Read the secret token from this environment variable when
+                 making requests.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub token list --help.txt b/test/testdata/goldens/help_test/pub token list --help.txt
new file mode 100644
index 0000000..5c333b5
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub token list --help.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub token list --help
+List servers for which a token exists.
+
+Usage: pub token list
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub token remove --help.txt b/test/testdata/goldens/help_test/pub token remove --help.txt
new file mode 100644
index 0000000..1f79f81
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub token remove --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub token remove --help
+Remove secret token for package repository.
+
+Usage: pub token remove
+-h, --help    Print this usage information.
+    --all     Remove all secret tokens.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub upgrade --help.txt b/test/testdata/goldens/help_test/pub upgrade --help.txt
new file mode 100644
index 0000000..c787951
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub upgrade --help.txt
@@ -0,0 +1,23 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub upgrade --help
+Upgrade the current package's dependencies to latest versions.
+
+Usage: pub upgrade [dependencies...]
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Precompile executables in immediate dependencies.
+    --null-safety             Upgrade constraints in pubspec.yaml to null-safety
+                              versions
+    --legacy-packages-file    Generate the legacy ".packages" file
+    --major-versions          Upgrades packages to their latest resolvable
+                              versions, and updates pubspec.yaml.
+-C, --directory=<dir>         Run this in the directory<dir>.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-upgrade for detailed documentation.
+
diff --git a/test/testdata/goldens/help_test/pub uploader --help.txt b/test/testdata/goldens/help_test/pub uploader --help.txt
new file mode 100644
index 0000000..10c9d9d
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub uploader --help.txt
@@ -0,0 +1,15 @@
+# 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.
+
diff --git a/test/testdata/goldens/help_test/pub version --help.txt b/test/testdata/goldens/help_test/pub version --help.txt
new file mode 100644
index 0000000..82e9f9f
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub version --help.txt
@@ -0,0 +1,11 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub version --help
+Print pub version.
+
+Usage: pub version
+-h, --help    Print this usage information.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/401-with-message.txt b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/401-with-message.txt
new file mode 100644
index 0000000..8dcaaf5
--- /dev/null
+++ b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/401-with-message.txt
@@ -0,0 +1,13 @@
+# GENERATED BY: test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
+
+## Section 0
+$ pub get
+Resolving dependencies...
+[STDERR] Because myapp depends on foo any which doesn't exist (authentication failed), version solving failed.
+[STDERR] 
+[STDERR] http://localhost:$PORT package repository requested authentication!
+[STDERR] You can provide credentials using:
+[STDERR]     pub token add http://localhost:$PORT
+[STDERR] <message>
+[EXIT CODE] 69
+
diff --git a/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/401.txt b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/401.txt
new file mode 100644
index 0000000..68d5bd0
--- /dev/null
+++ b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/401.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
+
+## Section 0
+$ pub get
+Resolving dependencies...
+[STDERR] Because myapp depends on foo any which doesn't exist (authentication failed), version solving failed.
+[STDERR] 
+[STDERR] http://localhost:$PORT package repository requested authentication!
+[STDERR] You can provide credentials using:
+[STDERR]     pub token add http://localhost:$PORT
+[EXIT CODE] 69
+
diff --git a/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/403-with-message.txt b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/403-with-message.txt
new file mode 100644
index 0000000..882660c
--- /dev/null
+++ b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/403-with-message.txt
@@ -0,0 +1,13 @@
+# GENERATED BY: test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
+
+## Section 0
+$ pub get
+Resolving dependencies...
+[STDERR] Because myapp depends on foo any which doesn't exist (authorization failed), version solving failed.
+[STDERR] 
+[STDERR] Insufficient permissions to the resource at the http://localhost:$PORT package repository.
+[STDERR] You can modify credentials using:
+[STDERR]     pub token add http://localhost:$PORT
+[STDERR] <message>
+[EXIT CODE] 69
+
diff --git a/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/403.txt b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/403.txt
new file mode 100644
index 0000000..f8a5af9
--- /dev/null
+++ b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/403.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
+
+## Section 0
+$ pub get
+Resolving dependencies...
+[STDERR] Because myapp depends on foo any which doesn't exist (authorization failed), version solving failed.
+[STDERR] 
+[STDERR] Insufficient permissions to the resource at the http://localhost:$PORT package repository.
+[STDERR] You can modify credentials using:
+[STDERR]     pub token add http://localhost:$PORT
+[EXIT CODE] 69
+
diff --git a/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/bad_json.txt b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/bad_json.txt
new file mode 100644
index 0000000..8e446c2
--- /dev/null
+++ b/test/testdata/goldens/hosted/fail_gracefully_on_bad_version_listing_response_test/bad_json.txt
@@ -0,0 +1,10 @@
+# GENERATED BY: test/hosted/fail_gracefully_on_bad_version_listing_response_test.dart
+
+## Section 0
+$ pub get
+Resolving dependencies...
+[STDERR] Because myapp depends on foo any which doesn't exist (Got badly formatted response trying to find package foo at http://localhost:$PORT), version solving failed.
+[STDERR] 
+[STDERR] Check that "http://localhost:$PORT" is a valid package repository.
+[EXIT CODE] 65
+
diff --git a/test/testdata/goldens/hosted/fail_gracefully_with_hint_test/supports two hints.txt b/test/testdata/goldens/hosted/fail_gracefully_with_hint_test/supports two hints.txt
new file mode 100644
index 0000000..a45b0f0
--- /dev/null
+++ b/test/testdata/goldens/hosted/fail_gracefully_with_hint_test/supports two hints.txt
@@ -0,0 +1,13 @@
+# GENERATED BY: test/hosted/fail_gracefully_with_hint_test.dart
+
+## Section 0
+$ pub get --offline
+Resolving dependencies...
+[STDERR] Because foo <1.2.4 requires the Flutter SDK and foo >=1.2.4 depends on bar any, every version of foo requires bar any.
+[STDERR] So, because bar doesn't exist (could not find package bar in cache) and myapp depends on foo any, version solving failed.
+[STDERR] 
+[STDERR] Flutter users should run `flutter pub get` instead of `dart pub get`.
+[STDERR] 
+[STDERR] Try again without --offline!
+[EXIT CODE] 69
+
diff --git a/test/outdated/goldens/handles_sdk_dependencies.txt b/test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt
similarity index 84%
rename from test/outdated/goldens/handles_sdk_dependencies.txt
rename to test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt
index aba2f87..c9bc73f 100644
--- a/test/outdated/goldens/handles_sdk_dependencies.txt
+++ b/test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -19,6 +22,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -33,6 +39,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -47,6 +56,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -63,6 +75,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -77,6 +92,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -89,6 +107,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -103,6 +124,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -119,6 +143,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -135,6 +162,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -151,6 +181,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `flutter pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -195,6 +228,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/outdated/goldens/circular_dependencies.txt b/test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt
similarity index 80%
rename from test/outdated/goldens/circular_dependencies.txt
rename to test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt
index 27b6202..b9ab0a5 100644
--- a/test/outdated/goldens/circular_dependencies.txt
+++ b/test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -19,6 +22,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -31,6 +37,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -43,6 +52,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -55,6 +67,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -67,6 +82,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -79,6 +97,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -91,6 +112,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -104,6 +128,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -117,6 +144,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -130,6 +160,9 @@
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `dart pub upgrade`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -155,6 +188,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/testdata/goldens/outdated/outdated_test/does not allow arguments - handles bad flags.txt b/test/testdata/goldens/outdated/outdated_test/does not allow arguments - handles bad flags.txt
new file mode 100644
index 0000000..6ab6163
--- /dev/null
+++ b/test/testdata/goldens/outdated/outdated_test/does not allow arguments - handles bad flags.txt
@@ -0,0 +1,64 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
+$ pub outdated random_argument
+[STDERR] Command "outdated" does not take any arguments.
+[STDERR] 
+[STDERR] Usage: pub outdated [options]
+[STDERR] -h, --help                         Print this usage information.
+[STDERR]     --[no-]color                   Whether to color the output.
+[STDERR]                                    Defaults to color when connected to a
+[STDERR]                                    terminal, and no-color otherwise.
+[STDERR]     --[no-]dependency-overrides    Show resolutions with `dependency_overrides`.
+[STDERR]                                    (defaults to on)
+[STDERR]     --[no-]dev-dependencies        Take dev dependencies into account.
+[STDERR]                                    (defaults to on)
+[STDERR]     --json                         Output the results using a json format.
+[STDERR]     --mode=<PROPERTY>              Highlight versions with PROPERTY.
+[STDERR]                                    Only packages currently missing that PROPERTY
+[STDERR]                                    will be included unless --show-all.
+[STDERR]                                    [outdated (default), null-safety]
+[STDERR]     --[no-]prereleases             Include prereleases in latest version.
+[STDERR]                                    (defaults to on in --mode=null-safety).
+[STDERR]     --[no-]show-all                Include dependencies that are already
+[STDERR]                                    fullfilling --mode.
+[STDERR]     --[no-]transitive              Show transitive dependencies.
+[STDERR]                                    (defaults to off in --mode=null-safety).
+[STDERR] -C, --directory=<dir>              Run this in the directory<dir>.
+[STDERR] 
+[STDERR] Run "pub help" to see global options.
+[STDERR] See https://dart.dev/tools/pub/cmd/pub-outdated for detailed documentation.
+[EXIT CODE] 64
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub outdated --bad_flag
+[STDERR] Could not find an option named "bad_flag".
+[STDERR] 
+[STDERR] Usage: pub outdated [options]
+[STDERR] -h, --help                         Print this usage information.
+[STDERR]     --[no-]color                   Whether to color the output.
+[STDERR]                                    Defaults to color when connected to a
+[STDERR]                                    terminal, and no-color otherwise.
+[STDERR]     --[no-]dependency-overrides    Show resolutions with `dependency_overrides`.
+[STDERR]                                    (defaults to on)
+[STDERR]     --[no-]dev-dependencies        Take dev dependencies into account.
+[STDERR]                                    (defaults to on)
+[STDERR]     --json                         Output the results using a json format.
+[STDERR]     --mode=<PROPERTY>              Highlight versions with PROPERTY.
+[STDERR]                                    Only packages currently missing that PROPERTY
+[STDERR]                                    will be included unless --show-all.
+[STDERR]                                    [outdated (default), null-safety]
+[STDERR]     --[no-]prereleases             Include prereleases in latest version.
+[STDERR]                                    (defaults to on in --mode=null-safety).
+[STDERR]     --[no-]show-all                Include dependencies that are already
+[STDERR]                                    fullfilling --mode.
+[STDERR]     --[no-]transitive              Show transitive dependencies.
+[STDERR]                                    (defaults to off in --mode=null-safety).
+[STDERR] -C, --directory=<dir>              Run this in the directory<dir>.
+[STDERR] 
+[STDERR] Run "pub help" to see global options.
+[STDERR] See https://dart.dev/tools/pub/cmd/pub-outdated for detailed documentation.
+[EXIT CODE] 64
+
diff --git a/test/outdated/goldens/prereleases.txt b/test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt
similarity index 87%
rename from test/outdated/goldens/prereleases.txt
rename to test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt
index 8b91475..12c2ee3 100644
--- a/test/outdated/goldens/prereleases.txt
+++ b/test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -34,6 +37,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -47,6 +53,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -60,6 +69,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -74,6 +86,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -88,6 +103,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -101,6 +119,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -114,6 +135,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -129,6 +153,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -144,6 +171,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -159,6 +189,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -222,6 +255,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/outdated/goldens/mutually_incompatible.txt b/test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt
similarity index 84%
rename from test/outdated/goldens/mutually_incompatible.txt
rename to test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt
index ae40331..18baa88 100644
--- a/test/outdated/goldens/mutually_incompatible.txt
+++ b/test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -34,6 +37,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -46,6 +52,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -58,6 +67,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -70,6 +82,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -82,6 +97,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -94,6 +112,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -106,6 +127,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -119,6 +143,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -132,6 +159,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -145,6 +175,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -189,6 +222,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/outdated/goldens/newer_versions.txt b/test/testdata/goldens/outdated/outdated_test/newer versions available.txt
similarity index 91%
rename from test/outdated/goldens/newer_versions.txt
rename to test/testdata/goldens/outdated/outdated_test/newer versions available.txt
index 6e74a3e..d6c0ff9 100644
--- a/test/outdated/goldens/newer_versions.txt
+++ b/test/testdata/goldens/outdated/outdated_test/newer versions available.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -84,6 +87,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -110,6 +116,9 @@
 3  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -128,6 +137,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -156,6 +168,9 @@
 3  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -182,6 +197,9 @@
 3  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -200,6 +218,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -226,6 +247,9 @@
 3  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -247,6 +271,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -276,6 +303,9 @@
 3  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -297,6 +327,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -440,6 +473,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/outdated/goldens/no_dependencies.txt b/test/testdata/goldens/outdated/outdated_test/no dependencies.txt
similarity index 63%
rename from test/outdated/goldens/no_dependencies.txt
rename to test/testdata/goldens/outdated/outdated_test/no dependencies.txt
index 672c124..d256d7f 100644
--- a/test/outdated/goldens/no_dependencies.txt
+++ b/test/testdata/goldens/outdated/outdated_test/no dependencies.txt
@@ -1,44 +1,68 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": []
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -46,6 +70,9 @@
 
 All your dependencies declare support for null-safety.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -53,6 +80,9 @@
 
 All your dependencies declare support for null-safety.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -60,11 +90,17 @@
 
 All your dependencies declare support for null-safety.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": []
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": []
diff --git a/test/outdated/goldens/no_lockfile.txt b/test/testdata/goldens/outdated/outdated_test/no lockfile.txt
similarity index 86%
rename from test/outdated/goldens/no_lockfile.txt
rename to test/testdata/goldens/outdated/outdated_test/no lockfile.txt
index bca5ab7..7f670db 100644
--- a/test/outdated/goldens/no_lockfile.txt
+++ b/test/testdata/goldens/outdated/outdated_test/no lockfile.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -30,6 +33,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -46,6 +52,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -62,6 +71,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -78,6 +90,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -94,6 +109,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -110,6 +128,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -126,6 +147,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -143,6 +167,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -160,6 +187,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -177,6 +207,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -215,6 +248,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/testdata/goldens/outdated/outdated_test/no pubspec.txt b/test/testdata/goldens/outdated/outdated_test/no pubspec.txt
new file mode 100644
index 0000000..ba8858a
--- /dev/null
+++ b/test/testdata/goldens/outdated/outdated_test/no pubspec.txt
@@ -0,0 +1,7 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
+$ pub outdated
+[STDERR] Could not find a file named "pubspec.yaml" in "$SANDBOX/myapp".
+[EXIT CODE] 66
+
diff --git a/test/outdated/goldens/null_safety.txt b/test/testdata/goldens/outdated/outdated_test/null safety compliance.txt
similarity index 90%
rename from test/outdated/goldens/null_safety.txt
rename to test/testdata/goldens/outdated/outdated_test/null safety compliance.txt
index 9a9ce9c..d345f55 100644
--- a/test/outdated/goldens/null_safety.txt
+++ b/test/testdata/goldens/outdated/outdated_test/null safety compliance.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -94,6 +97,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -111,6 +117,9 @@
 6  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -128,6 +137,9 @@
 6  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -145,6 +157,9 @@
 6  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -162,6 +177,9 @@
 6  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -179,6 +197,9 @@
 6  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -196,6 +217,9 @@
 6  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -210,6 +234,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -224,6 +251,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -238,6 +268,9 @@
 2  dependencies are constrained to versions that are older than a resolvable version.
 To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -282,6 +315,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/outdated/goldens/null_safety_already_migrated.txt b/test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt
similarity index 74%
rename from test/outdated/goldens/null_safety_already_migrated.txt
rename to test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt
index b7ad48e..3d227ec 100644
--- a/test/outdated/goldens/null_safety_already_migrated.txt
+++ b/test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt
@@ -1,20 +1,32 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": []
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -32,24 +44,36 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -62,6 +86,9 @@
 dev_dependencies: all support null safety.
 All dependencies opt in to null-safety.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -77,6 +104,9 @@
 devTransitive  ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0  
 All dependencies opt in to null-safety.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -89,6 +119,9 @@
 dev_dependencies: all support null safety.
 All dependencies opt in to null-safety.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -114,6 +147,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": []
diff --git a/test/outdated/goldens/null_safety_no_resolution.txt b/test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt
similarity index 78%
rename from test/outdated/goldens/null_safety_no_resolution.txt
rename to test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt
index 89fb0e2..1ab20ad 100644
--- a/test/outdated/goldens/null_safety_no_resolution.txt
+++ b/test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt
@@ -1,20 +1,32 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": []
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -27,6 +39,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -39,18 +54,27 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
 
 Found no outdated packages
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -63,6 +87,9 @@
 foo           ✗1.0.0   ✗1.0.0      -           ✓2.0.0-nullsafety.0  
 No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -75,6 +102,9 @@
 foo           ✗1.0.0   ✗1.0.0      -           ✓2.0.0-nullsafety.0  
 No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -87,6 +117,9 @@
 foo           ✗1.0.0   ✗1.0.0      -           ✗1.0.0  
 No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -125,6 +158,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": []
diff --git a/test/outdated/goldens/dependency_overrides_no_solution.txt b/test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt
similarity index 86%
rename from test/outdated/goldens/dependency_overrides_no_solution.txt
rename to test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt
index 9efeb58..909ee0a 100644
--- a/test/outdated/goldens/dependency_overrides_no_solution.txt
+++ b/test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -40,6 +43,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -52,6 +58,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -64,6 +73,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -76,6 +88,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -88,6 +103,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -100,6 +118,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -111,6 +132,9 @@
 foo           *1.0.0 (overridden)  -           -           2.0.0   
 No resolution was found. Try running `dart pub upgrade --dry-run` to explore why.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -124,6 +148,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -137,6 +164,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -150,6 +180,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -200,6 +233,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/outdated/goldens/dependency_overrides.txt b/test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt
similarity index 89%
rename from test/outdated/goldens/dependency_overrides.txt
rename to test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt
index eb0c881..49abe1b 100644
--- a/test/outdated/goldens/dependency_overrides.txt
+++ b/test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
 $ pub outdated --json
 {
   "packages": [
@@ -58,6 +61,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub outdated --no-color
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -71,6 +77,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
 $ pub outdated --no-color --no-transitive
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -84,6 +93,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
 $ pub outdated --no-color --up-to-date
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -97,6 +109,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
 $ pub outdated --no-color --prereleases
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -110,6 +125,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
 $ pub outdated --no-color --no-dev-dependencies
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -123,6 +141,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
 $ pub outdated --no-color --no-dependency-overrides
 Showing outdated packages.
 [*] indicates versions that are not the latest available.
@@ -140,6 +161,9 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -154,6 +178,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
 $ pub outdated --no-color --mode=null-safety --transitive
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -168,6 +195,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
 $ pub outdated --no-color --mode=null-safety --no-prereleases
 Showing dependencies that are currently not opted in to null-safety.
 [✗] indicates versions without null safety support.
@@ -182,6 +212,9 @@
 You are already using the newest resolvable versions listed in the 'Resolvable' column.
 Newer versions, listed in 'Latest', may not be mutually compatible.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
 $ pub outdated --json --mode=null-safety
 {
   "packages": [
@@ -254,6 +287,9 @@
   ]
 }
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
 $ pub outdated --json --no-dev-dependencies
 {
   "packages": [
diff --git a/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt b/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt
new file mode 100644
index 0000000..ae304ea
--- /dev/null
+++ b/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt
@@ -0,0 +1,25 @@
+# GENERATED BY: test/upgrade/example_warns_about_major_versions_test.dart
+
+## Section 0
+$ pub upgrade --major-versions --example
+Resolving dependencies...
++ bar 2.0.0
+Changed 1 dependency!
+
+Changed 1 constraint in pubspec.yaml:
+  bar: ^1.0.0 -> ^2.0.0
+Resolving dependencies in ./example...
+Got dependencies in ./example.
+[STDERR] Running `upgrade --major-versions` only in `.`. Run `dart pub upgrade --major-versions --directory example/` separately.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub upgrade --major-versions --directory example
+Resolving dependencies in example...
+> foo 2.0.0 (was 1.0.0)
+Changed 1 dependency in example!
+
+Changed 1 constraint in pubspec.yaml:
+  foo: ^1.0.0 -> ^2.0.0
+
diff --git a/test/goldens/upgrade_null_safety_example.txt b/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --null-safety does not update null-safety of dependencies in example~.txt
similarity index 62%
rename from test/goldens/upgrade_null_safety_example.txt
rename to test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --null-safety does not update null-safety of dependencies in example~.txt
index 5c5b31c..cabc0be 100644
--- a/test/goldens/upgrade_null_safety_example.txt
+++ b/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --null-safety does not update null-safety of dependencies in example~.txt
@@ -1,3 +1,6 @@
+# GENERATED BY: test/upgrade/example_warns_about_major_versions_test.dart
+
+## Section 0
 $ pub upgrade --null-safety --example
 Resolving dependencies...
 + bar 2.0.0
@@ -7,8 +10,11 @@
   bar: ^1.0.0 -> ^2.0.0
 Resolving dependencies in ./example...
 Got dependencies in ./example.
-[ERR] Running `upgrade --null-safety` only in `.`. Run `dart pub upgrade --null-safety --directory example/` separately.
+[STDERR] Running `upgrade --null-safety` only in `.`. Run `dart pub upgrade --null-safety --directory example/` separately.
 
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
 $ pub upgrade --null-safety --directory example
 Resolving dependencies in example...
 > bar 2.0.0 (was 1.0.0)
diff --git a/test/token/add_token_test.dart b/test/token/add_token_test.dart
index 8105ce5..ca68c41 100644
--- a/test/token/add_token_test.dart
+++ b/test/token/add_token_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -46,7 +44,7 @@
 
       await runPub(
         args: ['token', 'add', 'https://example.com/', '--env-var', 'TOKEN'],
-        error: 'Environment variable `TOKEN` is not defined.',
+        error: 'Environment variable "TOKEN" is not defined.',
       );
 
       await d.tokensFile({
@@ -68,7 +66,7 @@
       await runPub(
         args: ['token', 'add', 'https://example.com/', '--env-var', 'TOKEN'],
         environment: {'TOKEN': 'secret'},
-        error: isNot(contains('Environment variable TOKEN is not defined.')),
+        error: isNot(contains('is not defined.')),
       );
 
       await d.tokensFile({
@@ -135,10 +133,21 @@
     await d.dir(configPath).create();
     await runPub(
       args: ['token', 'add', 'http://mypub.com'],
-      error: contains('Insecure package repository could not be added.'),
-      exitCode: exit_codes.DATA,
+      error: contains('insecure repositories cannot use authentication'),
+      exitCode: exit_codes.USAGE,
     );
 
     await d.dir(configPath, [d.nothing('pub-tokens.json')]).validate();
   });
+
+  test('with empty environment gives error message', () async {
+    await runPub(
+      args: ['token', 'add', 'https://mypub.com'],
+      input: ['auth-token'],
+      error: contains('No config dir found.'),
+      exitCode: exit_codes.DATA,
+      environment: {'_PUB_TEST_CONFIG_DIR': null},
+      includeParentEnvironment: false,
+    );
+  });
 }
diff --git a/test/token/error_message_test.dart b/test/token/error_message_test.dart
new file mode 100644
index 0000000..53431a5
--- /dev/null
+++ b/test/token/error_message_test.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2021, 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:shelf/shelf.dart' as shelf;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void respondWithWwwAuthenticate(String headerValue) {
+  globalServer.expect('GET', '/api/packages/versions/new', (request) {
+    return shelf.Response(403, headers: {'www-authenticate': headerValue});
+  });
+}
+
+Future<void> expectPubErrorMessage(dynamic matcher) {
+  return runPub(
+    args: ['lish'],
+    environment: {
+      'PUB_HOSTED_URL': globalServer.url,
+      '_PUB_TEST_AUTH_METHOD': 'token',
+    },
+    exitCode: 65,
+    input: ['y'],
+    error: matcher,
+  );
+}
+
+void main() {
+  setUp(() async {
+    await d.validPackage.create();
+    await servePackages();
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': globalServer.url, 'token': 'access token'},
+      ]
+    }).create();
+  });
+
+  test('prints www-authenticate message', () async {
+    respondWithWwwAuthenticate('bearer realm="pub", message="custom message"');
+    await expectPubErrorMessage(contains('custom message'));
+  });
+
+  test('sanitizes and prints dirty www-authenticate message', () {
+    // Unable to test this case because shelf does not allow characters [1]
+    // that pub cli supposed to sanitize.
+    //
+    // [1] https://github.com/dart-lang/sdk/blob/main/sdk/lib/_http/http_headers.dart#L653-L662
+  });
+
+  test('trims and prints long www-authenticate message', () async {
+    var message = List.generate(2048, (_) => 'a').join();
+
+    respondWithWwwAuthenticate('bearer realm="pub", message="$message"');
+    await expectPubErrorMessage(allOf(
+      isNot(contains(message)),
+      contains(message.substring(0, 1024)),
+    ));
+  });
+
+  test('does not prints message if realm is not equals to pub', () async {
+    respondWithWwwAuthenticate('bearer realm="web", message="custom message"');
+    await expectPubErrorMessage(isNot(contains('custom message')));
+  });
+
+  test('does not prints message if challenge is not equals to bearer',
+      () async {
+    respondWithWwwAuthenticate('basic realm="pub", message="custom message"');
+    await expectPubErrorMessage(isNot(contains('custom message')));
+  });
+
+  test('prints message for bearer challenge for pub realm only', () async {
+    respondWithWwwAuthenticate(
+      'basic realm="pub", message="enter username and password", '
+      'newAuth message="use web portal to login", '
+      'bearer realm="api", message="contact IT dept to enroll", '
+      'bearer realm="pub", '
+      'bearer realm="pub", message="pub realm message"',
+    );
+    await expectPubErrorMessage(contains('pub realm message'));
+  });
+}
diff --git a/test/token/remove_token_test.dart b/test/token/remove_token_test.dart
index f9ce67d..bfe2428 100644
--- a/test/token/remove_token_test.dart
+++ b/test/token/remove_token_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
@@ -61,4 +59,14 @@
 
     await d.dir(configPath, [d.nothing('pub-tokens.json')]).validate();
   });
+
+  test('with empty environment gives error message', () async {
+    await runPub(
+      args: ['token', 'remove', 'http://mypub.com'],
+      error: contains('No config dir found.'),
+      exitCode: exit_codes.DATA,
+      environment: {'_PUB_TEST_CONFIG_DIR': null},
+      includeParentEnvironment: false,
+    );
+  });
 }
diff --git a/test/token/token_authentication_test.dart b/test/token/token_authentication_test.dart
new file mode 100644
index 0000000..c020ac0
--- /dev/null
+++ b/test/token/token_authentication_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, 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 '../lish/utils.dart';
+import '../test_pub.dart';
+
+void main() {
+  setUp(d.validPackage.create);
+
+  test('with a pre existing environment token authenticates', () async {
+    await servePackages();
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': globalServer.url, 'env': 'TOKEN'},
+      ]
+    }).create();
+    var pub = await startPublish(globalServer,
+        authMethod: 'token', environment: {'TOKEN': 'access token'});
+    await confirmPublish(pub);
+
+    handleUploadForm(globalServer);
+
+    await pub.shouldExit(1);
+  });
+
+  test('with a pre existing opaque token authenticates', () async {
+    await servePackages();
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': globalServer.url, 'token': 'access token'},
+      ]
+    }).create();
+    var pub = await startPublish(globalServer, authMethod: 'token');
+    await confirmPublish(pub);
+
+    handleUploadForm(globalServer);
+
+    await pub.shouldExit(1);
+  });
+}
diff --git a/test/token/when_receives_401_removes_token_test.dart b/test/token/when_receives_401_removes_token_test.dart
new file mode 100644
index 0000000..86c0559
--- /dev/null
+++ b/test/token/when_receives_401_removes_token_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, 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:shelf/shelf.dart' as shelf;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  setUp(d.validPackage.create);
+
+  test('when receives 401 response removes saved token', () async {
+    final server = await servePackages();
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': server.url, 'token': 'access token'},
+      ]
+    }).create();
+    var pub = await startPublish(server, authMethod: 'token');
+    await confirmPublish(pub);
+
+    server.expect('GET', '/api/packages/versions/new', (request) {
+      return shelf.Response(401);
+    });
+
+    await pub.shouldExit(65);
+
+    await d.tokensFile({'version': 1, 'hosted': []}).validate();
+  });
+}
diff --git a/test/token/when_receives_403_persists_saved_token_test.dart b/test/token/when_receives_403_persists_saved_token_test.dart
new file mode 100644
index 0000000..45fc7a4
--- /dev/null
+++ b/test/token/when_receives_403_persists_saved_token_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, 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:shelf/shelf.dart' as shelf;
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+  setUp(d.validPackage.create);
+
+  test('when receives 403 response persists saved token', () async {
+    final server = await servePackages();
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': server.url, 'token': 'access token'},
+      ]
+    }).create();
+    var pub = await startPublish(server, authMethod: 'token');
+    await confirmPublish(pub);
+
+    server.expect('GET', '/api/packages/versions/new', (request) {
+      return shelf.Response(403);
+    });
+
+    await pub.shouldExit(65);
+
+    await d.tokensFile({
+      'version': 1,
+      'hosted': [
+        {'url': server.url, 'token': 'access token'},
+      ]
+    }).validate();
+  });
+}
diff --git a/test/unknown_source_test.dart b/test/unknown_source_test.dart
index 241f9c7..37591d0 100644
--- a/test/unknown_source_test.dart
+++ b/test/unknown_source_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:test/test.dart';
@@ -74,7 +72,9 @@
       await pubCommand(command);
 
       // Should upgrade to the new one.
-      await d.appPackagesFile({'foo': '../foo'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', path: '../foo'),
+      ]).validate();
     });
   });
 }
diff --git a/test/upgrade/dry_run_does_not_apply_changes_test.dart b/test/upgrade/dry_run_does_not_apply_changes_test.dart
index e77d3b8..8c942ca 100644
--- a/test/upgrade/dry_run_does_not_apply_changes_test.dart
+++ b/test/upgrade/dry_run_does_not_apply_changes_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
@@ -13,10 +11,9 @@
 
 void main() {
   test('--dry-run: shows report, changes nothing', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0');
 
     // Create the first lockfile.
     await d.appDir({'foo': '1.0.0'}).create();
@@ -52,10 +49,9 @@
   });
 
   test('--dry-run --major-versions: shows report, changes nothing', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0');
 
     await d.appDir({'foo': '^1.0.0'}).create();
 
diff --git a/test/upgrade/example_warns_about_major_versions_test.dart b/test/upgrade/example_warns_about_major_versions_test.dart
index 0813ed2..b5c9a67 100644
--- a/test/upgrade/example_warns_about_major_versions_test.dart
+++ b/test/upgrade/example_warns_about_major_versions_test.dart
@@ -2,23 +2,19 @@
 // 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.
 
-// @dart = 2.11
-
-import 'package:test/test.dart';
-
 import '../descriptor.dart' as d;
 import '../golden_file.dart';
 import '../test_pub.dart';
 
 void main() {
-  test(
+  testWithGolden(
       'pub upgrade --major-versions does not update major versions in example/',
-      () async {
-    await servePackages((b) => b
+      (ctx) async {
+    await servePackages()
       ..serve('foo', '1.0.0')
       ..serve('foo', '2.0.0')
       ..serve('bar', '1.0.0')
-      ..serve('bar', '2.0.0'));
+      ..serve('bar', '2.0.0');
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
@@ -36,24 +32,14 @@
       ])
     ]).create();
 
-    final buffer = StringBuffer();
-    await runPubIntoBuffer(
-      ['upgrade', '--major-versions', '--example'],
-      buffer,
-    );
-    await runPubIntoBuffer(
-      ['upgrade', '--major-versions', '--directory', 'example'],
-      buffer,
-    );
-
-    expectMatchesGoldenFile(
-        buffer.toString(), 'test/goldens/upgrade_major_versions_example.txt');
+    await ctx.run(['upgrade', '--major-versions', '--example']);
+    await ctx.run(['upgrade', '--major-versions', '--directory', 'example']);
   });
 
-  test(
+  testWithGolden(
       'pub upgrade --null-safety does not update null-safety of dependencies in example/',
-      () async {
-    await servePackages((b) => b
+      (ctx) async {
+    await servePackages()
       ..serve('foo', '1.0.0', pubspec: {
         'environment': {'sdk': '>=2.7.0 <3.0.0'},
       })
@@ -65,7 +51,7 @@
       })
       ..serve('bar', '2.0.0', pubspec: {
         'environment': {'sdk': '>=2.12.0 <3.0.0'},
-      }));
+      });
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
@@ -86,20 +72,14 @@
       ])
     ]).create();
 
-    final buffer = StringBuffer();
-    await runPubIntoBuffer(
+    await ctx.run(
       ['upgrade', '--null-safety', '--example'],
-      buffer,
       environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'},
     );
 
-    await runPubIntoBuffer(
+    await ctx.run(
       ['upgrade', '--null-safety', '--directory', 'example'],
-      buffer,
       environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'},
     );
-
-    expectMatchesGoldenFile(
-        buffer.toString(), 'test/goldens/upgrade_null_safety_example.txt');
   });
 }
diff --git a/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart b/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
index dffb433..5638fb9 100644
--- a/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
+++ b/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
diff --git a/test/upgrade/git/upgrade_locked_test.dart b/test/upgrade/git/upgrade_locked_test.dart
index 55938de..a8e744f 100644
--- a/test/upgrade/git/upgrade_locked_test.dart
+++ b/test/upgrade/git/upgrade_locked_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/upgrade/git/upgrade_one_locked_test.dart b/test/upgrade/git/upgrade_one_locked_test.dart
index 14fa010..5c41fa6 100644
--- a/test/upgrade/git/upgrade_one_locked_test.dart
+++ b/test/upgrade/git/upgrade_one_locked_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart b/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart
index 33f584c..e74a11e 100644
--- a/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart
+++ b/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
diff --git a/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart b/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart
index e1e160f..ae865eb 100644
--- a/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart
+++ b/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
diff --git a/test/upgrade/hosted/unlock_if_necessary_test.dart b/test/upgrade/hosted/unlock_if_necessary_test.dart
index 9bd78fb..2707100 100644
--- a/test/upgrade/hosted/unlock_if_necessary_test.dart
+++ b/test/upgrade/hosted/unlock_if_necessary_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,24 +11,28 @@
   test(
       "upgrades one locked pub server package's dependencies if it's "
       'necessary', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'foo_dep': 'any'});
-      builder.serve('foo_dep', '1.0.0');
-    });
+    final server = await servePackages();
+
+    server.serve('foo', '1.0.0', deps: {'foo_dep': 'any'});
+    server.serve('foo_dep', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'foo_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '1.0.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '2.0.0', deps: {'foo_dep': '>1.0.0'});
-      builder.serve('foo_dep', '2.0.0');
-    });
+    server.serve('foo', '2.0.0', deps: {'foo_dep': '>1.0.0'});
+    server.serve('foo_dep', '2.0.0');
 
     await pubUpgrade(args: ['foo']);
 
-    await d.appPackagesFile({'foo': '2.0.0', 'foo_dep': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/upgrade/hosted/unlock_single_package_test.dart b/test/upgrade/hosted/unlock_single_package_test.dart
index ba3d80f..e443a6a 100644
--- a/test/upgrade/hosted/unlock_single_package_test.dart
+++ b/test/upgrade/hosted/unlock_single_package_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,37 +9,43 @@
 
 void main() {
   test('can unlock a single package only in upgrade', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
-      builder.serve('bar', '1.0.0');
-    });
+    final server = await servePackages();
+
+    server.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
+    server.serve('bar', '1.0.0');
 
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
 
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
-      builder.serve('bar', '2.0.0');
-    });
+    server.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
+    server.serve('bar', '2.0.0');
 
     // This can't upgrade 'bar'
     await pubUpgrade(args: ['bar']);
 
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
-
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
     // Introducing foo and bar 1.1.0, to show that only 'bar' will be upgraded
-    globalPackageServer.add((builder) {
-      builder.serve('foo', '1.1.0', deps: {'bar': '<2.0.0'});
-      builder.serve('bar', '1.1.0');
-    });
+    server.serve('foo', '1.1.0', deps: {'bar': '<2.0.0'});
+    server.serve('bar', '1.1.0');
 
     await pubUpgrade(args: ['bar']);
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
     await pubUpgrade();
-    await d.appPackagesFile({'foo': '2.0.0', 'bar': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/upgrade/hosted/upgrade_removed_constraints_test.dart b/test/upgrade/hosted/upgrade_removed_constraints_test.dart
index bb73ddc..cf72c07 100644
--- a/test/upgrade/hosted/upgrade_removed_constraints_test.dart
+++ b/test/upgrade/hosted/upgrade_removed_constraints_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,24 +9,29 @@
 
 void main() {
   test('upgrades dependencies whose constraints have been removed', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'shared_dep': 'any'});
-      builder.serve('bar', '1.0.0', deps: {'shared_dep': '<2.0.0'});
-      builder.serve('shared_dep', '1.0.0');
-      builder.serve('shared_dep', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'shared_dep': 'any'})
+      ..serve('bar', '1.0.0', deps: {'shared_dep': '<2.0.0'})
+      ..serve('shared_dep', '1.0.0')
+      ..serve('shared_dep', '2.0.0');
 
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
     await pubUpgrade();
 
-    await d.appPackagesFile(
-        {'foo': '1.0.0', 'bar': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+    ]).validate();
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubUpgrade();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'shared_dep': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/upgrade/hosted/warn_about_discontinued_test.dart b/test/upgrade/hosted/warn_about_discontinued_test.dart
new file mode 100644
index 0000000..ba7fc57
--- /dev/null
+++ b/test/upgrade/hosted/warn_about_discontinued_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, 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('Warns about discontinued dependencies', () async {
+    final server = await servePackages()
+      ..serve('foo', '1.2.3', deps: {'transitive': 'any'})
+      ..serve('transitive', '1.0.0');
+    await d.appDir({'foo': '1.2.3'}).create();
+    await pubGet();
+
+    server
+      ..discontinue('foo')
+      ..discontinue('transitive');
+    // We warn only about the direct dependency here:
+    await pubUpgrade(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued)
+  transitive 1.0.0
+  No dependencies changed.
+  1 package is discontinued.
+''');
+    server.discontinue('foo', replacementText: 'bar');
+    // We warn only about the direct dependency here:
+    await pubUpgrade(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued replaced by bar)
+  transitive 1.0.0
+  No dependencies changed.
+  1 package is discontinued.
+''');
+  });
+
+  test('Warns about discontinued dev_dependencies', () async {
+    final server = await servePackages()
+      ..serve('foo', '1.2.3', deps: {'transitive': 'any'})
+      ..serve('transitive', '1.0.0');
+
+    await d.dir(appPath, [
+      d.file('pubspec.yaml', '''
+name: myapp
+dependencies:
+
+dev_dependencies:
+  foo: 1.2.3
+environment:
+  sdk: '>=0.1.2 <1.0.0'
+''')
+    ]).create();
+    await pubGet();
+
+    server
+      ..discontinue('foo')
+      ..discontinue('transitive');
+
+    // We warn only about the direct dependency here:
+    await pubUpgrade(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued)
+    transitive 1.0.0
+  No dependencies changed.
+  1 package is discontinued.
+''');
+    server.discontinue('foo', replacementText: 'bar');
+    // We warn only about the direct dependency here:
+    await pubUpgrade(output: '''
+Resolving dependencies...
+  foo 1.2.3 (discontinued replaced by bar)
+  transitive 1.0.0
+  No dependencies changed.
+  1 package is discontinued.
+''');
+  });
+}
diff --git a/test/upgrade/renamed_package_circular_dependency.dart b/test/upgrade/renamed_package_circular_dependency.dart
index 84ce151..ec10b82 100644
--- a/test/upgrade/renamed_package_circular_dependency.dart
+++ b/test/upgrade/renamed_package_circular_dependency.dart
@@ -2,18 +2,15 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
 void main() {
   test('The upgrade report handles a package becoming root', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'myapp': 'any'});
-      builder.serve('myapp', '1.0.0', deps: {'foo': 'any'});
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'myapp': 'any'})
+      ..serve('myapp', '1.0.0', deps: {'foo': 'any'});
 
     await d.dir(appPath, [
       d.pubspec({
diff --git a/test/upgrade/report/describes_change_test.dart b/test/upgrade/report/describes_change_test.dart
index 382deb0..96410cb 100644
--- a/test/upgrade/report/describes_change_test.dart
+++ b/test/upgrade/report/describes_change_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,11 +9,11 @@
 
 void main() {
   test('Shows count of discontinued packages', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.0.0');
-    });
+    final server = await servePackages();
 
-    globalPackageServer.add((builder) => builder..discontinue('foo'));
+    server.serve('foo', '2.0.0');
+
+    server.discontinue('foo');
 
     // Create the first lockfile.
     await d.appDir({'foo': '2.0.0'}).create();
@@ -35,12 +33,11 @@
   });
 
   test('shows how package changed from previous lockfile', () async {
-    await servePackages((builder) {
-      builder.serve('unchanged', '1.0.0');
-      builder.serve('version_changed', '1.0.0');
-      builder.serve('version_changed', '2.0.0');
-      builder.serve('source_changed', '1.0.0');
-    });
+    await servePackages()
+      ..serve('unchanged', '1.0.0')
+      ..serve('version_changed', '1.0.0')
+      ..serve('version_changed', '2.0.0')
+      ..serve('source_changed', '1.0.0');
 
     await d.dir('source_changed', [
       d.libDir('source_changed'),
diff --git a/test/upgrade/report/does_not_show_newer_versions_for_locked_packages_test.dart b/test/upgrade/report/does_not_show_newer_versions_for_locked_packages_test.dart
index 41f90d0..ef987b9 100644
--- a/test/upgrade/report/does_not_show_newer_versions_for_locked_packages_test.dart
+++ b/test/upgrade/report/does_not_show_newer_versions_for_locked_packages_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,14 +11,13 @@
   test(
       'Shows newer versions available for packages that are locked and not being upgraded',
       () async {
-    await servePackages((builder) {
-      builder.serve('not_upgraded', '1.0.0');
-      builder.serve('not_upgraded', '2.0.0');
-      builder.serve('not_upgraded', '3.0.0-dev');
-      builder.serve('upgraded', '1.0.0');
-      builder.serve('upgraded', '2.0.0');
-      builder.serve('upgraded', '3.0.0-dev');
-    });
+    await servePackages()
+      ..serve('not_upgraded', '1.0.0')
+      ..serve('not_upgraded', '2.0.0')
+      ..serve('not_upgraded', '3.0.0-dev')
+      ..serve('upgraded', '1.0.0')
+      ..serve('upgraded', '2.0.0')
+      ..serve('upgraded', '3.0.0-dev');
 
     // Constraint everything to the first version.
     await d.appDir({'not_upgraded': '1.0.0', 'upgraded': '1.0.0'}).create();
diff --git a/test/upgrade/report/highlights_overrides_test.dart b/test/upgrade/report/highlights_overrides_test.dart
index 04ae0ba..93f9f3b 100644
--- a/test/upgrade/report/highlights_overrides_test.dart
+++ b/test/upgrade/report/highlights_overrides_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,7 +9,8 @@
 
 void main() {
   test('highlights overridden packages', () async {
-    await servePackages((builder) => builder.serve('overridden', '1.0.0'));
+    final server = await servePackages();
+    server.serve('overridden', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
diff --git a/test/upgrade/report/leading_character_shows_change_test.dart b/test/upgrade/report/leading_character_shows_change_test.dart
index e7438d8..88f7ed2 100644
--- a/test/upgrade/report/leading_character_shows_change_test.dart
+++ b/test/upgrade/report/leading_character_shows_change_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,17 +9,16 @@
 
 void main() {
   test('the character before each package describes the change', () async {
-    await servePackages((builder) {
-      builder.serve('added', '1.0.0');
-      builder.serve('downgraded', '1.0.0');
-      builder.serve('downgraded', '2.0.0');
-      builder.serve('overridden', '1.0.0');
-      builder.serve('removed', '1.0.0');
-      builder.serve('source_changed', '1.0.0');
-      builder.serve('upgraded', '1.0.0');
-      builder.serve('upgraded', '2.0.0');
-      builder.serve('unchanged', '1.0.0');
-    });
+    await servePackages()
+      ..serve('added', '1.0.0')
+      ..serve('downgraded', '1.0.0')
+      ..serve('downgraded', '2.0.0')
+      ..serve('overridden', '1.0.0')
+      ..serve('removed', '1.0.0')
+      ..serve('source_changed', '1.0.0')
+      ..serve('upgraded', '1.0.0')
+      ..serve('upgraded', '2.0.0')
+      ..serve('unchanged', '1.0.0');
 
     await d.dir('description_changed_1', [
       d.libDir('description_changed'),
diff --git a/test/upgrade/report/shows_newer_available_versions_test.dart b/test/upgrade/report/shows_newer_available_versions_test.dart
index 54c32ad..39d4e1a 100644
--- a/test/upgrade/report/shows_newer_available_versions_test.dart
+++ b/test/upgrade/report/shows_newer_available_versions_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,28 +9,26 @@
 
 void main() {
   test('shows how many newer versions are available', () async {
-    await servePackages((builder) {
-      builder.serve('multiple_newer', '1.0.0');
-      builder.serve('multiple_newer', '1.0.1-unstable.1');
-      builder.serve('multiple_newer', '1.0.1');
-      builder.serve('multiple_newer', '1.0.2-unstable.1');
-      builder.serve('multiple_newer_stable', '1.0.0');
-      builder.serve('multiple_newer_stable', '1.0.1');
-      builder.serve('multiple_newer_stable', '1.0.2');
-      builder.serve('multiple_newer_unstable', '1.0.0');
-      builder.serve('multiple_newer_unstable', '1.0.1-unstable.1');
-      builder.serve('multiple_newer_unstable', '1.0.1-unstable.2');
-      builder.serve('multiple_newer_unstable2', '1.0.1-unstable.1');
-      builder.serve('multiple_newer_unstable2', '1.0.1-unstable.2');
-      builder.serve('multiple_newer_unstable2', '1.0.1-unstable.2');
-      builder.serve('no_newer', '1.0.0');
-      builder.serve('one_newer_unstable', '1.0.0');
-      builder.serve('one_newer_unstable', '1.0.1-unstable.1');
-      builder.serve('one_newer_unstable2', '1.0.1-unstable.1');
-      builder.serve('one_newer_unstable2', '1.0.1-unstable.2');
-      builder.serve('one_newer_stable', '1.0.0');
-      builder.serve('one_newer_stable', '1.0.1');
-    });
+    await servePackages()
+      ..serve('multiple_newer', '1.0.0')
+      ..serve('multiple_newer', '1.0.1-unstable.1')
+      ..serve('multiple_newer', '1.0.1')
+      ..serve('multiple_newer', '1.0.2-unstable.1')
+      ..serve('multiple_newer_stable', '1.0.0')
+      ..serve('multiple_newer_stable', '1.0.1')
+      ..serve('multiple_newer_stable', '1.0.2')
+      ..serve('multiple_newer_unstable', '1.0.0')
+      ..serve('multiple_newer_unstable', '1.0.1-unstable.1')
+      ..serve('multiple_newer_unstable', '1.0.1-unstable.2')
+      ..serve('multiple_newer_unstable2', '1.0.1-unstable.1')
+      ..serve('multiple_newer_unstable2', '1.0.1-unstable.2')
+      ..serve('no_newer', '1.0.0')
+      ..serve('one_newer_unstable', '1.0.0')
+      ..serve('one_newer_unstable', '1.0.1-unstable.1')
+      ..serve('one_newer_unstable2', '1.0.1-unstable.1')
+      ..serve('one_newer_unstable2', '1.0.1-unstable.2')
+      ..serve('one_newer_stable', '1.0.0')
+      ..serve('one_newer_stable', '1.0.1');
 
     // Constraint everything to the first version.
     await d.appDir({
diff --git a/test/upgrade/report/shows_number_of_changed_dependencies_test.dart b/test/upgrade/report/shows_number_of_changed_dependencies_test.dart
index 1c79302..91ba5aa 100644
--- a/test/upgrade/report/shows_number_of_changed_dependencies_test.dart
+++ b/test/upgrade/report/shows_number_of_changed_dependencies_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -13,11 +11,10 @@
   test(
       'does not show how many newer versions are available for '
       'packages that are locked and not being upgraded', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0');
-      builder.serve('c', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0')
+      ..serve('c', '2.0.0');
 
     await d.appDir({'a': 'any'}).create();
 
diff --git a/test/upgrade/report/shows_pub_outdated_test.dart b/test/upgrade/report/shows_pub_outdated_test.dart
index 11d50da..0fdb322 100644
--- a/test/upgrade/report/shows_pub_outdated_test.dart
+++ b/test/upgrade/report/shows_pub_outdated_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -11,24 +9,23 @@
 
 void main() {
   test('shows pub outdated', () async {
-    await servePackages((builder) {
-      builder.serve('multiple_newer', '1.0.0');
-      builder.serve('multiple_newer', '1.0.1-unstable.1');
-      builder.serve('multiple_newer', '1.0.1');
-      builder.serve('multiple_newer', '1.0.2-unstable.1');
-      builder.serve('multiple_newer', '1.0.2-unstable.2');
-      builder.serve('multiple_newer_stable', '1.0.0');
-      builder.serve('multiple_newer_stable', '1.0.1');
-      builder.serve('multiple_newer_stable', '1.0.2');
-      builder.serve('multiple_newer_unstable', '1.0.0');
-      builder.serve('multiple_newer_unstable', '1.0.1-unstable.1');
-      builder.serve('multiple_newer_unstable', '1.0.1-unstable.2');
-      builder.serve('no_newer', '1.0.0');
-      builder.serve('one_newer_unstable', '1.0.0');
-      builder.serve('one_newer_unstable', '1.0.1-unstable.1');
-      builder.serve('one_newer_stable', '1.0.0');
-      builder.serve('one_newer_stable', '1.0.1');
-    });
+    await servePackages()
+      ..serve('multiple_newer', '1.0.0')
+      ..serve('multiple_newer', '1.0.1-unstable.1')
+      ..serve('multiple_newer', '1.0.1')
+      ..serve('multiple_newer', '1.0.2-unstable.1')
+      ..serve('multiple_newer', '1.0.2-unstable.2')
+      ..serve('multiple_newer_stable', '1.0.0')
+      ..serve('multiple_newer_stable', '1.0.1')
+      ..serve('multiple_newer_stable', '1.0.2')
+      ..serve('multiple_newer_unstable', '1.0.0')
+      ..serve('multiple_newer_unstable', '1.0.1-unstable.1')
+      ..serve('multiple_newer_unstable', '1.0.1-unstable.2')
+      ..serve('no_newer', '1.0.0')
+      ..serve('one_newer_unstable', '1.0.0')
+      ..serve('one_newer_unstable', '1.0.1-unstable.1')
+      ..serve('one_newer_stable', '1.0.0')
+      ..serve('one_newer_stable', '1.0.1');
 
     // Constraint everything to the first version.
     await d.appDir({
diff --git a/test/upgrade/upgrade_major_versions_test.dart b/test/upgrade/upgrade_major_versions_test.dart
index 7ab389b..c5f0994 100644
--- a/test/upgrade/upgrade_major_versions_test.dart
+++ b/test/upgrade/upgrade_major_versions_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -12,14 +10,13 @@
 void main() {
   group('pub upgrade --major-versions', () {
     test('bumps dependency constraints and shows summary report', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('bar', '0.1.0');
-        builder.serve('bar', '0.2.0');
-        builder.serve('baz', '1.0.0');
-        builder.serve('baz', '1.0.1');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('bar', '0.1.0')
+        ..serve('bar', '0.2.0')
+        ..serve('baz', '1.0.0')
+        ..serve('baz', '1.0.1');
 
       await d.appDir({
         'foo': '^1.0.0',
@@ -44,23 +41,21 @@
         'bar': '^0.2.0',
         'baz': '^1.0.0',
       }).validate();
-
-      await d.appPackagesFile({
-        'foo': '2.0.0',
-        'bar': '0.2.0',
-        'baz': '1.0.1',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '0.2.0'),
+        d.packageConfigEntry(name: 'baz', version: '1.0.1'),
+      ]).validate();
     });
 
     test('bumps dev_dependency constraints and shows summary report', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('bar', '0.1.0');
-        builder.serve('bar', '0.2.0');
-        builder.serve('baz', '1.0.0');
-        builder.serve('baz', '1.0.1');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('bar', '0.1.0')
+        ..serve('bar', '0.2.0')
+        ..serve('baz', '1.0.0')
+        ..serve('baz', '1.0.1');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -96,20 +91,18 @@
         }),
       ]).validate();
 
-      await d.appPackagesFile({
-        'foo': '2.0.0',
-        'bar': '0.2.0',
-        'baz': '1.0.1',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '0.2.0'),
+        d.packageConfigEntry(name: 'baz', version: '1.0.1'),
+      ]).validate();
     });
 
     test('upgrades only the selected package', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('bar', '0.1.0');
-        builder.serve('bar', '0.2.0');
-      });
+      final server = await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('bar', '0.1.0');
 
       await d.appDir({
         'foo': '^1.0.0',
@@ -118,6 +111,8 @@
 
       await pubGet();
 
+      server.serve('bar', '0.1.1');
+
       // 1 constraint should be updated
       await pubUpgrade(
         args: ['--major-versions', 'foo'],
@@ -132,15 +127,17 @@
         'bar': '^0.1.0',
       }).validate();
 
-      await d.appPackagesFile({'foo': '2.0.0', 'bar': '0.1.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '0.1.0'),
+      ]).validate();
     });
 
     test('chooses the latest version where possible', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('foo', '3.0.0');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('foo', '3.0.0');
 
       await d.appDir({'foo': '^1.0.0'}).create();
 
@@ -164,17 +161,17 @@
         d.file('pubspec.lock', contains('3.0.0'))
       ]).validate();
 
-      await d.appPackagesFile({'foo': '3.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '3.0.0'),
+      ]).validate();
     });
 
     test('overridden dependencies - no resolution', () async {
-      await servePackages(
-        (builder) => builder
-          ..serve('foo', '1.0.0', deps: {'bar': '^2.0.0'})
-          ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
-          ..serve('bar', '1.0.0', deps: {'foo': '^1.0.0'})
-          ..serve('bar', '2.0.0', deps: {'foo': '^2.0.0'}),
-      );
+      await servePackages()
+        ..serve('foo', '1.0.0', deps: {'bar': '^2.0.0'})
+        ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
+        ..serve('bar', '1.0.0', deps: {'foo': '^1.0.0'})
+        ..serve('bar', '2.0.0', deps: {'foo': '^2.0.0'});
 
       await d.dir(appPath, [
         d.pubspec({
@@ -218,23 +215,25 @@
         })
       ]).validate();
 
-      await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      ]).validate();
     });
 
     test('upgrade should not downgrade any versions', () async {
       /// The version solver solves the packages with the least number of
       /// versions remaining, so we add more 'bar' packages to force 'foo' to be
       /// resolved first
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0', pubspec: {
           'dependencies': {'bar': '1.0.0'}
-        });
-        builder.serve('bar', '1.0.0');
-        builder.serve('bar', '2.0.0');
-        builder.serve('bar', '3.0.0');
-        builder.serve('bar', '4.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0')
+        ..serve('bar', '2.0.0')
+        ..serve('bar', '3.0.0')
+        ..serve('bar', '4.0.0');
 
       await d.appDir({
         'foo': '^1.0.0',
@@ -257,10 +256,10 @@
         'bar': '^4.0.0',
       }).validate();
 
-      await d.appPackagesFile({
-        'foo': '1.0.0',
-        'bar': '4.0.0',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '4.0.0'),
+      ]).validate();
     });
   });
 }
diff --git a/test/upgrade/upgrade_null_safety_test.dart b/test/upgrade/upgrade_null_safety_test.dart
index 9c7dad0..557c589 100644
--- a/test/upgrade/upgrade_null_safety_test.dart
+++ b/test/upgrade/upgrade_null_safety_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
@@ -11,29 +9,28 @@
 void main() {
   group('pub upgrade --null-safety', () {
     setUp(() async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'environment': {'sdk': '>=2.10.0<3.0.0'},
-        });
-        builder.serve('foo', '2.0.0', pubspec: {
+        })
+        ..serve('foo', '2.0.0', pubspec: {
           'environment': {'sdk': '>=2.12.0<3.0.0'},
-        });
-        builder.serve('bar', '1.0.0', pubspec: {
+        })
+        ..serve('bar', '1.0.0', pubspec: {
           'environment': {'sdk': '>=2.9.0<3.0.0'},
-        });
-        builder.serve('bar', '2.0.0-nullsafety.0', pubspec: {
+        })
+        ..serve('bar', '2.0.0-nullsafety.0', pubspec: {
           'environment': {'sdk': '>=2.12.0<3.0.0'},
-        });
-        builder.serve('baz', '1.0.0', pubspec: {
+        })
+        ..serve('baz', '1.0.0', pubspec: {
           'environment': {'sdk': '>=2.9.0<3.0.0'},
-        });
-        builder.serve('has_conflict', '1.0.0', pubspec: {
+        })
+        ..serve('has_conflict', '1.0.0', pubspec: {
           'environment': {'sdk': '>=2.9.0<3.0.0'},
-        });
-        builder.serve('has_conflict', '2.0.0', pubspec: {
+        })
+        ..serve('has_conflict', '2.0.0', pubspec: {
           'environment': {'sdk': '>=2.13.0<3.0.0'},
         });
-      });
     });
 
     test('upgrades to null-safety versions', () async {
diff --git a/test/validator/changelog_test.dart b/test/validator/changelog_test.dart
index be166c3..5bef662 100644
--- a/test/validator/changelog_test.dart
+++ b/test/validator/changelog_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/changelog.dart';
diff --git a/test/validator/compiled_dartdoc_test.dart b/test/validator/compiled_dartdoc_test.dart
index a656958..f1d3d6d 100644
--- a/test/validator/compiled_dartdoc_test.dart
+++ b/test/validator/compiled_dartdoc_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/compiled_dartdoc.dart';
diff --git a/test/validator/dependency_override_test.dart b/test/validator/dependency_override_test.dart
index f459119..21a437d 100644
--- a/test/validator/dependency_override_test.dart
+++ b/test/validator/dependency_override_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/dependency_override.dart';
diff --git a/test/validator/dependency_test.dart b/test/validator/dependency_test.dart
index 00ab0c3..db9db15 100644
--- a/test/validator/dependency_test.dart
+++ b/test/validator/dependency_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -29,7 +27,7 @@
 
 /// Sets up a test package with dependency [dep] and mocks a server with
 /// [hostedVersions] of the package available.
-Future setUpDependency(Map dep, {List<String> hostedVersions}) {
+Future setUpDependency(Map dep, {List<String>? hostedVersions}) {
   useMockClient(MockClient((request) {
     expect(request.method, equals('GET'));
     expect(request.url.path, equals('/api/packages/foo'));
diff --git a/test/validator/deprecated_fields_test.dart b/test/validator/deprecated_fields_test.dart
index a13774b..ee4551d 100644
--- a/test/validator/deprecated_fields_test.dart
+++ b/test/validator/deprecated_fields_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/deprecated_fields.dart';
diff --git a/test/validator/directory_test.dart b/test/validator/directory_test.dart
index 5a5ec99..7cc53b6 100644
--- a/test/validator/directory_test.dart
+++ b/test/validator/directory_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/directory.dart';
diff --git a/test/validator/executable_test.dart b/test/validator/executable_test.dart
index 555f768..d9973e7 100644
--- a/test/validator/executable_test.dart
+++ b/test/validator/executable_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/executable.dart';
diff --git a/test/validator/flutter_constraint_test.dart b/test/validator/flutter_constraint_test.dart
index 685c59d..b7bdf7c 100644
--- a/test/validator/flutter_constraint_test.dart
+++ b/test/validator/flutter_constraint_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -20,7 +18,7 @@
 }
 
 Future<void> setup({
-  String flutterConstraint,
+  String? flutterConstraint,
 }) async {
   final fakeFlutterRoot =
       d.dir('fake_flutter_root', [d.file('version', '1.23.0')]);
diff --git a/test/validator/flutter_plugin_format_test.dart b/test/validator/flutter_plugin_format_test.dart
index cddeaf9..0c24bda 100644
--- a/test/validator/flutter_plugin_format_test.dart
+++ b/test/validator/flutter_plugin_format_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/flutter_plugin_format.dart';
diff --git a/test/validator/gitignore_test.dart b/test/validator/gitignore_test.dart
index d6140f5..f7b02fe 100644
--- a/test/validator/gitignore_test.dart
+++ b/test/validator/gitignore_test.dart
@@ -2,20 +2,25 @@
 // 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.
 
-// @dart=2.10
+import 'dart:io';
 
+import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 
-Future<void> expectValidation(error, int exitCode) async {
+Future<void> expectValidation(
+  error,
+  int exitCode, {
+  String? workingDirectory,
+}) async {
   await runPub(
     error: error,
     args: ['publish', '--dry-run'],
     environment: {'_PUB_TEST_SDK_VERSION': '2.12.0'},
-    workingDirectory: d.path(appPath),
+    workingDirectory: workingDirectory ?? d.path(appPath),
     exitCode: exitCode,
   );
 }
@@ -46,4 +51,55 @@
         ]),
         exit_codes.DATA);
   });
+
+  test('Should also consider gitignores from above the package root', () async {
+    await d.git('reporoot', [
+      d.dir(
+        'myapp',
+        [
+          d.file('foo.txt'),
+          ...d.validPackage.contents,
+        ],
+      ),
+    ]).create();
+    final packageRoot = p.join(d.sandbox, 'reporoot', 'myapp');
+    await pubGet(
+        environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'},
+        workingDirectory: packageRoot);
+
+    await expectValidation(contains('Package has 0 warnings.'), 0,
+        workingDirectory: packageRoot);
+
+    await d.dir('reporoot', [
+      d.file('.gitignore', '*.txt'),
+    ]).create();
+
+    await expectValidation(
+        allOf([
+          contains('Package has 1 warning.'),
+          contains('foo.txt'),
+          contains(
+              'Consider adjusting your `.gitignore` files to not ignore those files'),
+        ]),
+        exit_codes.DATA,
+        workingDirectory: packageRoot);
+  });
+
+  test('Should not follow symlinks', () async {
+    await d.git('myapp', [
+      ...d.validPackage.contents,
+    ]).create();
+    final packageRoot = p.join(d.sandbox, 'myapp');
+    await pubGet(
+        environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'},
+        workingDirectory: packageRoot);
+
+    Link(p.join(packageRoot, '.abc', 'itself')).createSync(
+      packageRoot,
+      recursive: true,
+    );
+
+    await expectValidation(contains('Package has 0 warnings.'), 0,
+        workingDirectory: packageRoot);
+  });
 }
diff --git a/test/validator/language_version_test.dart b/test/validator/language_version_test.dart
index 37431d4..39c9465 100644
--- a/test/validator/language_version_test.dart
+++ b/test/validator/language_version_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/language_version.dart';
@@ -17,7 +15,7 @@
     LanguageVersionValidator(entrypoint);
 
 Future<void> setup(
-    {String sdkConstraint, String libraryLanguageVersion}) async {
+    {required String sdkConstraint, String? libraryLanguageVersion}) async {
   await d.validPackage.create();
   await d.dir(appPath, [
     d.pubspec({
diff --git a/test/validator/leak_detection_test.dart b/test/validator/leak_detection_test.dart
index ee7d5d3..c055e59 100644
--- a/test/validator/leak_detection_test.dart
+++ b/test/validator/leak_detection_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/leak_detection.dart';
diff --git a/test/validator/license_test.dart b/test/validator/license_test.dart
index 77225b5..8717c75 100644
--- a/test/validator/license_test.dart
+++ b/test/validator/license_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/io.dart';
diff --git a/test/validator/name_test.dart b/test/validator/name_test.dart
index 5639c09..7cbb51c 100644
--- a/test/validator/name_test.dart
+++ b/test/validator/name_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/io.dart';
diff --git a/test/validator/null_safety_mixed_mode_test.dart b/test/validator/null_safety_mixed_mode_test.dart
index 6ec4746..d62d051 100644
--- a/test/validator/null_safety_mixed_mode_test.dart
+++ b/test/validator/null_safety_mixed_mode_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -20,7 +18,7 @@
 }
 
 Future<void> setup({
-  String sdkConstraint,
+  required String sdkConstraint,
   Map dependencies = const {},
   Map devDependencies = const {},
   List<d.Descriptor> extraFiles = const [],
@@ -47,14 +45,13 @@
   group('should consider a package valid if it', () {
     test('is not opting in to null-safety, but depends on package that is',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'foo',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.12.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.12.0<3.0.0'}
+        },
       );
 
       await setup(
@@ -63,14 +60,13 @@
     });
     test('is opting in to null-safety and depends on package that is',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'foo',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.12.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.12.0<3.0.0'}
+        },
       );
 
       await setup(
@@ -80,14 +76,13 @@
 
     test('is opting in to null-safety has dev_dependency that is not',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'foo',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.9.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.9.0<3.0.0'}
+        },
       );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0', devDependencies: {
@@ -100,14 +95,13 @@
   group('should consider a package invalid if it', () {
     test('is opting in to null-safety, but depends on package that is not',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'foo',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.9.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.9.0<3.0.0'}
+        },
       );
 
       await setup(
@@ -136,17 +130,16 @@
     test(
         'is opting in to null-safety, but depends on package has file opting out',
         () async {
-      await servePackages(
-        (server) => server.serve('foo', '0.0.1', pubspec: {
-          'environment': {'sdk': '>=2.12.0<3.0.0'}
-        }, contents: [
-          d.dir('lib', [
-            d.file('foo.dart', '''
+      final server = await servePackages();
+      server.serve('foo', '0.0.1', pubspec: {
+        'environment': {'sdk': '>=2.12.0<3.0.0'}
+      }, contents: [
+        d.dir('lib', [
+          d.file('foo.dart', '''
 // @dart = 2.9
           ''')
-          ])
-        ]),
-      );
+        ])
+      ]);
 
       await setup(
           sdkConstraint: '>=2.12.0 <3.0.0', dependencies: {'foo': '^0.0.1'});
diff --git a/test/validator/pubspec_field_test.dart b/test/validator/pubspec_field_test.dart
index 7d4dbd7..8e6fefc 100644
--- a/test/validator/pubspec_field_test.dart
+++ b/test/validator/pubspec_field_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/pubspec_field.dart';
@@ -57,7 +55,7 @@
 
     test('has executables', () async {
       var pkg = packageMap('test_pkg', '1.0.0');
-      pkg['executables'] = <String, String>{
+      pkg['executables'] = <String, String?>{
         'test_pkg': null,
         'test_pkg_helper': 'helper',
       };
diff --git a/test/validator/pubspec_test.dart b/test/validator/pubspec_test.dart
index 0d964ce..9942220 100644
--- a/test/validator/pubspec_test.dart
+++ b/test/validator/pubspec_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/validator/pubspec.dart';
 import 'package:test/test.dart';
 
diff --git a/test/validator/pubspec_typo_test.dart b/test/validator/pubspec_typo_test.dart
index efc731b..ab2a547 100644
--- a/test/validator/pubspec_typo_test.dart
+++ b/test/validator/pubspec_typo_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/pubspec_typo.dart';
diff --git a/test/validator/readme_test.dart b/test/validator/readme_test.dart
index 132ff6c..6519c6f 100644
--- a/test/validator/readme_test.dart
+++ b/test/validator/readme_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/io.dart';
diff --git a/test/validator/relative_version_numbering_test.dart b/test/validator/relative_version_numbering_test.dart
index 0a9a1f7..2f3da5c 100644
--- a/test/validator/relative_version_numbering_test.dart
+++ b/test/validator/relative_version_numbering_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/relative_version_numbering.dart';
@@ -15,10 +13,10 @@
 
 Validator validator(Entrypoint entrypoint) => RelativeVersionNumberingValidator(
       entrypoint,
-      Uri.parse(globalPackageServer.url),
+      Uri.parse(globalServer.url),
     );
 
-Future<void> setup({String sdkConstraint}) async {
+Future<void> setup({required String sdkConstraint}) async {
   await d.validPackage.create();
   await d.dir(appPath, [
     d.pubspec({
@@ -35,14 +33,13 @@
   group('should consider a package valid if it', () {
     test('is not opting in to null-safety with previous non-null-safe version',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.9.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.9.0<3.0.0'}
+        },
       );
 
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
@@ -52,23 +49,21 @@
     test(
         'is not opting in to null-safety with previous non-null-safe version. '
         'Even with a later null-safe version', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '2.0.0',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '2.0.0',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
       await expectValidation(validator);
@@ -76,14 +71,13 @@
 
     test('is opting in to null-safety with previous null-safe version',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.12.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.12.0<3.0.0'}
+        },
       );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
@@ -93,14 +87,13 @@
     test(
         'is opting in to null-safety using a pre-release of 2.12.0 '
         'with previous null-safe version', () async {
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.12.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.12.0<3.0.0'}
+        },
       );
 
       await setup(sdkConstraint: '>=2.12.0-dev <3.0.0');
@@ -110,23 +103,21 @@
     test(
         'is opting in to null-safety with previous null-safe version. '
         'Even with a later non-null-safe version', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '2.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '2.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
       await expectValidation(validator);
@@ -134,13 +125,13 @@
 
     test('is opting in to null-safety with no existing versions', () async {
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
-      await servePackages((x) => x);
+      await servePackages();
       await expectValidation(validator);
     });
 
     test('is not opting in to null-safety with no existing versions', () async {
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
-      await servePackages((x) => x);
+      await servePackages();
 
       await expectValidation(validator);
     });
@@ -148,23 +139,21 @@
     test(
         'is not opting in to null-safety with previous null-safe stable version. '
         'With an in-between not null-safe prerelease', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '0.0.2-dev',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '0.0.2-dev',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
       await expectValidation(validator);
@@ -173,23 +162,21 @@
     test(
         'opts in to null-safety, with previous stable version not-null-safe. '
         'With an in-between non-null-safe prerelease', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '0.0.2-dev',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '0.0.2-dev',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
       await expectValidation(validator);
@@ -199,14 +186,13 @@
   group('should warn if ', () {
     test('opts in to null-safety, with previous version not-null-safe',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.9.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.9.0<3.0.0'}
+        },
       );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
@@ -217,14 +203,13 @@
         'is not opting in to null-safety with no existing stable versions. '
         'With a previous in-between null-safe prerelease', () async {
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.2-dev',
-          pubspec: {
-            'environment': {'sdk': '>=2.12.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.2-dev',
+        pubspec: {
+          'environment': {'sdk': '>=2.12.0<3.0.0'}
+        },
       );
 
       await expectValidation(validator, hints: isNotEmpty);
@@ -232,23 +217,21 @@
     test(
         'is not opting in to null-safety with previous non-null-safe stable version. '
         'With an in-between null-safe prerelease', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '0.0.2-dev',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '0.0.2-dev',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
       await expectValidation(validator, hints: isNotEmpty);
@@ -257,23 +240,21 @@
     test(
         'opts in to null-safety, with previous version not-null-safe. '
         'Even with a later null-safe version', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '2.0.0',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '2.0.0',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
       await expectValidation(validator, hints: isNotEmpty);
@@ -281,14 +262,13 @@
 
     test('is not opting in to null-safety with previous null-safe version',
         () async {
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.1',
-          pubspec: {
-            'environment': {'sdk': '>=2.12.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.1',
+        pubspec: {
+          'environment': {'sdk': '>=2.12.0<3.0.0'}
+        },
       );
 
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
@@ -298,23 +278,21 @@
     test(
         'is not opting in to null-safety with previous null-safe version. '
         'Even with a later non-null-safe version', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '2.0.0',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '2.0.0',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.9.0 <3.0.0');
       await expectValidation(validator, hints: isNotEmpty);
@@ -323,23 +301,21 @@
     test(
         'is opting in to null-safety with previous null-safe stable version. '
         'with an in-between non-null-safe prerelease', () async {
-      await servePackages(
-        (server) => server
-          ..serve(
-            'test_pkg',
-            '0.0.1',
-            pubspec: {
-              'environment': {'sdk': '>=2.12.0<3.0.0'}
-            },
-          )
-          ..serve(
-            'test_pkg',
-            '0.0.2-dev',
-            pubspec: {
-              'environment': {'sdk': '>=2.9.0<3.0.0'}
-            },
-          ),
-      );
+      await servePackages()
+        ..serve(
+          'test_pkg',
+          '0.0.1',
+          pubspec: {
+            'environment': {'sdk': '>=2.12.0<3.0.0'}
+          },
+        )
+        ..serve(
+          'test_pkg',
+          '0.0.2-dev',
+          pubspec: {
+            'environment': {'sdk': '>=2.9.0<3.0.0'}
+          },
+        );
 
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
       await expectValidation(validator, hints: isNotEmpty);
@@ -349,14 +325,13 @@
         'is opting in to null-safety with no existing stable versions. '
         'With a previous non-null-safe prerelease', () async {
       await setup(sdkConstraint: '>=2.12.0 <3.0.0');
-      await servePackages(
-        (server) => server.serve(
-          'test_pkg',
-          '0.0.2-dev',
-          pubspec: {
-            'environment': {'sdk': '>=2.9.0<3.0.0'}
-          },
-        ),
+      final server = await servePackages();
+      server.serve(
+        'test_pkg',
+        '0.0.2-dev',
+        pubspec: {
+          'environment': {'sdk': '>=2.9.0<3.0.0'}
+        },
       );
       await expectValidation(validator, hints: isNotEmpty);
     });
diff --git a/test/validator/sdk_constraint_test.dart b/test/validator/sdk_constraint_test.dart
index 1310d17..c24adf2 100644
--- a/test/validator/sdk_constraint_test.dart
+++ b/test/validator/sdk_constraint_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
 import 'package:pub/src/validator/sdk_constraint.dart';
diff --git a/test/validator/size_test.dart b/test/validator/size_test.dart
index 37a746d..3a22e60 100644
--- a/test/validator/size_test.dart
+++ b/test/validator/size_test.dart
@@ -2,10 +2,7 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
-import 'dart:math' as math;
 
 import 'package:pub/src/validator/size.dart';
 import 'package:test/test.dart';
@@ -20,7 +17,7 @@
 
 Future<void> expectSizeValidationError(Matcher matcher) async {
   await expectValidation(
-    size(100 * math.pow(2, 20) + 1),
+    size(100 * 1048577 /*2^20 +1*/),
     errors: contains(matcher),
   );
 }
@@ -30,7 +27,7 @@
     await d.validPackage.create();
 
     await expectValidation(size(100));
-    await expectValidation(size(100 * math.pow(2, 20)));
+    await expectValidation(size(100 * 1048576 /*2^20*/));
   });
 
   group('considers a package invalid if it is more than 100 MB', () {
diff --git a/test/validator/strict_dependencies_test.dart b/test/validator/strict_dependencies_test.dart
index 0456413..7b7962a 100644
--- a/test/validator/strict_dependencies_test.dart
+++ b/test/validator/strict_dependencies_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub/src/entrypoint.dart';
 import 'package:pub/src/validator.dart';
@@ -67,8 +65,8 @@
 
     for (var port in ['import', 'export']) {
       for (var isDev in [false, true]) {
-        Map<String, String> deps;
-        Map<String, String> devDeps;
+        Map<String, String>? deps;
+        Map<String, String>? devDeps;
 
         if (isDev) {
           devDeps = {'silly_monkey': '^1.2.3'};
diff --git a/test/validator/utils.dart b/test/validator/utils.dart
index 7eda0fd..5123683 100644
--- a/test/validator/utils.dart
+++ b/test/validator/utils.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'package:test/test.dart';
 
 import '../test_pub.dart';
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index d9ea6a0..d36a16f 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -31,6 +29,8 @@
   group('override', override);
   group('downgrade', downgrade);
   group('features', features, skip: true);
+
+  group('regressions', regressions);
 }
 
 void basicGraph() {
@@ -40,14 +40,13 @@
   });
 
   test('simple dependency tree', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'aa': '1.0.0', 'ab': '1.0.0'});
-      builder.serve('aa', '1.0.0');
-      builder.serve('ab', '1.0.0');
-      builder.serve('b', '1.0.0', deps: {'ba': '1.0.0', 'bb': '1.0.0'});
-      builder.serve('ba', '1.0.0');
-      builder.serve('bb', '1.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'aa': '1.0.0', 'ab': '1.0.0'})
+      ..serve('aa', '1.0.0')
+      ..serve('ab', '1.0.0')
+      ..serve('b', '1.0.0', deps: {'ba': '1.0.0', 'bb': '1.0.0'})
+      ..serve('ba', '1.0.0')
+      ..serve('bb', '1.0.0');
 
     await d.appDir({'a': '1.0.0', 'b': '1.0.0'}).create();
     await expectResolves(result: {
@@ -61,15 +60,14 @@
   });
 
   test('shared dependency with overlapping constraints', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'shared': '>=2.0.0 <4.0.0'});
-      builder.serve('b', '1.0.0', deps: {'shared': '>=3.0.0 <5.0.0'});
-      builder.serve('shared', '2.0.0');
-      builder.serve('shared', '3.0.0');
-      builder.serve('shared', '3.6.9');
-      builder.serve('shared', '4.0.0');
-      builder.serve('shared', '5.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'shared': '>=2.0.0 <4.0.0'})
+      ..serve('b', '1.0.0', deps: {'shared': '>=3.0.0 <5.0.0'})
+      ..serve('shared', '2.0.0')
+      ..serve('shared', '3.0.0')
+      ..serve('shared', '3.6.9')
+      ..serve('shared', '4.0.0')
+      ..serve('shared', '5.0.0');
 
     await d.appDir({'a': '1.0.0', 'b': '1.0.0'}).create();
     await expectResolves(
@@ -79,16 +77,15 @@
   test(
       'shared dependency where dependent version in turn affects other '
       'dependencies', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '1.0.1', deps: {'bang': '1.0.0'});
-      builder.serve('foo', '1.0.2', deps: {'whoop': '1.0.0'});
-      builder.serve('foo', '1.0.3', deps: {'zoop': '1.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'foo': '<=1.0.1'});
-      builder.serve('bang', '1.0.0');
-      builder.serve('whoop', '1.0.0');
-      builder.serve('zoop', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '1.0.1', deps: {'bang': '1.0.0'})
+      ..serve('foo', '1.0.2', deps: {'whoop': '1.0.0'})
+      ..serve('foo', '1.0.3', deps: {'zoop': '1.0.0'})
+      ..serve('bar', '1.0.0', deps: {'foo': '<=1.0.1'})
+      ..serve('bang', '1.0.0')
+      ..serve('whoop', '1.0.0')
+      ..serve('zoop', '1.0.0');
 
     await d.appDir({'foo': '<=1.0.2', 'bar': '1.0.0'}).create();
     await expectResolves(
@@ -96,23 +93,21 @@
   });
 
   test('circular dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'foo': '1.0.0'});
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('bar', '1.0.0', deps: {'foo': '1.0.0'});
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
   });
 
   test('removed dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0');
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '2.0.0', deps: {'baz': '1.0.0'});
-      builder.serve('baz', '1.0.0', deps: {'foo': '2.0.0'});
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0')
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '2.0.0', deps: {'baz': '1.0.0'})
+      ..serve('baz', '1.0.0', deps: {'foo': '2.0.0'});
 
     await d.appDir({'foo': '1.0.0', 'bar': 'any'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'}, tries: 2);
@@ -121,14 +116,13 @@
 
 void withLockFile() {
   test('with compatible locked dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
-      builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '1.0.1');
-      builder.serve('bar', '1.0.2');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '1.0.1', deps: {'bar': '1.0.1'})
+      ..serve('foo', '1.0.2', deps: {'bar': '1.0.2'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '1.0.1')
+      ..serve('bar', '1.0.2');
 
     await d.appDir({'foo': '1.0.1'}).create();
     await expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
@@ -138,14 +132,13 @@
   });
 
   test('with incompatible locked dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
-      builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '1.0.1');
-      builder.serve('bar', '1.0.2');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '1.0.1', deps: {'bar': '1.0.1'})
+      ..serve('foo', '1.0.2', deps: {'bar': '1.0.2'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '1.0.1')
+      ..serve('bar', '1.0.2');
 
     await d.appDir({'foo': '1.0.1'}).create();
     await expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
@@ -155,15 +148,14 @@
   });
 
   test('with unrelated locked dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
-      builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '1.0.1');
-      builder.serve('bar', '1.0.2');
-      builder.serve('baz', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '1.0.1', deps: {'bar': '1.0.1'})
+      ..serve('foo', '1.0.2', deps: {'bar': '1.0.2'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '1.0.1')
+      ..serve('bar', '1.0.2')
+      ..serve('baz', '1.0.0');
 
     await d.appDir({'baz': '1.0.0'}).create();
     await expectResolves(result: {'baz': '1.0.0'});
@@ -175,17 +167,16 @@
   test(
       'unlocks dependencies if necessary to ensure that a new '
       'dependency is satisfied', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'baz': '<2.0.0'});
-      builder.serve('baz', '1.0.0', deps: {'qux': '<2.0.0'});
-      builder.serve('qux', '1.0.0');
-      builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
-      builder.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
-      builder.serve('baz', '2.0.0', deps: {'qux': '<3.0.0'});
-      builder.serve('qux', '2.0.0');
-      builder.serve('newdep', '2.0.0', deps: {'baz': '>=1.5.0'});
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '<2.0.0'})
+      ..serve('bar', '1.0.0', deps: {'baz': '<2.0.0'})
+      ..serve('baz', '1.0.0', deps: {'qux': '<2.0.0'})
+      ..serve('qux', '1.0.0')
+      ..serve('foo', '2.0.0', deps: {'bar': '<3.0.0'})
+      ..serve('bar', '2.0.0', deps: {'baz': '<3.0.0'})
+      ..serve('baz', '2.0.0', deps: {'qux': '<3.0.0'})
+      ..serve('qux', '2.0.0')
+      ..serve('newdep', '2.0.0', deps: {'baz': '>=1.5.0'});
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(result: {
@@ -209,11 +200,10 @@
   test(
       "produces a nice message for a locked dependency that's the only "
       'version of its package', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '>=2.0.0'});
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '>=2.0.0'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '2.0.0');
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '2.0.0'});
@@ -229,30 +219,27 @@
 
 void rootDependency() {
   test('with root source', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'myapp': 'any'});
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', deps: {'myapp': 'any'});
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(result: {'foo': '1.0.0'});
   });
 
   test('with mismatched sources', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'myapp': 'any'});
-      builder.serve('bar', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'myapp': 'any'})
+      ..serve('bar', '1.0.0', deps: {
         'myapp': {'git': 'http://nowhere.com/'}
       });
-    });
 
     await d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
   });
 
   test('with wrong version', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'myapp': '>0.0.0'});
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', deps: {'myapp': '>0.0.0'});
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -265,10 +252,9 @@
 
 void devDependency() {
   test("includes root package's dev dependencies", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('bar', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -281,10 +267,9 @@
   });
 
   test("includes dev dependency's transitive dependencies", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('bar', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -297,10 +282,9 @@
   });
 
   test("ignores transitive dependency's dev dependencies", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'dev_dependencies': {'bar': '1.0.0'}
-      });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'dev_dependencies': {'bar': '1.0.0'}
     });
 
     await d.appDir({'foo': '1.0.0'}).create();
@@ -309,11 +293,10 @@
 
   group('with both a dev and regular dependency', () {
     test('succeeds when both are satisfied', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('foo', '3.0.0');
-      });
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('foo', '3.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -327,9 +310,8 @@
     });
 
     test("fails when main dependency isn't satisfied", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '3.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '3.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -347,9 +329,8 @@
     });
 
     test("fails when dev dependency isn't satisfied", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -367,9 +348,8 @@
     });
 
     test('fails when dev and main constraints are incompatible', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -386,9 +366,8 @@
     });
 
     test('fails when dev and main sources are incompatible', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -407,9 +386,8 @@
     });
 
     test('fails when dev and main descriptions are incompatible', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.dir(appPath, [
         d.pubspec({
@@ -433,10 +411,9 @@
 
 void unsolvable() {
   test('no version that matches constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '2.1.3');
-    });
+    await servePackages()
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '2.1.3');
 
     await d.appDir({'foo': '>=1.0.0 <2.0.0'}).create();
     await expectResolves(error: equalsIgnoringWhitespace("""
@@ -446,12 +423,11 @@
   });
 
   test('no version that matches combined constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'shared': '>=2.0.0 <3.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'shared': '>=2.9.0 <4.0.0'});
-      builder.serve('shared', '2.5.0');
-      builder.serve('shared', '3.5.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'shared': '>=2.0.0 <3.0.0'})
+      ..serve('bar', '1.0.0', deps: {'shared': '>=2.9.0 <4.0.0'})
+      ..serve('shared', '2.5.0')
+      ..serve('shared', '3.5.0');
 
     await d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -466,12 +442,11 @@
   });
 
   test('disjoint constraints', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'shared': '<=2.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'shared': '>3.0.0'});
-      builder.serve('shared', '2.0.0');
-      builder.serve('shared', '4.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'shared': '<=2.0.0'})
+      ..serve('bar', '1.0.0', deps: {'shared': '>3.0.0'})
+      ..serve('shared', '2.0.0')
+      ..serve('shared', '4.0.0');
 
     await d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -483,20 +458,18 @@
   });
 
   test('mismatched descriptions', () async {
-    var otherServer = await PackageServer.start((builder) {
-      builder.serve('shared', '1.0.0');
-    });
+    var otherServer = await startPackageServer();
+    otherServer.serve('shared', '1.0.0');
 
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'shared': '1.0.0'});
-      builder.serve('bar', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'shared': '1.0.0'})
+      ..serve('bar', '1.0.0', deps: {
         'shared': {
           'hosted': {'name': 'shared', 'url': otherServer.url},
           'version': '1.0.0'
         }
-      });
-      builder.serve('shared', '1.0.0');
-    });
+      })
+      ..serve('shared', '1.0.0');
 
     await d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
 
@@ -515,13 +488,12 @@
   test('mismatched sources', () async {
     await d.dir('shared', [d.libPubspec('shared', '1.0.0')]).create();
 
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'shared': '1.0.0'});
-      builder.serve('bar', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'shared': '1.0.0'})
+      ..serve('bar', '1.0.0', deps: {
         'shared': {'path': p.join(d.sandbox, 'shared')}
-      });
-      builder.serve('shared', '1.0.0');
-    });
+      })
+      ..serve('shared', '1.0.0');
 
     await d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -534,12 +506,11 @@
   });
 
   test('no valid solution', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'b': '1.0.0'});
-      builder.serve('a', '2.0.0', deps: {'b': '2.0.0'});
-      builder.serve('b', '1.0.0', deps: {'a': '2.0.0'});
-      builder.serve('b', '2.0.0', deps: {'a': '1.0.0'});
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'b': '1.0.0'})
+      ..serve('a', '2.0.0', deps: {'b': '2.0.0'})
+      ..serve('b', '1.0.0', deps: {'a': '2.0.0'})
+      ..serve('b', '2.0.0', deps: {'a': '1.0.0'});
 
     await d.appDir({'a': 'any', 'b': 'any'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -554,10 +525,9 @@
 
   // This is a regression test for #15550.
   test('no version that matches while backtracking', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0');
 
     await d.appDir({'a': 'any', 'b': '>1.0.0'}).create();
     await expectResolves(error: equalsIgnoringWhitespace("""
@@ -568,19 +538,18 @@
 
   // This is a regression test for #18300.
   test('issue 18300', () async {
-    await servePackages((builder) {
-      builder.serve('analyzer', '0.12.2');
-      builder.serve('angular', '0.10.0',
-          deps: {'di': '>=0.0.32 <0.1.0', 'collection': '>=0.9.1 <1.0.0'});
-      builder.serve('angular', '0.9.11',
-          deps: {'di': '>=0.0.32 <0.1.0', 'collection': '>=0.9.1 <1.0.0'});
-      builder.serve('angular', '0.9.10',
-          deps: {'di': '>=0.0.32 <0.1.0', 'collection': '>=0.9.1 <1.0.0'});
-      builder.serve('collection', '0.9.0');
-      builder.serve('collection', '0.9.1');
-      builder.serve('di', '0.0.37', deps: {'analyzer': '>=0.13.0 <0.14.0'});
-      builder.serve('di', '0.0.36', deps: {'analyzer': '>=0.13.0 <0.14.0'});
-    });
+    await servePackages()
+      ..serve('analyzer', '0.12.2')
+      ..serve('angular', '0.10.0',
+          deps: {'di': '>=0.0.32 <0.1.0', 'collection': '>=0.9.1 <1.0.0'})
+      ..serve('angular', '0.9.11',
+          deps: {'di': '>=0.0.32 <0.1.0', 'collection': '>=0.9.1 <1.0.0'})
+      ..serve('angular', '0.9.10',
+          deps: {'di': '>=0.0.32 <0.1.0', 'collection': '>=0.9.1 <1.0.0'})
+      ..serve('collection', '0.9.0')
+      ..serve('collection', '0.9.1')
+      ..serve('di', '0.0.37', deps: {'analyzer': '>=0.13.0 <0.14.0'})
+      ..serve('di', '0.0.36', deps: {'analyzer': '>=0.13.0 <0.14.0'});
 
     await d.appDir({'angular': 'any', 'collection': 'any'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -620,42 +589,40 @@
   });
 
   test('fail if all versions have bad source in dep', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {
         'bar': {'bad': 'any'}
-      });
-      builder.serve('foo', '1.0.1', deps: {
+      })
+      ..serve('foo', '1.0.1', deps: {
         'baz': {'bad': 'any'}
-      });
-      builder.serve('foo', '1.0.2', deps: {
+      })
+      ..serve('foo', '1.0.2', deps: {
         'bang': {'bad': 'any'}
       });
-    });
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
       Because foo <1.0.1 depends on bar from unknown source "bad", foo <1.0.1 is
         forbidden.
-      And because foo >=1.0.1 <1.0.2 depends on baz any from bad, foo <1.0.2
-        requires baz any from bad.
+      And because foo >=1.0.1 <1.0.2 depends on baz from bad, foo <1.0.2
+        requires baz from bad.
       And because baz comes from unknown source "bad" and foo >=1.0.2 depends on
-        bang any from bad, every version of foo requires bang any from bad.
-      So, because bang comes from unknown source "bad" and myapp depends on foo
-        any, version solving failed.
+        bang from bad, every version of foo requires bang from bad.
+      So, because bang comes from unknown source "bad" and myapp depends on foo any,
+        version solving failed.
     '''), tries: 3);
   });
 
   test('ignore versions with bad source in dep', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
-      builder.serve('foo', '1.0.1', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': 'any'})
+      ..serve('foo', '1.0.1', deps: {
         'bar': {'bad': 'any'}
-      });
-      builder.serve('foo', '1.0.2', deps: {
+      })
+      ..serve('foo', '1.0.2', deps: {
         'bar': {'bad': 'any'}
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'}, tries: 2);
@@ -663,11 +630,10 @@
 
   // Issue 1853
   test('reports a nice error across a collapsed cause', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
-      builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
-      builder.serve('baz', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': 'any'})
+      ..serve('bar', '1.0.0', deps: {'baz': 'any'})
+      ..serve('baz', '1.0.0');
     await d.dir('baz', [d.libPubspec('baz', '1.0.0')]).create();
 
     await d.appDir({
@@ -685,28 +651,24 @@
 
 void backtracking() {
   test('circular dependency on older version', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '2.0.0', deps: {'b': '1.0.0'});
-      builder.serve('b', '1.0.0', deps: {'a': '1.0.0'});
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '2.0.0', deps: {'b': '1.0.0'})
+      ..serve('b', '1.0.0', deps: {'a': '1.0.0'});
 
     await d.appDir({'a': '>=1.0.0'}).create();
     await expectResolves(result: {'a': '1.0.0'}, tries: 2);
   });
 
   test('diamond dependency graph', () async {
-    await servePackages((builder) {
-      builder.serve('a', '2.0.0', deps: {'c': '^1.0.0'});
-      builder.serve('a', '1.0.0');
-
-      builder.serve('b', '2.0.0', deps: {'c': '^3.0.0'});
-      builder.serve('b', '1.0.0', deps: {'c': '^2.0.0'});
-
-      builder.serve('c', '3.0.0');
-      builder.serve('c', '2.0.0');
-      builder.serve('c', '1.0.0');
-    });
+    await servePackages()
+      ..serve('a', '2.0.0', deps: {'c': '^1.0.0'})
+      ..serve('a', '1.0.0')
+      ..serve('b', '2.0.0', deps: {'c': '^3.0.0'})
+      ..serve('b', '1.0.0', deps: {'c': '^2.0.0'})
+      ..serve('c', '3.0.0')
+      ..serve('c', '2.0.0')
+      ..serve('c', '1.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any'}).create();
     await expectResolves(result: {'a': '1.0.0', 'b': '2.0.0', 'c': '3.0.0'});
@@ -716,20 +678,16 @@
   // requirement only exists because of both a and b. The solver should be able
   // to deduce c 2.0.0's incompatibility and select c 1.0.0 instead.
   test('backjumps after a partial satisfier', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'x': '>=1.0.0'});
-      builder.serve('b', '1.0.0', deps: {'x': '<2.0.0'});
-
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0', deps: {'a': 'any', 'b': 'any'});
-
-      builder.serve('x', '0.0.0');
-      builder.serve('x', '1.0.0', deps: {'y': '1.0.0'});
-      builder.serve('x', '2.0.0');
-
-      builder.serve('y', '1.0.0');
-      builder.serve('y', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'x': '>=1.0.0'})
+      ..serve('b', '1.0.0', deps: {'x': '<2.0.0'})
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0', deps: {'a': 'any', 'b': 'any'})
+      ..serve('x', '0.0.0')
+      ..serve('x', '1.0.0', deps: {'y': '1.0.0'})
+      ..serve('x', '2.0.0')
+      ..serve('y', '1.0.0')
+      ..serve('y', '2.0.0');
 
     await d.appDir({'c': 'any', 'y': '^2.0.0'}).create();
     await expectResolves(result: {'c': '1.0.0', 'y': '2.0.0'}, tries: 2);
@@ -738,16 +696,15 @@
   // This matches the Branching Error Reporting example in the version solver
   // documentation, and tests that we display line numbers correctly.
   test('branching error reporting', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'a': '^1.0.0', 'b': '^1.0.0'});
-      builder.serve('foo', '1.1.0', deps: {'x': '^1.0.0', 'y': '^1.0.0'});
-      builder.serve('a', '1.0.0', deps: {'b': '^2.0.0'});
-      builder.serve('b', '1.0.0');
-      builder.serve('b', '2.0.0');
-      builder.serve('x', '1.0.0', deps: {'y': '^2.0.0'});
-      builder.serve('y', '1.0.0');
-      builder.serve('y', '2.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'a': '^1.0.0', 'b': '^1.0.0'})
+      ..serve('foo', '1.1.0', deps: {'x': '^1.0.0', 'y': '^1.0.0'})
+      ..serve('a', '1.0.0', deps: {'b': '^2.0.0'})
+      ..serve('b', '1.0.0')
+      ..serve('b', '2.0.0')
+      ..serve('x', '1.0.0', deps: {'y': '^2.0.0'})
+      ..serve('y', '1.0.0')
+      ..serve('y', '2.0.0');
 
     await d.appDir({'foo': '^1.0.0'}).create();
     await expectResolves(
@@ -772,14 +729,13 @@
   // will resolve the problem. This test validates that b, which is farther
   // in the dependency graph from myapp is downgraded first.
   test('rolls back leaf versions first', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'b': 'any'});
-      builder.serve('a', '2.0.0', deps: {'b': 'any', 'c': '2.0.0'});
-      builder.serve('b', '1.0.0');
-      builder.serve('b', '2.0.0', deps: {'c': '1.0.0'});
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'b': 'any'})
+      ..serve('a', '2.0.0', deps: {'b': 'any', 'c': '2.0.0'})
+      ..serve('b', '1.0.0')
+      ..serve('b', '2.0.0', deps: {'c': '1.0.0'})
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0');
 
     await d.appDir({'a': 'any'}).create();
     await expectResolves(result: {'a': '2.0.0', 'b': '1.0.0', 'c': '2.0.0'});
@@ -788,15 +744,14 @@
   // Only one version of baz, so foo and bar will have to downgrade until they
   // reach it.
   test('simple transitive', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '2.0.0', deps: {'bar': '2.0.0'});
-      builder.serve('foo', '3.0.0', deps: {'bar': '3.0.0'});
-      builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
-      builder.serve('bar', '2.0.0', deps: {'baz': '2.0.0'});
-      builder.serve('bar', '3.0.0', deps: {'baz': '3.0.0'});
-      builder.serve('baz', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '2.0.0', deps: {'bar': '2.0.0'})
+      ..serve('foo', '3.0.0', deps: {'bar': '3.0.0'})
+      ..serve('bar', '1.0.0', deps: {'baz': 'any'})
+      ..serve('bar', '2.0.0', deps: {'baz': '2.0.0'})
+      ..serve('bar', '3.0.0', deps: {'baz': '3.0.0'})
+      ..serve('baz', '1.0.0');
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(
@@ -808,14 +763,13 @@
   // make sure b has more versions than a so that the solver tries a first
   // since it sorts sibling dependencies by number of versions.
   test('backjump to nearer unsatisfied package', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'c': '1.0.0'});
-      builder.serve('a', '2.0.0', deps: {'c': '2.0.0-nonexistent'});
-      builder.serve('b', '1.0.0');
-      builder.serve('b', '2.0.0');
-      builder.serve('b', '3.0.0');
-      builder.serve('c', '1.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'c': '1.0.0'})
+      ..serve('a', '2.0.0', deps: {'c': '2.0.0-nonexistent'})
+      ..serve('b', '1.0.0')
+      ..serve('b', '2.0.0')
+      ..serve('b', '3.0.0')
+      ..serve('c', '1.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any'}).create();
     await expectResolves(
@@ -839,18 +793,17 @@
   test('successful backjump to conflicting source', () async {
     await d.dir('a', [d.libPubspec('a', '1.0.0')]).create();
 
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0', deps: {'a': 'any'});
-      builder.serve('b', '2.0.0', deps: {
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0', deps: {'a': 'any'})
+      ..serve('b', '2.0.0', deps: {
         'a': {'path': p.join(d.sandbox, 'a')}
-      });
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0');
-      builder.serve('c', '3.0.0');
-      builder.serve('c', '4.0.0');
-      builder.serve('c', '5.0.0');
-    });
+      })
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0')
+      ..serve('c', '3.0.0')
+      ..serve('c', '4.0.0')
+      ..serve('c', '5.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
     await expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'c': '5.0.0'});
@@ -858,24 +811,22 @@
 
   // Like the above test, but for a conflicting description.
   test('successful backjump to conflicting description', () async {
-    var otherServer = await PackageServer.start((builder) {
-      builder.serve('a', '1.0.0');
-    });
+    var otherServer = await startPackageServer();
+    otherServer.serve('a', '1.0.0');
 
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0', deps: {'a': 'any'});
-      builder.serve('b', '2.0.0', deps: {
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0', deps: {'a': 'any'})
+      ..serve('b', '2.0.0', deps: {
         'a': {
           'hosted': {'name': 'a', 'url': otherServer.url}
         }
-      });
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0');
-      builder.serve('c', '3.0.0');
-      builder.serve('c', '4.0.0');
-      builder.serve('c', '5.0.0');
-    });
+      })
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0')
+      ..serve('c', '3.0.0')
+      ..serve('c', '4.0.0')
+      ..serve('c', '5.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
     await expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'c': '5.0.0'});
@@ -886,17 +837,16 @@
   test('failing backjump to conflicting source', () async {
     await d.dir('a', [d.libPubspec('a', '1.0.0')]).create();
 
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0', deps: {
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0', deps: {
         'a': {'path': p.join(d.sandbox, 'shared')}
-      });
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0');
-      builder.serve('c', '3.0.0');
-      builder.serve('c', '4.0.0');
-      builder.serve('c', '5.0.0');
-    });
+      })
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0')
+      ..serve('c', '3.0.0')
+      ..serve('c', '4.0.0')
+      ..serve('c', '5.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -907,23 +857,21 @@
   });
 
   test('failing backjump to conflicting description', () async {
-    var otherServer = await PackageServer.start((builder) {
-      builder.serve('a', '1.0.0');
-    });
+    var otherServer = await startPackageServer();
+    otherServer.serve('a', '1.0.0');
 
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0', deps: {
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0', deps: {
         'a': {
           'hosted': {'name': 'a', 'url': otherServer.url}
         }
-      });
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0');
-      builder.serve('c', '3.0.0');
-      builder.serve('c', '4.0.0');
-      builder.serve('c', '5.0.0');
-    });
+      })
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0')
+      ..serve('c', '3.0.0')
+      ..serve('c', '4.0.0')
+      ..serve('c', '5.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
     await expectResolves(
@@ -942,39 +890,37 @@
   // Since b has fewer versions, it will be traversed first, which means a will
   // come later. Since later selections are revised first, a gets downgraded.
   test('traverse into package with fewer versions first', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'c': 'any'});
-      builder.serve('a', '2.0.0', deps: {'c': 'any'});
-      builder.serve('a', '3.0.0', deps: {'c': 'any'});
-      builder.serve('a', '4.0.0', deps: {'c': 'any'});
-      builder.serve('a', '5.0.0', deps: {'c': '1.0.0'});
-      builder.serve('b', '1.0.0', deps: {'c': 'any'});
-      builder.serve('b', '2.0.0', deps: {'c': 'any'});
-      builder.serve('b', '3.0.0', deps: {'c': 'any'});
-      builder.serve('b', '4.0.0', deps: {'c': '2.0.0'});
-      builder.serve('c', '1.0.0');
-      builder.serve('c', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'c': 'any'})
+      ..serve('a', '2.0.0', deps: {'c': 'any'})
+      ..serve('a', '3.0.0', deps: {'c': 'any'})
+      ..serve('a', '4.0.0', deps: {'c': 'any'})
+      ..serve('a', '5.0.0', deps: {'c': '1.0.0'})
+      ..serve('b', '1.0.0', deps: {'c': 'any'})
+      ..serve('b', '2.0.0', deps: {'c': 'any'})
+      ..serve('b', '3.0.0', deps: {'c': 'any'})
+      ..serve('b', '4.0.0', deps: {'c': '2.0.0'})
+      ..serve('c', '1.0.0')
+      ..serve('c', '2.0.0');
 
     await d.appDir({'a': 'any', 'b': 'any'}).create();
     await expectResolves(result: {'a': '4.0.0', 'b': '4.0.0', 'c': '2.0.0'});
   });
 
   test('complex backtrack', () async {
-    await servePackages((builder) {
-      // This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each
-      // version of foo depends on a baz with the same major version. Each version
-      // of bar depends on a baz with the same minor version. There is only one
-      // version of baz, 0.0.0, so only older versions of foo and bar will
-      // satisfy it.
-      builder.serve('baz', '0.0.0');
-      for (var i = 0; i < 10; i++) {
-        for (var j = 0; j < 10; j++) {
-          builder.serve('foo', '$i.$j.0', deps: {'baz': '$i.0.0'});
-          builder.serve('bar', '$i.$j.0', deps: {'baz': '0.$j.0'});
-        }
+    final server = await servePackages();
+    // This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each
+    // version of foo depends on a baz with the same major version. Each version
+    // of bar depends on a baz with the same minor version. There is only one
+    // version of baz, 0.0.0, so only older versions of foo and bar will
+    // satisfy it.
+    server.serve('baz', '0.0.0');
+    for (var i = 0; i < 10; i++) {
+      for (var j = 0; j < 10; j++) {
+        server.serve('foo', '$i.$j.0', deps: {'baz': '$i.0.0'});
+        server.serve('bar', '$i.$j.0', deps: {'baz': '0.$j.0'});
       }
-    });
+    }
 
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
     await expectResolves(
@@ -985,19 +931,18 @@
   // versions of it is a waste of time: no possible versions can match. We need
   // to jump past it to the most recent package that affected the constraint.
   test('backjump past failed package on disjoint constraint', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {
         'foo': 'any' // ok
-      });
-      builder.serve('a', '2.0.0', deps: {
+      })
+      ..serve('a', '2.0.0', deps: {
         'foo': '<1.0.0' // disjoint with myapp's constraint on foo
-      });
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '2.0.1');
-      builder.serve('foo', '2.0.2');
-      builder.serve('foo', '2.0.3');
-      builder.serve('foo', '2.0.4');
-    });
+      })
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '2.0.1')
+      ..serve('foo', '2.0.2')
+      ..serve('foo', '2.0.3')
+      ..serve('foo', '2.0.4');
 
     await d.appDir({'a': 'any', 'foo': '>2.0.0'}).create();
     await expectResolves(result: {'a': '1.0.0', 'foo': '2.0.4'});
@@ -1008,14 +953,13 @@
   // would backtrack over the failed package instead of trying different
   // versions of it.
   test('finds solution with less strict constraint', () async {
-    await servePackages((builder) {
-      builder.serve('a', '2.0.0');
-      builder.serve('a', '1.0.0');
-      builder.serve('b', '1.0.0', deps: {'a': '1.0.0'});
-      builder.serve('c', '1.0.0', deps: {'b': 'any'});
-      builder.serve('d', '2.0.0', deps: {'myapp': 'any'});
-      builder.serve('d', '1.0.0', deps: {'myapp': '<1.0.0'});
-    });
+    await servePackages()
+      ..serve('a', '2.0.0')
+      ..serve('a', '1.0.0')
+      ..serve('b', '1.0.0', deps: {'a': '1.0.0'})
+      ..serve('c', '1.0.0', deps: {'b': 'any'})
+      ..serve('d', '2.0.0', deps: {'myapp': 'any'})
+      ..serve('d', '1.0.0', deps: {'myapp': '<1.0.0'});
 
     await d.appDir({'a': 'any', 'c': 'any', 'd': 'any'}).create();
     await expectResolves(
@@ -1051,10 +995,9 @@
   });
 
   test('dependency does not match SDK', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
-        'environment': {'sdk': '0.0.0'}
-      });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0', pubspec: {
+      'environment': {'sdk': '0.0.0'}
     });
 
     await d.appDir({'foo': 'any'}).create();
@@ -1067,12 +1010,11 @@
   });
 
   test('transitive dependency does not match SDK', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
-      builder.serve('bar', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': 'any'})
+      ..serve('bar', '1.0.0', pubspec: {
         'environment': {'sdk': '0.0.0'}
       });
-    });
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(error: equalsIgnoringWhitespace('''
@@ -1085,41 +1027,39 @@
   });
 
   test('selects a dependency version that allows the SDK', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'environment': {'sdk': '0.1.2+3'}
-      });
-      builder.serve('foo', '2.0.0', pubspec: {
+      })
+      ..serve('foo', '2.0.0', pubspec: {
         'environment': {'sdk': '0.1.2+3'}
-      });
-      builder.serve('foo', '3.0.0', pubspec: {
+      })
+      ..serve('foo', '3.0.0', pubspec: {
+        'environment': {'sdk': '0.0.0'}
+      })
+      ..serve('foo', '4.0.0', pubspec: {
         'environment': {'sdk': '0.0.0'}
       });
-      builder.serve('foo', '4.0.0', pubspec: {
-        'environment': {'sdk': '0.0.0'}
-      });
-    });
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(result: {'foo': '2.0.0'});
   });
 
   test('selects a transitive dependency version that allows the SDK', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
-      builder.serve('bar', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': 'any'})
+      ..serve('bar', '1.0.0', pubspec: {
         'environment': {'sdk': '0.1.2+3'}
-      });
-      builder.serve('bar', '2.0.0', pubspec: {
+      })
+      ..serve('bar', '2.0.0', pubspec: {
         'environment': {'sdk': '0.1.2+3'}
-      });
-      builder.serve('bar', '3.0.0', pubspec: {
+      })
+      ..serve('bar', '3.0.0', pubspec: {
+        'environment': {'sdk': '0.0.0'}
+      })
+      ..serve('bar', '4.0.0', pubspec: {
         'environment': {'sdk': '0.0.0'}
       });
-      builder.serve('bar', '4.0.0', pubspec: {
-        'environment': {'sdk': '0.0.0'}
-      });
-    });
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '2.0.0'});
@@ -1128,24 +1068,23 @@
   test(
       'selects a dependency version that allows a transitive '
       'dependency that allows the SDK', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '2.0.0', deps: {'bar': '2.0.0'});
-      builder.serve('foo', '3.0.0', deps: {'bar': '3.0.0'});
-      builder.serve('foo', '4.0.0', deps: {'bar': '4.0.0'});
-      builder.serve('bar', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '2.0.0', deps: {'bar': '2.0.0'})
+      ..serve('foo', '3.0.0', deps: {'bar': '3.0.0'})
+      ..serve('foo', '4.0.0', deps: {'bar': '4.0.0'})
+      ..serve('bar', '1.0.0', pubspec: {
         'environment': {'sdk': '0.1.2+3'}
-      });
-      builder.serve('bar', '2.0.0', pubspec: {
+      })
+      ..serve('bar', '2.0.0', pubspec: {
         'environment': {'sdk': '0.1.2+3'}
-      });
-      builder.serve('bar', '3.0.0', pubspec: {
+      })
+      ..serve('bar', '3.0.0', pubspec: {
+        'environment': {'sdk': '0.0.0'}
+      })
+      ..serve('bar', '4.0.0', pubspec: {
         'environment': {'sdk': '0.0.0'}
       });
-      builder.serve('bar', '4.0.0', pubspec: {
-        'environment': {'sdk': '0.0.0'}
-      });
-    });
 
     await d.appDir({'foo': 'any'}).create();
     await expectResolves(result: {'foo': '2.0.0', 'bar': '2.0.0'}, tries: 2);
@@ -1463,10 +1402,9 @@
     });
 
     test('fails for a dependency', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
-          'environment': {'flutter': '0.0.0'}
-        });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0', pubspec: {
+        'environment': {'flutter': '0.0.0'}
       });
 
       await d.appDir({'foo': 'any'}).create();
@@ -1479,13 +1417,12 @@
     });
 
     test("chooses a version that doesn't need Flutter", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-        builder.serve('foo', '2.0.0');
-        builder.serve('foo', '3.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0')
+        ..serve('foo', '3.0.0', pubspec: {
           'environment': {'flutter': '0.0.0'}
         });
-      });
 
       await d.appDir({'foo': 'any'}).create();
       await expectResolves(result: {'foo': '2.0.0'});
@@ -1612,17 +1549,16 @@
     });
 
     test('selects the latest dependency with a matching constraint', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'environment': {'flutter': '^0.0.0'}
-        });
-        builder.serve('foo', '2.0.0', pubspec: {
+        })
+        ..serve('foo', '2.0.0', pubspec: {
           'environment': {'flutter': '^1.0.0'}
-        });
-        builder.serve('foo', '3.0.0', pubspec: {
+        })
+        ..serve('foo', '3.0.0', pubspec: {
           'environment': {'flutter': '^2.0.0'}
         });
-      });
 
       await d.appDir({'foo': 'any'}).create();
       await expectResolves(
@@ -1634,36 +1570,33 @@
 
 void prerelease() {
   test('prefer stable versions over unstable', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '1.1.0-dev');
-      builder.serve('a', '2.0.0-dev');
-      builder.serve('a', '3.0.0-dev');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '1.1.0-dev')
+      ..serve('a', '2.0.0-dev')
+      ..serve('a', '3.0.0-dev');
 
     await d.appDir({'a': 'any'}).create();
     await expectResolves(result: {'a': '1.0.0'});
   });
 
   test('use latest allowed prerelease if no stable versions match', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0-dev');
-      builder.serve('a', '1.1.0-dev');
-      builder.serve('a', '1.9.0-dev');
-      builder.serve('a', '3.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0-dev')
+      ..serve('a', '1.1.0-dev')
+      ..serve('a', '1.9.0-dev')
+      ..serve('a', '3.0.0');
 
     await d.appDir({'a': '<2.0.0'}).create();
     await expectResolves(result: {'a': '1.9.0-dev'});
   });
 
   test('use an earlier stable version on a < constraint', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '1.1.0');
-      builder.serve('a', '2.0.0-dev');
-      builder.serve('a', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '1.1.0')
+      ..serve('a', '2.0.0-dev')
+      ..serve('a', '2.0.0');
 
     await d.appDir({'a': '<2.0.0'}).create();
     await expectResolves(result: {'a': '1.1.0'});
@@ -1671,33 +1604,30 @@
 
   test('prefer a stable version even if constraint mentions unstable',
       () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '1.1.0');
-      builder.serve('a', '2.0.0-dev');
-      builder.serve('a', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '1.1.0')
+      ..serve('a', '2.0.0-dev')
+      ..serve('a', '2.0.0');
 
     await d.appDir({'a': '<=2.0.0-dev'}).create();
     await expectResolves(result: {'a': '1.1.0'});
   });
 
   test('use pre-release when desired', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '1.1.0-dev');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '1.1.0-dev');
 
     await d.appDir({'a': '^1.1.0-dev'}).create();
     await expectResolves(result: {'a': '1.1.0-dev'});
   });
 
   test('can upgrade from pre-release', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '1.1.0-dev');
-      builder.serve('a', '1.1.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '1.1.0-dev')
+      ..serve('a', '1.1.0');
 
     await d.appDir({'a': '^1.1.0-dev'}).create();
     await expectResolves(result: {'a': '1.1.0'});
@@ -1706,12 +1636,11 @@
   test('will use pre-release if depended on in stable release', () async {
     // This behavior is desired because a stable package has dependency on a
     // pre-release.
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'b': '^1.0.0'});
-      builder.serve('a', '1.1.0', deps: {'b': '^1.1.0-dev'});
-      builder.serve('b', '1.0.0');
-      builder.serve('b', '1.1.0-dev');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'b': '^1.0.0'})
+      ..serve('a', '1.1.0', deps: {'b': '^1.1.0-dev'})
+      ..serve('b', '1.0.0')
+      ..serve('b', '1.1.0-dev');
 
     await d.appDir({'a': '^1.0.0'}).create();
     await expectResolves(result: {
@@ -1721,12 +1650,11 @@
   });
 
   test('backtracks pre-release choice with direct dependency', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'b': '^1.0.0'});
-      builder.serve('a', '1.1.0', deps: {'b': '^1.1.0-dev'});
-      builder.serve('b', '1.0.0');
-      builder.serve('b', '1.1.0-dev');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'b': '^1.0.0'})
+      ..serve('a', '1.1.0', deps: {'b': '^1.1.0-dev'})
+      ..serve('b', '1.0.0')
+      ..serve('b', '1.1.0-dev');
 
     await d.appDir({
       'a': '^1.0.0',
@@ -1741,13 +1669,12 @@
   test('backtracking pre-release fails with indirect dependency', () async {
     // NOTE: This behavior is not necessarily desired.
     //       If feasible it might worth changing this behavior in the future.
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'b': '^1.0.0'});
-      builder.serve('a', '1.1.0', deps: {'b': '^1.1.0-dev'});
-      builder.serve('b', '1.0.0');
-      builder.serve('b', '1.1.0-dev');
-      builder.serve('c', '1.0.0', deps: {'b': '^1.0.0'});
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'b': '^1.0.0'})
+      ..serve('a', '1.1.0', deps: {'b': '^1.1.0-dev'})
+      ..serve('b', '1.0.0')
+      ..serve('b', '1.1.0-dev')
+      ..serve('c', '1.0.0', deps: {'b': '^1.0.0'});
 
     await d.appDir({
       'a': '^1.0.0',
@@ -1762,14 +1689,13 @@
 
   test('https://github.com/dart-lang/pub/issues/3057 regression', () async {
     // This used to cause an infinite loop.
-    await servePackages((builder) {
-      builder.serve('a', '0.12.0', deps: {});
-      builder.serve('b', '0.1.0', deps: {'c': '2.0.0'});
-      builder.serve('b', '0.9.0-1', deps: {'c': '^1.6.0'});
-      builder.serve('b', '0.10.0', deps: {'a': '1.0.0'});
-      builder.serve('b', '0.17.0', deps: {'a': '1.0.0'});
-      builder.serve('c', '2.0.1', deps: {});
-    });
+    await servePackages()
+      ..serve('a', '0.12.0', deps: {})
+      ..serve('b', '0.1.0', deps: {'c': '2.0.0'})
+      ..serve('b', '0.9.0-1', deps: {'c': '^1.6.0'})
+      ..serve('b', '0.10.0', deps: {'a': '1.0.0'})
+      ..serve('b', '0.17.0', deps: {'a': '1.0.0'})
+      ..serve('c', '2.0.1', deps: {});
 
     await d.appDir(
       {
@@ -1784,13 +1710,12 @@
   });
 
   test('https://github.com/dart-lang/pub/pull/3038 regression', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.1.0', deps: {'b': '^1.0.0'});
-      builder.serve('b', '1.0.0', deps: {'c': '^1.0.0'});
-      builder.serve('c', '0.9.0');
-      builder.serve('b', '1.1.0-alpha');
-      builder.serve('a', '1.0.0', deps: {'b': '^1.1.0-alpha'});
-    });
+    await servePackages()
+      ..serve('a', '1.1.0', deps: {'b': '^1.0.0'})
+      ..serve('b', '1.0.0', deps: {'c': '^1.0.0'})
+      ..serve('c', '0.9.0')
+      ..serve('b', '1.1.0-alpha')
+      ..serve('a', '1.0.0', deps: {'b': '^1.1.0-alpha'});
 
     await d.appDir({
       'a': '^1.0.0',
@@ -1801,11 +1726,10 @@
 
 void override() {
   test('chooses best version matching override constraint', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '2.0.0');
-      builder.serve('a', '3.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '2.0.0')
+      ..serve('a', '3.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1819,11 +1743,10 @@
   });
 
   test('uses override as dependency', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '2.0.0');
-      builder.serve('a', '3.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '2.0.0')
+      ..serve('a', '3.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1836,13 +1759,12 @@
   });
 
   test('ignores other constraints on overridden package', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '2.0.0');
-      builder.serve('a', '3.0.0');
-      builder.serve('b', '1.0.0', deps: {'a': '1.0.0'});
-      builder.serve('c', '1.0.0', deps: {'a': '3.0.0'});
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '2.0.0')
+      ..serve('a', '3.0.0')
+      ..serve('b', '1.0.0', deps: {'a': '1.0.0'})
+      ..serve('c', '1.0.0', deps: {'a': '3.0.0'});
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1856,12 +1778,11 @@
   });
 
   test('backtracks on overidden package for its constraints', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0', deps: {'shared': 'any'});
-      builder.serve('a', '2.0.0', deps: {'shared': '1.0.0'});
-      builder.serve('shared', '1.0.0');
-      builder.serve('shared', '2.0.0');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0', deps: {'shared': 'any'})
+      ..serve('a', '2.0.0', deps: {'shared': '1.0.0'})
+      ..serve('shared', '1.0.0')
+      ..serve('shared', '2.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1875,14 +1796,13 @@
   });
 
   test('override compatible with locked dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
-      builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '1.0.1');
-      builder.serve('bar', '1.0.2');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '1.0.1', deps: {'bar': '1.0.1'})
+      ..serve('foo', '1.0.2', deps: {'bar': '1.0.2'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '1.0.1')
+      ..serve('bar', '1.0.2');
 
     await d.appDir({'foo': '1.0.1'}).create();
     await expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
@@ -1898,14 +1818,13 @@
   });
 
   test('override incompatible with locked dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
-      builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
-      builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '1.0.1');
-      builder.serve('bar', '1.0.2');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'bar': '1.0.0'})
+      ..serve('foo', '1.0.1', deps: {'bar': '1.0.1'})
+      ..serve('foo', '1.0.2', deps: {'bar': '1.0.2'})
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '1.0.1')
+      ..serve('bar', '1.0.2');
 
     await d.appDir({'foo': '1.0.1'}).create();
     await expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
@@ -1921,10 +1840,9 @@
   });
 
   test('no version that matches override', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '2.1.3');
-    });
+    await servePackages()
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '2.1.3');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1940,9 +1858,8 @@
   });
 
   test('overrides a bad source without error', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '0.0.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '0.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1958,10 +1875,9 @@
   });
 
   test('overrides an unmatched SDK constraint', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '0.0.0', pubspec: {
-        'environment': {'sdk': '0.0.0'}
-      });
+    final server = await servePackages();
+    server.serve('foo', '0.0.0', pubspec: {
+      'environment': {'sdk': '0.0.0'}
     });
 
     await d.dir(appPath, [
@@ -1975,9 +1891,8 @@
   });
 
   test('overrides an unmatched root dependency', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '0.0.0', deps: {'myapp': '1.0.0'});
-    });
+    final server = await servePackages();
+    server.serve('foo', '0.0.0', deps: {'myapp': '1.0.0'});
 
     await d.dir(appPath, [
       d.pubspec({
@@ -1992,11 +1907,10 @@
 
   // Regression test for #1853
   test("overrides a locked package's dependency", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.2.3', deps: {'bar': '1.2.3'});
-      builder.serve('bar', '1.2.3');
-      builder.serve('bar', '0.0.1');
-    });
+    await servePackages()
+      ..serve('foo', '1.2.3', deps: {'bar': '1.2.3'})
+      ..serve('bar', '1.2.3')
+      ..serve('bar', '0.0.1');
 
     await d.appDir({'foo': 'any'}).create();
 
@@ -2016,12 +1930,11 @@
 
 void downgrade() {
   test('downgrades a dependency to the lowest matching version', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('foo', '2.0.0-dev');
-      builder.serve('foo', '2.0.0');
-      builder.serve('foo', '2.1.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '2.0.0-dev')
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '2.1.0');
 
     await d.appDir({'foo': '2.1.0'}).create();
     await expectResolves(result: {'foo': '2.1.0'});
@@ -2033,12 +1946,11 @@
   test(
       'use earliest allowed prerelease if no stable versions match '
       'while downgrading', () async {
-    await servePackages((builder) {
-      builder.serve('a', '1.0.0');
-      builder.serve('a', '2.0.0-dev.1');
-      builder.serve('a', '2.0.0-dev.2');
-      builder.serve('a', '2.0.0-dev.3');
-    });
+    await servePackages()
+      ..serve('a', '1.0.0')
+      ..serve('a', '2.0.0-dev.1')
+      ..serve('a', '2.0.0-dev.2')
+      ..serve('a', '2.0.0-dev.3');
 
     await d.appDir({'a': '>=2.0.0-dev.1 <3.0.0'}).create();
     await expectResolves(result: {'a': '2.0.0-dev.1'}, downgrade: true);
@@ -2047,67 +1959,63 @@
 
 void features() {
   test("doesn't enable an opt-in feature by default", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(result: {'foo': '1.0.0'});
   });
 
   test('enables an opt-out feature by default', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': true,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
   });
 
   test('features are opt-out by default', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.appDir({'foo': '1.0.0'}).create();
     await expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
   });
 
   test("enables an opt-in feature if it's required", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.appDir({
       'foo': {
@@ -2119,16 +2027,15 @@
   });
 
   test("doesn't enable an opt-out feature if it's disabled", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.appDir({
       'foo': {
@@ -2140,22 +2047,21 @@
   });
 
   test('opting in takes precedence over opting out', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0', deps: {
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0', deps: {
         'foo': {
           'version': '1.0.0',
           'features': {'stuff': true}
         }
       });
-    });
 
     await d.appDir({
       'foo': {
@@ -2169,21 +2075,20 @@
   });
 
   test('implicitly opting in takes precedence over opting out', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-      builder.serve('baz', '1.0.0', deps: {
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0', deps: {
         'foo': {
           'version': '1.0.0',
         }
       });
-    });
 
     await d.appDir({
       'foo': {
@@ -2197,18 +2102,17 @@
   });
 
   test("doesn't select a version with an unavailable feature", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('foo', '1.1.0');
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('foo', '1.1.0')
+      ..serve('bar', '1.0.0');
 
     await d.appDir({
       'foo': {
@@ -2220,26 +2124,25 @@
   });
 
   test("doesn't select a version with an incompatible feature", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('foo', '1.1.0', pubspec: {
+      })
+      ..serve('foo', '1.1.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '2.0.0'}
           }
         }
-      });
-      builder.serve('bar', '1.0.0');
-      builder.serve('bar', '2.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0')
+      ..serve('bar', '2.0.0');
 
     await d.appDir({
       'foo': {
@@ -2254,8 +2157,8 @@
   test(
       'backtracks if a feature is transitively incompatible with another '
       'feature', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
@@ -2267,27 +2170,24 @@
             }
           }
         }
-      });
-      builder.serve('foo', '1.1.0', pubspec: {
+      })
+      ..serve('foo', '1.1.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0', pubspec: {
+      })
+      ..serve('bar', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'dependencies': {'baz': '1.0.0'}
           }
         }
-      });
-
-      builder.serve('baz', '1.0.0');
-      builder.serve('baz', '2.0.0');
-    });
+      })
+      ..serve('baz', '1.0.0')
+      ..serve('baz', '2.0.0');
 
     await d.appDir({
       'foo': {
@@ -2302,30 +2202,27 @@
 
   test("backtracks if a feature's dependencies are transitively incompatible",
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', pubspec: {
+    await servePackages()
+      ..serve('foo', '1.0.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '1.0.0'}
           }
         }
-      });
-      builder.serve('foo', '1.1.0', pubspec: {
+      })
+      ..serve('foo', '1.1.0', pubspec: {
         'features': {
           'stuff': {
             'default': false,
             'dependencies': {'bar': '2.0.0'}
           }
         }
-      });
-
-      builder.serve('bar', '1.0.0', deps: {'baz': '1.0.0'});
-      builder.serve('bar', '2.0.0', deps: {'baz': '2.0.0'});
-
-      builder.serve('baz', '1.0.0');
-      builder.serve('baz', '2.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0', deps: {'baz': '1.0.0'})
+      ..serve('bar', '2.0.0', deps: {'baz': '2.0.0'})
+      ..serve('baz', '1.0.0')
+      ..serve('baz', '2.0.0');
 
     await d.appDir({
       'foo': {
@@ -2339,9 +2236,9 @@
   });
 
   test('disables a feature when it backtracks', () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {'myapp': '0.0.0'});
-      builder.serve('foo', '1.1.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {'myapp': '0.0.0'})
+      ..serve('foo', '1.1.0', deps: {
         // This is a transitively incompatible dependency with myapp, which will
         // force the solver to backtrack and unselect foo 1.1.0.
         'bar': '1.0.0',
@@ -2349,15 +2246,11 @@
           'version': '0.0.0',
           'features': {'stuff': true}
         }
-      });
-
-      builder.serve('bar', '1.0.0', deps: {'baz': '2.0.0'});
-
-      builder.serve('baz', '1.0.0');
-      builder.serve('baz', '2.0.0');
-
-      builder.serve('qux', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0', deps: {'baz': '2.0.0'})
+      ..serve('baz', '1.0.0')
+      ..serve('baz', '2.0.0')
+      ..serve('qux', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -2375,10 +2268,9 @@
   });
 
   test("the root package's features are opt-out by default", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('bar', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -2395,10 +2287,9 @@
   });
 
   test("the root package's features can be made opt-in", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-      builder.serve('bar', '1.0.0');
-    });
+    await servePackages()
+      ..serve('foo', '1.0.0')
+      ..serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -2421,14 +2312,13 @@
   // increases the total number of dependencies.
   test("the root package's features can't be disabled by dependencies",
       () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {
         'myapp': {
           'features': {'stuff': false}
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -2445,14 +2335,13 @@
   });
 
   test("the root package's features can be enabled by dependencies", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0', deps: {
+    await servePackages()
+      ..serve('foo', '1.0.0', deps: {
         'myapp': {
           'features': {'stuff': true}
         }
-      });
-      builder.serve('bar', '1.0.0');
-    });
+      })
+      ..serve('bar', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -2470,9 +2359,8 @@
   });
 
   test("resolution fails because a feature doesn't exist", () async {
-    await servePackages((builder) {
-      builder.serve('foo', '1.0.0');
-    });
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
 
     await d.dir(appPath, [
       d.pubspec({
@@ -2492,17 +2380,16 @@
 
   group('an "if available" dependency', () {
     test('enables an opt-in feature', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'features': {
             'stuff': {
               'default': false,
               'dependencies': {'bar': '1.0.0'}
             }
           }
-        });
-        builder.serve('bar', '1.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2514,9 +2401,8 @@
     });
 
     test("is compatible with a feature that doesn't exist", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0');
-      });
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2535,14 +2421,13 @@
 
     group('succeeds when', () {
       test('a Dart SDK constraint is matched', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0', pubspec: {
-            'features': {
-              'stuff': {
-                'environment': {'sdk': '^0.1.0'}
-              }
+        final server = await servePackages();
+        server.serve('foo', '1.0.0', pubspec: {
+          'features': {
+            'stuff': {
+              'environment': {'sdk': '^0.1.0'}
             }
-          });
+          }
         });
 
         await d.dir(appPath, [
@@ -2556,14 +2441,13 @@
       });
 
       test('a Flutter SDK constraint is matched', () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0', pubspec: {
-            'features': {
-              'stuff': {
-                'environment': {'flutter': '^1.0.0'}
-              }
+        final server = await servePackages();
+        server.serve('foo', '1.0.0', pubspec: {
+          'features': {
+            'stuff': {
+              'environment': {'flutter': '^1.0.0'}
             }
-          });
+          }
         });
 
         await d.dir(appPath, [
@@ -2581,16 +2465,15 @@
 
     group("doesn't choose a version because", () {
       test("a Dart SDK constraint isn't matched", () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0');
-          builder.serve('foo', '1.1.0', pubspec: {
+        await servePackages()
+          ..serve('foo', '1.0.0')
+          ..serve('foo', '1.1.0', pubspec: {
             'features': {
               'stuff': {
                 'environment': {'sdk': '0.0.1'}
               }
             }
           });
-        });
 
         await d.dir(appPath, [
           d.pubspec({
@@ -2603,16 +2486,15 @@
       });
 
       test("Flutter isn't available", () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0');
-          builder.serve('foo', '1.1.0', pubspec: {
+        await servePackages()
+          ..serve('foo', '1.0.0')
+          ..serve('foo', '1.1.0', pubspec: {
             'features': {
               'stuff': {
                 'environment': {'flutter': '1.0.0'}
               }
             }
           });
-        });
 
         await d.dir(appPath, [
           d.pubspec({
@@ -2625,16 +2507,15 @@
       });
 
       test("a Flutter SDK constraint isn't matched", () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0');
-          builder.serve('foo', '1.1.0', pubspec: {
+        await servePackages()
+          ..serve('foo', '1.0.0')
+          ..serve('foo', '1.1.0', pubspec: {
             'features': {
               'stuff': {
                 'environment': {'flutter': '^2.0.0'}
               }
             }
           });
-        });
 
         await d.dir(appPath, [
           d.pubspec({
@@ -2651,14 +2532,13 @@
 
     group('resolution fails because', () {
       test("a Dart SDK constraint isn't matched", () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0', pubspec: {
-            'features': {
-              'stuff': {
-                'environment': {'sdk': '0.0.1'}
-              }
+        final server = await servePackages();
+        server.serve('foo', '1.0.0', pubspec: {
+          'features': {
+            'stuff': {
+              'environment': {'sdk': '0.0.1'}
             }
-          });
+          }
         });
 
         await d.dir(appPath, [
@@ -2675,14 +2555,13 @@
       });
 
       test("Flutter isn't available", () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0', pubspec: {
-            'features': {
-              'stuff': {
-                'environment': {'flutter': '1.0.0'}
-              }
+        final server = await servePackages();
+        server.serve('foo', '1.0.0', pubspec: {
+          'features': {
+            'stuff': {
+              'environment': {'flutter': '1.0.0'}
             }
-          });
+          }
         });
 
         await d.dir(appPath, [
@@ -2698,14 +2577,13 @@
       });
 
       test("a Flutter SDK constraint isn't matched", () async {
-        await servePackages((builder) {
-          builder.serve('foo', '1.0.0', pubspec: {
-            'features': {
-              'stuff': {
-                'environment': {'flutter': '^2.0.0'}
-              }
+        final server = await servePackages();
+        server.serve('foo', '1.0.0', pubspec: {
+          'features': {
+            'stuff': {
+              'environment': {'flutter': '^2.0.0'}
             }
-          });
+          }
         });
 
         await d.dir(appPath, [
@@ -2725,8 +2603,8 @@
 
   group('with overlapping dependencies', () {
     test('can enable extra features', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'dependencies': {'bar': '1.0.0'},
           'features': {
             'stuff': {
@@ -2738,19 +2616,16 @@
               }
             }
           }
-        });
-
-        builder.serve('bar', '1.0.0', pubspec: {
+        })
+        ..serve('bar', '1.0.0', pubspec: {
           'features': {
             'stuff': {
               'default': false,
               'dependencies': {'baz': '1.0.0'}
             }
           }
-        });
-
-        builder.serve('baz', '1.0.0');
-      });
+        })
+        ..serve('baz', '1.0.0');
 
       await d.appDir({
         'foo': {'version': '1.0.0'}
@@ -2768,8 +2643,8 @@
     });
 
     test("can't disable features", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'dependencies': {
             'bar': {
               'version': '1.0.0',
@@ -2786,19 +2661,16 @@
               }
             }
           }
-        });
-
-        builder.serve('bar', '1.0.0', pubspec: {
+        })
+        ..serve('bar', '1.0.0', pubspec: {
           'features': {
             'stuff': {
               'default': true,
               'dependencies': {'baz': '1.0.0'}
             }
           }
-        });
-
-        builder.serve('baz', '1.0.0');
-      });
+        })
+        ..serve('baz', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2813,8 +2685,8 @@
 
   group('with required features', () {
     test('enables those features', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'features': {
             'main': {
               'default': false,
@@ -2829,10 +2701,9 @@
               'dependencies': {'baz': '1.0.0'}
             }
           }
-        });
-        builder.serve('bar', '1.0.0');
-        builder.serve('baz', '1.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0')
+        ..serve('baz', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2845,8 +2716,8 @@
     });
 
     test('enables those features by default', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'features': {
             'main': {
               'requires': ['required1', 'required2']
@@ -2860,10 +2731,9 @@
               'dependencies': {'baz': '1.0.0'}
             }
           }
-        });
-        builder.serve('bar', '1.0.0');
-        builder.serve('baz', '1.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0')
+        ..serve('baz', '1.0.0');
 
       await d.appDir({'foo': '1.0.0'}).create();
       await expectResolves(
@@ -2871,8 +2741,8 @@
     });
 
     test("doesn't enable those features if it's disabled", () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'features': {
             'main': {
               'requires': ['required']
@@ -2882,9 +2752,8 @@
               'dependencies': {'bar': '1.0.0'}
             }
           }
-        });
-        builder.serve('bar', '1.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2897,8 +2766,8 @@
 
     test("enables those features even if they'd otherwise be disabled",
         () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'features': {
             'main': {
               'requires': ['required']
@@ -2908,9 +2777,8 @@
               'dependencies': {'bar': '1.0.0'}
             }
           }
-        });
-        builder.serve('bar', '1.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2922,8 +2790,8 @@
     });
 
     test('enables features transitively', () async {
-      await servePackages((builder) {
-        builder.serve('foo', '1.0.0', pubspec: {
+      await servePackages()
+        ..serve('foo', '1.0.0', pubspec: {
           'features': {
             'main': {
               'requires': ['required1']
@@ -2937,9 +2805,8 @@
               'dependencies': {'bar': '1.0.0'}
             }
           }
-        });
-        builder.serve('bar', '1.0.0');
-      });
+        })
+        ..serve('bar', '1.0.0');
 
       await d.appDir({
         'foo': {
@@ -2972,11 +2839,11 @@
 ///
 /// If [downgrade] is `true`, this runs "pub downgrade" instead of "pub get".
 Future expectResolves(
-    {Map result,
+    {Map? result,
     error,
     output,
-    int tries,
-    Map<String, String> environment,
+    int? tries,
+    Map<String, String>? environment,
     bool downgrade = false}) async {
   await runPub(
       args: [downgrade ? 'downgrade' : 'get'],
@@ -3001,12 +2868,13 @@
   for (var dep in resultPubspec.dependencies.values) {
     expect(ids, contains(dep.name));
     var id = ids.remove(dep.name);
+    final source = dep.source;
 
-    if (dep.source is HostedSource && dep.description is String) {
+    if (source is HostedSource && (dep.description.uri == source.defaultUrl)) {
       // If the dep uses the default hosted source, grab it from the test
       // package server rather than pub.dartlang.org.
       dep = registry.hosted
-          .refFor(dep.name, url: Uri.parse(globalPackageServer.url))
+          .refFor(dep.name, url: Uri.parse(globalServer.url))
           .withConstraint(dep.constraint);
     }
     expect(dep.allows(id), isTrue, reason: 'Expected $id to match $dep.');
@@ -3014,3 +2882,20 @@
 
   expect(ids, isEmpty, reason: 'Expected no additional packages.');
 }
+
+void regressions() {
+  test('reformatRanges with a build', () async {
+    await servePackages()
+      ..serve('integration_test', '1.0.1',
+          deps: {'vm_service': '>= 4.2.0 <6.0.0'})
+      ..serve('integration_test', '1.0.2+2',
+          deps: {'vm_service': '>= 4.2.0 <7.0.0'})
+      ..serve('vm_service', '7.3.0');
+    await d.appDir({'integration_test': '^1.0.2'}).create();
+    await expectResolves(
+      error: contains(
+        'Because no versions of integration_test match >=1.0.2 <1.0.2+2',
+      ),
+    );
+  });
+}
diff --git a/tool/extract_all_pub_dev.dart b/tool/extract_all_pub_dev.dart
index 8d7f049..f96dc5a 100644
--- a/tool/extract_all_pub_dev.dart
+++ b/tool/extract_all_pub_dev.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 /// This is a manual test that can be run to test the .tar.gz decoding.
 /// It will save progress in [statusFileName] such that it doesn't have to be
 /// finished in a single run.
@@ -33,7 +31,7 @@
 
 Future<void> main() async {
   var alreadyDonePackages = <String>{};
-  var failures = <Map<String, dynamic>>[];
+  var failures = <Map<String, dynamic>?>[];
   if (fileExists(statusFilename)) {
     final json = jsonDecode(readTextFile(statusFilename));
     for (final packageName in json['packages'] ?? []) {
diff --git a/tool/test-bin/pub_command_runner.dart b/tool/test-bin/pub_command_runner.dart
index 2470a91..e438aae 100644
--- a/tool/test-bin/pub_command_runner.dart
+++ b/tool/test-bin/pub_command_runner.dart
@@ -1,22 +1,50 @@
-// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2020, 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.
 
-// @dart=2.10
-
+/// A trivial embedding of the pub command. Used from tests.
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:args/args.dart';
 import 'package:args/command_runner.dart';
+import 'package:pub/pub.dart';
+import 'package:pub/src/command.dart';
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/log.dart' as log;
-import 'package:pub/src/pub_embeddable_command.dart';
+import 'package:usage/usage.dart';
+
+final _LoggingAnalytics loggingAnalytics = _LoggingAnalytics();
+
+// A command for explicitly throwing an exception, to test the handling of
+// unexpected eceptions.
+class ThrowingCommand extends PubCommand {
+  @override
+  String get name => 'fail';
+
+  @override
+  String get description => 'Throws an exception';
+
+  bool get hide => true;
+
+  @override
+  Future<int> runProtected() async {
+    throw StateError('Pub has crashed');
+  }
+}
 
 class Runner extends CommandRunner<int> {
-  ArgResults _options;
+  late ArgResults _options;
 
   Runner() : super('pub_command_runner', 'Tests the embeddable pub command.') {
-    addCommand(PubEmbeddableCommand());
+    final analytics = Platform.environment['_PUB_LOG_ANALYTICS'] == 'true'
+        ? PubAnalytics(() => loggingAnalytics,
+            dependencyKindCustomDimensionName: 'cd1')
+        : null;
+    addCommand(
+        pubCommand(analytics: analytics, isVerbose: () => _options['verbose'])
+          ..addSubcommand(ThrowingCommand()));
+    argParser.addFlag('verbose');
   }
 
   @override
@@ -40,3 +68,51 @@
 Future<void> main(List<String> arguments) async {
   exitCode = await Runner().run(arguments);
 }
+
+class _LoggingAnalytics extends AnalyticsMock {
+  _LoggingAnalytics() {
+    onSend.listen((event) {
+      stderr.writeln('[analytics]${json.encode(event)}');
+    });
+  }
+
+  @override
+  bool get firstRun => false;
+
+  @override
+  Future sendScreenView(String viewName, {Map<String, String>? parameters}) {
+    parameters ??= <String, String>{};
+    parameters['viewName'] = viewName;
+    return _log('screenView', parameters);
+  }
+
+  @override
+  Future sendEvent(String category, String action,
+      {String? label, int? value, Map<String, String>? parameters}) {
+    parameters ??= <String, String>{};
+    return _log(
+        'event',
+        {'category': category, 'action': action, 'label': label, 'value': value}
+          ..addAll(parameters));
+  }
+
+  @override
+  Future sendSocial(String network, String action, String target) =>
+      _log('social', {'network': network, 'action': action, 'target': target});
+
+  @override
+  Future sendTiming(String variableName, int time,
+      {String? category, String? label}) {
+    return _log('timing', {
+      'variableName': variableName,
+      'time': time,
+      'category': category,
+      'label': label
+    });
+  }
+
+  Future<void> _log(String hitType, Map message) async {
+    final encoded = json.encode({'hitType': hitType, 'message': message});
+    stderr.writeln('[analytics]: $encoded');
+  }
+}
diff --git a/tool/test.dart b/tool/test.dart
index 2f4769d..7d98156 100755
--- a/tool/test.dart
+++ b/tool/test.dart
@@ -3,8 +3,6 @@
 // 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.
 
-// @dart=2.10
-
 /// Test wrapper script.
 /// Many of the integration tests runs the `pub` command, this is slow if every
 /// invocation requires the dart compiler to load all the sources. This script
@@ -19,7 +17,7 @@
 import 'package:pub/src/exceptions.dart';
 
 Future<void> main(List<String> args) async {
-  Process testProcess;
+  Process? testProcess;
   final sub = ProcessSignal.sigint.watch().listen((signal) {
     testProcess?.kill(signal);
   });
@@ -31,7 +29,7 @@
     await precompile(
         executablePath: path.join('bin', 'pub.dart'),
         outputPath: pubSnapshotFilename,
-        incrementalDillOutputPath: pubSnapshotIncrementalFilename,
+        incrementalDillPath: pubSnapshotIncrementalFilename,
         name: 'bin/pub.dart',
         packageConfigPath: path.join('.dart_tool', 'package_config.json'));
     testProcess = await Process.start(