diff --git a/analysis_options.yaml b/analysis_options.yaml
index f07a060..6177eba 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -31,6 +31,7 @@
     - sort_pub_dependencies
     - test_types_in_equals
     - throw_in_finally
+    - unawaited_futures
     - unnecessary_lambdas
     - unnecessary_null_aware_assignments
     - unnecessary_parenthesis
diff --git a/doc/repository-spec-v2.md b/doc/repository-spec-v2.md
index aa65e14..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 */
diff --git a/lib/pub.dart b/lib/pub.dart
index 5b3054f..71de9d6 100644
--- a/lib/pub.dart
+++ b/lib/pub.dart
@@ -18,8 +18,12 @@
 ///
 /// If [analytics] is given, pub will use that analytics instance to send
 /// statistics about resolutions.
-Command<int> pubCommand({PubAnalytics? analytics}) =>
-    PubEmbeddableCommand(analytics);
+///
+/// [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/command.dart b/lib/src/command.dart
index 3f06db1..556c04f 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -10,6 +10,7 @@
 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';
@@ -55,7 +56,11 @@
     return a;
   }
 
-  String get directory => argResults['directory'] ?? _pubTopLevel.directory;
+  String get directory =>
+      (argResults.options.contains('directory')
+          ? argResults['directory']
+          : null) ??
+      _pubTopLevel.directory;
 
   late final SystemCache cache = SystemCache(isOffline: isOffline);
 
@@ -170,12 +175,11 @@
   @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<void>(() async => runProtected(),
           captureStackChains: _pubTopLevel.captureStackChains);
@@ -187,8 +191,24 @@
       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.
@@ -196,16 +216,23 @@
             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();
     }
   }
@@ -288,6 +315,10 @@
     }
     _command = list.join(' ');
   }
+
+  String get transcriptPath {
+    return p.join(cache.rootDir, 'log', 'pub_log.txt');
+  }
 }
 
 abstract class PubTopLevel {
diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index f268381..328d935 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -13,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';
@@ -33,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
@@ -53,19 +55,24 @@
   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:
@@ -84,23 +91,28 @@
     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');
   }
 
   @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();
 
-    final packageInformation = _parsePackage(argResults.rest.first);
-    final package = packageInformation.first;
-
-    /// Perform version resolution in-memory.
-    final updatedPubSpec =
-        await _addPackageToPubspec(entrypoint.root.pubspec, package);
+    var updatedPubSpec = entrypoint.root.pubspec;
+    for (final update in updates) {
+      /// Perform version resolution in-memory.
+      updatedPubSpec =
+          await _addPackageToPubspec(updatedPubSpec, update.packageRange);
+    }
 
     late SolveResult solveResult;
 
@@ -113,7 +125,9 @@
       solveResult = await resolveVersions(
           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);
@@ -122,33 +136,34 @@
       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.
-    var constraint = package.constraint;
-    if (!constraint.allows(resultPackage.version)) {
-      var dependencyOverrides = updatedPubSpec.dependencyOverrides;
-      if (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)
+      await Entrypoint.inMemory(newRoot, cache,
+              solveResult: solveResult, lockFile: entrypoint.lockFile)
           .acquireDependencies(SolveType.get,
               dryRun: true,
               precompile: argResults['precompile'],
@@ -158,7 +173,7 @@
       /// 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.
@@ -274,9 +289,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'],
@@ -380,13 +393,18 @@
           .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 = cache.hosted.source
@@ -408,66 +426,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: () => YamlScalar.wrap(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: () => YamlScalar.wrap(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".');
+        }
       }
     }
 
@@ -475,3 +491,9 @@
     writeTextFile(entrypoint.pubspecPath, yamlEditor.toString());
   }
 }
+
+class _ParseResult {
+  PackageRange packageRange;
+  Object? description;
+  _ParseResult(this.packageRange, this.description);
+}
diff --git a/lib/src/command/cache_repair.dart b/lib/src/command/cache_repair.dart
index 41bae2b..74968be 100644
--- a/lib/src/command/cache_repair.dart
+++ b/lib/src/command/cache_repair.dart
@@ -23,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/lish.dart b/lib/src/command/lish.dart
index b226c55..c927e07 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -91,7 +91,7 @@
 
     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);
 
diff --git a/lib/src/command/remove.dart b/lib/src/command/remove.dart
index af91d8c..f3b27bd 100644
--- a/lib/src/command/remove.dart
+++ b/lib/src/command/remove.dart
@@ -65,7 +65,7 @@
       final newPubspec = _removePackagesFromPubspec(rootPubspec, packages);
       final newRoot = Package.inMemory(newPubspec);
 
-      await Entrypoint.global(newRoot, entrypoint.lockFile, cache)
+      await Entrypoint.inMemory(newRoot, cache, lockFile: entrypoint.lockFile)
           .acquireDependencies(SolveType.get,
               precompile: argResults['precompile'],
               dryRun: true,
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 34e061d..3063770 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -222,11 +222,10 @@
     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(
+      await Entrypoint.inMemory(
         Package.inMemory(resolvablePubspec),
-        entrypoint.lockFile,
         cache,
+        lockFile: entrypoint.lockFile,
         solveResult: solveResult,
       ).acquireDependencies(
         SolveType.upgrade,
@@ -317,10 +316,10 @@
       // 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(
+      await Entrypoint.inMemory(
         Package.inMemory(nullsafetyPubspec),
-        entrypoint.lockFile,
         cache,
+        lockFile: entrypoint.lockFile,
         solveResult: solveResult,
       ).acquireDependencies(
         SolveType.upgrade,
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index 02faaf9..21f568b 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -72,6 +72,7 @@
       default:
         // No specific verbosity given, so check for the shortcut.
         if (argResults['verbose']) return log.Verbosity.all;
+        if (runningFromTest) return log.Verbosity.testing;
         return log.Verbosity.normal;
     }
   }
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index 97219dc..f4f8686 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -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 453ef56..26a8243 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -10,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';
@@ -72,8 +72,14 @@
 /// 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;
@@ -82,7 +88,8 @@
   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.
   ///
@@ -122,8 +129,7 @@
   ///
   /// 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.
   ///
@@ -152,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');
@@ -173,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 passed as an optimization.
-  Entrypoint.global(this.root, this._lockFile, this.cache,
-      {SolveResult? solveResult})
-      : isGlobal = true {
+  Entrypoint.global(this.globalDir, this.root, this._lockFile, this.cache,
+      {SolveResult? solveResult}) {
     if (solveResult != null) {
       _packageGraph = PackageGraph.fromSolveResult(this, solveResult);
     }
@@ -202,18 +214,20 @@
 
   /// Writes .packages and .dart_tool/package_config.json
   Future<void> writePackagesFiles() async {
+    final entrypointName = isGlobal ? null : root.name;
     writeTextFile(
         packagesFile,
         lockFile.packagesFile(cache,
-            entrypoint: root.name, relativeFrom: root.dir));
+            entrypoint: entrypointName,
+            relativeFrom: isGlobal ? null : root.dir));
     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.
@@ -250,8 +264,7 @@
     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,
@@ -294,7 +307,7 @@
       await result.showReport(type, cache);
     }
     if (!dryRun) {
-      await Future.wait(result.packages.map(_get));
+      await result.downloadCachedPackages(cache);
       _saveLockFile(result);
     }
     if (onlyReportSuccessOrFailure) {
@@ -387,10 +400,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)}');
@@ -470,21 +484,6 @@
     }
   }
 
-  /// 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.
   ///
@@ -872,7 +871,9 @@
   }
 
   /// 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.min == null) {
       // Suggest version range '>=2.10.0 <3.0.0', we avoid using:
@@ -902,6 +903,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/global_packages.dart b/lib/src/global_packages.dart
index a868b4b..773f2ec 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -12,7 +12,6 @@
 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;
@@ -62,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');
 
@@ -151,26 +152,19 @@
     // Get the package's dependencies.
     await entrypoint.acquireDependencies(SolveType.get, analytics: analytics);
     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);
@@ -178,17 +172,12 @@
   }
 
   /// Installs the package [dep] and its dependencies into the system cache.
+  ///
+  /// If [silent] less logging will be printed.
   Future<void> _installInCache(PackageRange dep, List<String>? executables,
-      {required 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.
-    }
+      {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',
@@ -200,103 +189,89 @@
     // 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 `$topLevelProgram pub 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]!),
       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) {
+  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;
@@ -312,6 +287,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].
@@ -341,22 +317,8 @@
     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
@@ -370,7 +332,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.
@@ -446,16 +409,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;
@@ -542,17 +495,24 @@
           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 '
@@ -706,10 +666,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');
@@ -761,6 +718,7 @@
     // 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 (fileExists(snapshot)) {
         // We expect absolute paths from the precompiler since relative ones
@@ -786,7 +744,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}
@@ -795,7 +753,6 @@
 rem Script: $script
 $invocation
 ''';
-      writeTextFile(binStubPath, batch);
     } else {
       if (fileExists(snapshot)) {
         // We expect absolute paths from the precompiler since relative ones
@@ -818,7 +775,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}
@@ -827,25 +784,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;
diff --git a/lib/src/ignore.dart b/lib/src/ignore.dart
index fbd93e8..ff22dff 100644
--- a/lib/src/ignore.dart
+++ b/lib/src/ignore.dart
@@ -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 231aba5..280dcbd 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -361,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.
@@ -451,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].
@@ -966,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.');
     }
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 e2723c7..8f563ab 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -218,7 +218,7 @@
   String packagesFile(
     SystemCache cache, {
     String? entrypoint,
-    required String relativeFrom,
+    String? relativeFrom,
   }) {
     var header = '''
 This file is deprecated. Tools should instead consume 
@@ -256,7 +256,7 @@
     SystemCache cache, {
     String? entrypoint,
     VersionConstraint? entrypointSdkConstraint,
-    required String relativeFrom,
+    String? relativeFrom,
   }) async {
     final entries = <PackageConfigEntry>[];
     for (final name in ordered(packages.keys)) {
@@ -306,8 +306,9 @@
   /// 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) {
diff --git a/lib/src/log.dart b/lib/src/log.dart
index 930e0fb..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';
 
@@ -36,7 +38,7 @@
 
 /// 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.
 ///
@@ -168,6 +170,16 @@
     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);
 
   final String name;
@@ -233,7 +245,7 @@
   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]
@@ -313,18 +325,10 @@
   }
 }
 
-/// Enables recording of log entries.
-void recordTranscript() {
-  _transcript = Transcript<_Entry>(_maxTranscript);
-}
-
-/// 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,
@@ -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/package.dart b/lib/src/package.dart
index 0bc6f89..d100157 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -83,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();
diff --git a/lib/src/pub_embeddable_command.dart b/lib/src/pub_embeddable_command.dart
index a46e3f5..b9e59c6 100644
--- a/lib/src/pub_embeddable_command.dart
+++ b/lib/src/pub_embeddable_command.dart
@@ -58,11 +58,13 @@
   @override
   final PubAnalytics? analytics;
 
-  PubEmbeddableCommand(this.analytics) : super() {
+  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',
@@ -101,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/solver/result.dart b/lib/src/solver/result.dart
index b8ff0e5..bc01162 100644
--- a/lib/src/solver/result.dart
+++ b/lib/src/solver/result.dart
@@ -5,6 +5,7 @@
 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;
@@ -12,6 +13,7 @@
 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';
@@ -78,6 +80,18 @@
 
   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.
diff --git a/lib/src/source.dart b/lib/src/source.dart
index add55fa..ecbc5eb 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -116,7 +116,7 @@
   /// [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;
   }
 
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 0d98ae9..3be752c 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -108,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());
     }
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 1cb1950..2cfc52a 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -159,7 +159,7 @@
   }
 
   @override
-  dynamic serializeDescription(String containingPath, description) {
+  dynamic serializeDescription(String? containingPath, description) {
     final desc = _asDescription(description);
     return _serializedDescriptionFor(desc.packageName, desc.uri);
   }
@@ -232,8 +232,7 @@
       return _HostedDescription(packageName, defaultUrl);
     }
 
-    final canUseShorthandSyntax =
-        languageVersion >= _minVersionForShorterHostedSyntax;
+    final canUseShorthandSyntax = languageVersion.supportsShorterHostedSyntax;
 
     if (description is String) {
       // Old versions of pub (pre Dart 2.15) interpret `hosted: foo` as
@@ -256,7 +255,7 @@
         } else {
           throw FormatException(
             'Using `hosted: <url>` is only supported with a minimum SDK '
-            'constraint of $_minVersionForShorterHostedSyntax.',
+            'constraint of ${LanguageVersion.firstVersionWithShorterHostedSyntax}.',
           );
         }
       }
@@ -271,7 +270,7 @@
 
     if (name is! String) {
       throw FormatException("The 'name' key must have a string value without "
-          'a minimum Dart SDK constraint of $_minVersionForShorterHostedSyntax.0 or higher.');
+          'a minimum Dart SDK constraint of ${LanguageVersion.firstVersionWithShorterHostedSyntax}.0 or higher.');
     }
 
     var url = defaultUrl;
@@ -286,21 +285,6 @@
     return _HostedDescription(name, url);
   }
 
-  /// 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.
-  static const LanguageVersion _minVersionForShorterHostedSyntax =
-      LanguageVersion(2, 15);
-
   static final RegExp _looksLikePackageName =
       RegExp(r'^[a-zA-Z_]+[a-zA-Z0-9_]*$');
 }
@@ -704,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) {
@@ -812,14 +797,13 @@
       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);
     });
   }
 
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index 0401ebd..4679f98 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -130,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)),
diff --git a/lib/src/validator/changelog.dart b/lib/src/validator/changelog.dart
index 694ace2..614eba2 100644
--- a/lib/src/validator/changelog.dart
+++ b/lib/src/validator/changelog.dart
@@ -21,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;
       }
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index 78bf9aa..4fb49b1 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -71,7 +71,7 @@
 
       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/test/add/common/add_test.dart b/test/add/common/add_test.dart
index 69f33fe..1a1e9df 100644
--- a/test/add/common/add_test.dart
+++ b/test/add/common/add_test.dart
@@ -37,29 +37,6 @@
   });
 
   group('normally', () {
-    test('fails if extra arguments are passed', () async {
-      final server = await servePackages();
-      server.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 {
       final server = await servePackages();
       server.serve('foo', '1.2.3');
@@ -73,6 +50,24 @@
       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.appPackagesFile(
+          {'foo': '1.2.3', 'bar': '1.1.0', 'baz': '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 {
diff --git a/test/add/git/git_test.dart b/test/add/git/git_test.dart
index 7bc51f0..00bd334 100644
--- a/test/add/git/git_test.dart
+++ b/test/add/git/git_test.dart
@@ -184,4 +184,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/hosted/non_default_pub_server_test.dart b/test/add/hosted/non_default_pub_server_test.dart
index eea6c09..0a83ac5 100644
--- a/test/add/hosted/non_default_pub_server_test.dart
+++ b/test/add/hosted/non_default_pub_server_test.dart
@@ -35,6 +35,46 @@
     }).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.appPackagesFile(
+        {'foo': '1.2.3', 'bar': '3.2.3', 'baz': '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();
 
diff --git a/test/add/path/absolute_path_test.dart b/test/add/path/absolute_path_test.dart
index 5b7e726..b15ec2d 100644
--- a/test/add/path/absolute_path_test.dart
+++ b/test/add/path/absolute_path_test.dart
@@ -41,6 +41,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();
 
diff --git a/test/embedding/embedding_test.dart b/test/embedding/embedding_test.dart
index ccb16f6..a9795ea 100644
--- a/test/embedding/embedding_test.dart
+++ b/test/embedding/embedding_test.dart
@@ -6,8 +6,10 @@
 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';
@@ -16,6 +18,8 @@
 
 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> runEmbeddingToBuffer(
   List<String> args,
@@ -37,12 +41,9 @@
 
   buffer.writeln([
     '\$ $_commandRunner ${args.join(' ')}',
-    ...await process.stdout.rest.toList(),
+    ...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');
 }
 
@@ -51,7 +52,7 @@
   /// next section in golden file.
   Future<void> runEmbedding(
     List<String> args, {
-    String? workingDirextory,
+    String? workingDirectory,
     Map<String, String>? environment,
     dynamic exitCode = 0,
   }) async {
@@ -59,7 +60,7 @@
     await runEmbeddingToBuffer(
       args,
       buffer,
-      workingDirectory: workingDirextory,
+      workingDirectory: workingDirectory,
       environment: environment,
       exitCode: exitCode,
     );
@@ -82,6 +83,7 @@
   });
 
   testWithGolden('run works, though hidden', (ctx) async {
+    await servePackages();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
@@ -101,12 +103,53 @@
     ]).create();
     await ctx.runEmbedding(
       ['pub', 'get'],
-      workingDirextory: d.path(appPath),
+      workingDirectory: d.path(appPath),
     );
     await ctx.runEmbedding(
       ['pub', 'run', 'bin/main.dart'],
       exitCode: 123,
-      workingDirextory: d.path(appPath),
+      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(),
+      ),
     );
   });
 
@@ -175,5 +218,113 @@
         }
       },
     });
+    // 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/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/global/activate/activate_hosted_after_git_test.dart b/test/global/activate/activate_hosted_after_git_test.dart
index 58c65fb..1546280 100644
--- a/test/global/activate/activate_hosted_after_git_test.dart
+++ b/test/global/activate/activate_hosted_after_git_test.dart
@@ -2,6 +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.
 
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -21,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_twice_test.dart b/test/global/activate/activate_hosted_twice_test.dart
index 9baed5b..a4d1337 100644
--- a/test/global/activate/activate_hosted_twice_test.dart
+++ b/test/global/activate/activate_hosted_twice_test.dart
@@ -24,15 +24,7 @@
         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.
diff --git a/test/global/activate/activate_path_after_hosted_test.dart b/test/global/activate/activate_path_after_hosted_test.dart
index cfc28fd..01d493d 100644
--- a/test/global/activate/activate_path_after_hosted_test.dart
+++ b/test/global/activate/activate_path_after_hosted_test.dart
@@ -10,7 +10,7 @@
 import '../../test_pub.dart';
 
 void main() {
-  test('activating a hosted package deactivates the path one', () async {
+  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');")])
diff --git a/test/global/activate/reactivating_git_upgrades_test.dart b/test/global/activate/reactivating_git_upgrades_test.dart
index 100aaea..68102da 100644
--- a/test/global/activate/reactivating_git_upgrades_test.dart
+++ b/test/global/activate/reactivating_git_upgrades_test.dart
@@ -2,6 +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.
 
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -29,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 3392aff..0000000
--- a/test/global/activate/removes_old_lockfile_test.dart
+++ /dev/null
@@ -1,33 +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.
-
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('removes the 1.6-style lockfile', () async {
-    final server = await servePackages();
-    server.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/deactivate/git_package_test.dart b/test/global/deactivate/git_package_test.dart
index f06ce53..7fc2855 100644
--- a/test/global/deactivate/git_package_test.dart
+++ b/test/global/deactivate/git_package_test.dart
@@ -2,6 +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.
 
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -18,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/list_test.dart b/test/global/list_test.dart
index 8d4d02e..00ca22c 100644
--- a/test/global/list_test.dart
+++ b/test/global/list_test.dart
@@ -30,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 {
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 1afab81..0000000
--- a/test/global/run/uses_old_lockfile_test.dart
+++ /dev/null
@@ -1,54 +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.
-
-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()
-      ..serve('bar', '1.0.0')
-      ..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/ignore_test.dart b/test/ignore_test.dart
index 42a189a..a8ab8ac 100644
--- a/test/ignore_test.dart
+++ b/test/ignore_test.dart
@@ -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/lish/archives_and_uploads_a_package_test.dart b/test/lish/archives_and_uploads_a_package_test.dart
index 5bbac54..88e4862 100644
--- a/test/lish/archives_and_uploads_a_package_test.dart
+++ b/test/lish/archives_and_uploads_a_package_test.dart
@@ -37,6 +37,36 @@
     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!'}
+      }));
+    });
+
+    expect(pub.stdout, emits(startsWith('Uploading...')));
+    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    await pub.shouldExit(exit_codes.SUCCESS);
+  });
+
   // This is a regression test for #1679. We create a submodule that's not
   // checked out to ensure that file listing doesn't choke on the empty
   // directory.
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/upload_form_fields_has_a_non_string_value_test.dart b/test/lish/upload_form_fields_has_a_non_string_value_test.dart
index 290e807..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
@@ -24,7 +24,7 @@
       'url': 'http://example.com/upload',
       'fields': {'field': 12}
     };
-    handleUploadForm(globalServer, 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 ed87da6..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
@@ -21,7 +21,7 @@
     await confirmPublish(pub);
 
     var body = {'url': 'http://example.com/upload', 'fields': 12};
-    handleUploadForm(globalServer, 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 79b6e0b..b0032f8 100644
--- a/test/lish/upload_form_is_missing_fields_test.dart
+++ b/test/lish/upload_form_is_missing_fields_test.dart
@@ -21,7 +21,7 @@
     await confirmPublish(pub);
 
     var body = {'url': 'http://example.com/upload'};
-    handleUploadForm(globalServer, 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 e3986cb..eae43ea 100644
--- a/test/lish/upload_form_is_missing_url_test.dart
+++ b/test/lish/upload_form_is_missing_url_test.dart
@@ -24,7 +24,7 @@
       'fields': {'field1': 'value1', 'field2': 'value2'}
     };
 
-    handleUploadForm(globalServer, 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_url_is_not_a_string_test.dart b/test/lish/upload_form_url_is_not_a_string_test.dart
index 784289f..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
@@ -25,7 +25,7 @@
       'fields': {'field1': 'value1', 'field2': 'value2'}
     };
 
-    handleUploadForm(globalServer, 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/utils.dart b/test/lish/utils.dart
index 8e1ed5f..f134e4a 100644
--- a/test/lish/utils.dart
+++ b/test/lish/utils.dart
@@ -9,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/test_pub.dart b/test/test_pub.dart
index e180f3d..fbf63de 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -373,11 +373,12 @@
   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,
+    'PUB_HOSTED_URL': server.url + path,
     '_PUB_TEST_AUTH_METHOD': authMethod,
     if (environment != null) ...environment,
   });
@@ -465,7 +466,7 @@
 
   var dartArgs = ['--packages=$dotPackagesPath', '--enable-asserts'];
   dartArgs
-    ..addAll([pubPath, if (verbose) '--verbose'])
+    ..addAll([pubPath, if (!verbose) '--verbosity=normal'])
     ..addAll(args);
 
   final mergedEnvironment = getPubTestEnvironment(tokenEndpoint);
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..1d3a5f5
--- /dev/null
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -0,0 +1,328 @@
+# 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 .packages.
+[E] FINE: Contents:
+[E]    | # This file is deprecated. Tools should instead consume 
+[E]    | # `.dart_tool/package_config.json`.
+[E]    | # 
+[E]    | # For more info see: https://dart.dev/go/dot-packages-deprecation
+[E]    | # 
+[E]    | # Generated by pub on $TIME
+[E]    | foo:file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0/lib/
+[E]    | myapp:lib/
+[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 .packages.
+FINE: Contents:
+   | # This file is deprecated. Tools should instead consume 
+   | # `.dart_tool/package_config.json`.
+   | # 
+   | # For more info see: https://dart.dev/go/dot-packages-deprecation
+   | # 
+   | # Generated by pub on $TIME
+   | foo:file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0/lib/
+   | myapp:lib/
+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/help_test/pub add --help.txt b/test/testdata/goldens/help_test/pub add --help.txt
index 5d32e5e..2a37c21 100644
--- a/test/testdata/goldens/help_test/pub add --help.txt
+++ b/test/testdata/goldens/help_test/pub add --help.txt
@@ -2,22 +2,23 @@
 
 ## Section 0
 $ pub add --help
-Add a dependency to pubspec.yaml.
+Add dependencies to pubspec.yaml.
 
-Usage: pub add <package>[:<constraint>] [options]
+Usage: pub add <package>[:<constraint>] [<package2>[:<constraint2>]...] [options]
 -h, --help               Print this usage information.
--d, --dev                Adds package to the development dependencies instead.
+-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               Local path
-    --sdk                SDK source for package
+    --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>.
+-C, --directory=<dir>    Run this in the directory <dir>.
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-add for detailed documentation.
diff --git a/tool/test-bin/pub_command_runner.dart b/tool/test-bin/pub_command_runner.dart
index 2be51e8..e438aae 100644
--- a/tool/test-bin/pub_command_runner.dart
+++ b/tool/test-bin/pub_command_runner.dart
@@ -9,12 +9,30 @@
 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: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> {
   late ArgResults _options;
 
@@ -23,7 +41,10 @@
         ? PubAnalytics(() => loggingAnalytics,
             dependencyKindCustomDimensionName: 'cd1')
         : null;
-    addCommand(pubCommand(analytics: analytics));
+    addCommand(
+        pubCommand(analytics: analytics, isVerbose: () => _options['verbose'])
+          ..addSubcommand(ThrowingCommand()));
+    argParser.addFlag('verbose');
   }
 
   @override
diff --git a/tool/test.dart b/tool/test.dart
index e7877a2..7d98156 100755
--- a/tool/test.dart
+++ b/tool/test.dart
@@ -29,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(