diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 9940883..fdb170b 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -12,3 +12,7 @@
       interval: monthly
     labels:
       - autosubmit
+    groups:
+      github-actions:
+        patterns:
+          - "*"
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 6a96ed7..b5dc528 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -24,8 +24,8 @@
       matrix:
         sdk: [dev]
     steps:
-      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
-      - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+      - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b
+      - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30
         with:
           sdk: ${{ matrix.sdk }}
       - id: install
@@ -52,8 +52,8 @@
         sdk: [dev]
         shard: [0, 1, 2, 3, 4, 5, 6]
     steps:
-      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
-      - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+      - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b
+      - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30
         with:
           sdk: ${{ matrix.sdk }}
       - name: Install dependencies
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 8b82c82..29602e0 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -33,6 +33,7 @@
     - package_api_docs
     - prefer_asserts_in_initializer_lists
     - prefer_const_declarations
+    - prefer_final_locals
     - prefer_relative_imports
     - prefer_single_quotes
     - require_trailing_commas
diff --git a/lib/src/ascii_tree.dart b/lib/src/ascii_tree.dart
index 58e93f4..fbd9c84 100644
--- a/lib/src/ascii_tree.dart
+++ b/lib/src/ascii_tree.dart
@@ -66,7 +66,7 @@
   required bool showFileSizes,
 }) {
   // Parse out the files into a tree of nested maps.
-  var root = <String, Map>{};
+  final root = <String, Map>{};
   for (var file in files) {
     final relativeFile =
         baseDir == null ? file : p.relative(file, from: baseDir);
@@ -117,7 +117,7 @@
 ///     '   '---logging
 ///     '---barback
 String fromMap(Map<String, Map> map, {bool startingAtTop = true}) {
-  var buffer = StringBuffer();
+  final buffer = StringBuffer();
   _draw(buffer, '', null, map, depth: startingAtTop ? 0 : 1);
   return buffer.toString();
 }
@@ -162,10 +162,10 @@
   if (name != null) _drawLine(buffer, prefix, isLast, name, depth <= 1);
 
   // Recurse to the children.
-  var childNames = ordered(children.keys);
+  final childNames = ordered(children.keys);
 
   void drawChild(bool isLastChild, String child) {
-    var childPrefix = _getPrefix(depth <= 1, isLast);
+    final childPrefix = _getPrefix(depth <= 1, isLast);
     _draw(
       buffer,
       '$prefix$childPrefix',
diff --git a/lib/src/authentication/token_store.dart b/lib/src/authentication/token_store.dart
index a34fcd4..0f0cf42 100644
--- a/lib/src/authentication/token_store.dart
+++ b/lib/src/authentication/token_store.dart
@@ -186,7 +186,7 @@
   ///
   /// `null` if no config directory could be found.
   String? get tokensFile {
-    var dir = configDir;
+    final dir = configDir;
     return dir == null ? null : p.join(dir, 'pub-tokens.json');
   }
 }
diff --git a/lib/src/command.dart b/lib/src/command.dart
index 61f8cde..3014a1c 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -121,7 +121,7 @@
   Command get _topCommand {
     Command current = this;
     while (true) {
-      var parent = current.parent;
+      final parent = current.parent;
       if (parent == null) return current;
       current = parent;
     }
@@ -145,7 +145,7 @@
   @override
   String get invocation {
     PubCommand? command = this;
-    var names = <String?>[];
+    final names = <String?>[];
     do {
       names.add(command?.name);
       command = command?.parent as PubCommand?;
@@ -254,7 +254,7 @@
   /// appropriate exit code could be found.
   int _chooseExitCode(Object exception) {
     if (exception is SolveFailure) {
-      var packageNotFound = exception.packageNotFound;
+      final packageNotFound = exception.packageNotFound;
       if (packageNotFound != null) exception = packageNotFound;
     }
     while (exception is WrappedException && exception.innerError is Exception) {
@@ -322,7 +322,7 @@
   }
 
   static void _computeCommand(ArgResults argResults) {
-    var list = <String?>[];
+    final list = <String?>[];
     for (var command = argResults.command;
         command != null;
         command = command.command) {
diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index a310515..502294a 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -300,8 +300,8 @@
   ) async {
     final name = package.ref.name;
     final dependencies = [...original.dependencies.values];
-    var devDependencies = [...original.devDependencies.values];
-    var dependencyOverrides = [...original.dependencyOverrides.values];
+    final devDependencies = [...original.devDependencies.values];
+    final dependencyOverrides = [...original.dependencyOverrides.values];
 
     final dependencyNames = dependencies.map((dependency) => dependency.name);
     final devDependencyNames =
diff --git a/lib/src/command/cache_add.dart b/lib/src/command/cache_add.dart
index cbcbda1..693631f 100644
--- a/lib/src/command/cache_add.dart
+++ b/lib/src/command/cache_add.dart
@@ -42,12 +42,12 @@
 
     // Don't allow extra arguments.
     if (argResults.rest.length > 1) {
-      var unexpected = argResults.rest.skip(1).map((arg) => '"$arg"');
-      var arguments = pluralize('argument', unexpected.length);
+      final unexpected = argResults.rest.skip(1).map((arg) => '"$arg"');
+      final arguments = pluralize('argument', unexpected.length);
       usageException('Unexpected $arguments ${toSentence(unexpected)}.');
     }
 
-    var package = argResults.rest.single;
+    final package = argResults.rest.single;
 
     // Parse the version constraint, if there is one.
     var constraint = VersionConstraint.any;
@@ -61,10 +61,10 @@
     }
 
     // TODO(rnystrom): Support installing from git too.
-    var source = cache.hosted;
+    final source = cache.hosted;
 
     // TODO(rnystrom): Allow specifying the server.
-    var ids = (await cache.getVersions(source.refFor(package)))
+    final ids = (await cache.getVersions(source.refFor(package)))
         .where((id) => constraint.allows(id.version))
         .toList();
 
diff --git a/lib/src/command/cache_list.dart b/lib/src/command/cache_list.dart
index 76faaf0..ce3eb1b 100644
--- a/lib/src/command/cache_list.dart
+++ b/lib/src/command/cache_list.dart
@@ -22,11 +22,11 @@
   @override
   Future<void> runProtected() async {
     // TODO(keertip): Add flag to list packages from non default sources.
-    var packagesObj = <String, Map>{};
+    final packagesObj = <String, Map>{};
 
-    var source = cache.defaultSource as CachedSource;
+    final source = cache.defaultSource as CachedSource;
     for (var package in source.getCachedPackages(cache)) {
-      var packageInfo = packagesObj.putIfAbsent(package.name, () => {});
+      final packageInfo = packagesObj.putIfAbsent(package.name, () => {});
       packageInfo[package.version.toString()] = {'location': package.dir};
     }
 
diff --git a/lib/src/command/cache_repair.dart b/lib/src/command/cache_repair.dart
index 8e5892f..b617394 100644
--- a/lib/src/command/cache_repair.dart
+++ b/lib/src/command/cache_repair.dart
@@ -37,15 +37,15 @@
     final failures = repairResults.where((result) => !result.success);
 
     if (successes.isNotEmpty) {
-      var packages = pluralize('package', successes.length);
+      final packages = pluralize('package', successes.length);
       log.message(
         'Reinstalled ${log.green(successes.length.toString())} $packages.',
       );
     }
 
     if (failures.isNotEmpty) {
-      var packages = pluralize('package', failures.length);
-      var buffer = StringBuffer(
+      final packages = pluralize('package', failures.length);
+      final buffer = StringBuffer(
         'Failed to reinstall ${log.red(failures.length.toString())} $packages:\n',
       );
 
@@ -60,17 +60,17 @@
       log.message(buffer.toString());
     }
 
-    var (repairSuccesses, repairFailures) =
+    final (repairSuccesses, repairFailures) =
         await globals.repairActivatedPackages();
     if (repairSuccesses.isNotEmpty) {
-      var packages = pluralize('package', repairSuccesses.length);
+      final packages = pluralize('package', repairSuccesses.length);
       log.message(
         'Reactivated ${log.green(repairSuccesses.length.toString())} $packages.',
       );
     }
 
     if (repairFailures.isNotEmpty) {
-      var packages = pluralize('package', repairFailures.length);
+      final packages = pluralize('package', repairFailures.length);
       log.message(
         'Failed to reactivate ${log.red(repairFailures.length.toString())} $packages:',
       );
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index 9e1bcf7..63b81cc 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -374,9 +374,9 @@
           );
           // Remove the now outdated content-hash - it will be restored below
           // after resolution.
-          var packageMap = lockFileEditor
+          final packageMap = lockFileEditor
               .parseAt(['packages', targetPackage, 'description']).value as Map;
-          var hasSha = packageMap.containsKey('sha256');
+          final hasSha = packageMap.containsKey('sha256');
           if (hasSha) {
             lockFileEditor.remove(
               ['packages', targetPackage, 'description', 'sha256'],
diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 313d8a5..a9d370d 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -127,7 +127,7 @@
         });
         toVisit.addAll(next);
       }
-      var executables = [
+      final executables = [
         for (final package in [
           entrypoint.workspaceRoot,
           ...entrypoint.workspaceRoot.immediateDependencies.keys
@@ -211,12 +211,12 @@
       }
       await _outputCompactPackages(
         'dependency overrides',
-        root.dependencyOverrides.keys,
+        root.pubspec.dependencyOverrides.keys,
         buffer,
       );
     }
 
-    var transitive = await _getTransitiveDependencies();
+    final transitive = await _getTransitiveDependencies();
     await _outputCompactPackages('transitive dependencies', transitive, buffer);
   }
 
@@ -231,14 +231,14 @@
     buffer.writeln();
     buffer.writeln('$section:');
     for (var name in ordered(names)) {
-      var package = await _getPackage(name);
+      final package = await _getPackage(name);
 
       buffer.write('- ${_labelPackage(package)}');
       if (package.dependencies.isEmpty) {
         buffer.writeln();
       } else {
-        var depNames = package.dependencies.keys;
-        var depsList = "[${depNames.join(' ')}]";
+        final depNames = package.dependencies.keys;
+        final depsList = "[${depNames.join(' ')}]";
         buffer.writeln(' ${log.gray(depsList)}');
       }
     }
@@ -268,12 +268,12 @@
       }
       await _outputListSection(
         'dependency overrides',
-        root.dependencyOverrides.keys,
+        root.pubspec.dependencyOverrides.keys,
         buffer,
       );
     }
 
-    var transitive = await _getTransitiveDependencies();
+    final transitive = await _getTransitiveDependencies();
     if (transitive.isEmpty) return;
 
     await _outputListSection(
@@ -295,7 +295,7 @@
     buffer.writeln('$name:');
 
     for (var name in deps) {
-      var package = await _getPackage(name);
+      final package = await _getPackage(name);
       buffer.writeln('- ${_labelPackage(package)}');
 
       for (var dep in package.dependencies.values) {
@@ -318,15 +318,15 @@
     // The work list for the breadth-first traversal. It contains the package
     // being added to the tree, and the parent map that will receive that
     // package.
-    var toWalk = Queue<(Package, Map<String, Map>)>();
-    var visited = <String>{};
+    final toWalk = Queue<(Package, Map<String, Map>)>();
+    final visited = <String>{};
 
     // Start with the root dependencies.
-    var packageTree = <String, Map>{};
+    final packageTree = <String, Map>{};
     final workspacePackageNames = [
       ...entrypoint.workspaceRoot.transitiveWorkspace.map((p) => p.name),
     ];
-    var immediateDependencies =
+    final immediateDependencies =
         entrypoint.workspaceRoot.immediateDependencies.keys.toSet();
     if (!_includeDev) {
       immediateDependencies
@@ -346,7 +346,7 @@
       }
 
       // Populate the map with this package's dependencies.
-      var childMap = <String, Map>{};
+      final childMap = <String, Map>{};
       map[_labelPackage(package)] = childMap;
 
       final isRoot = workspacePackageNames.contains(package.name);
@@ -370,14 +370,14 @@
 
   /// Gets the names of the non-immediate dependencies of the workspace packages.
   Future<Set<String>> _getTransitiveDependencies() async {
-    var transitive = await _getAllDependencies();
+    final transitive = await _getAllDependencies();
     for (final root in entrypoint.workspaceRoot.transitiveWorkspace) {
       transitive.remove(root.name);
       transitive.removeAll(root.dependencies.keys);
       if (_includeDev) {
         transitive.removeAll(root.devDependencies.keys);
       }
-      transitive.removeAll(root.dependencyOverrides.keys);
+      transitive.removeAll(root.pubspec.dependencyOverrides.keys);
     }
     return transitive;
   }
@@ -388,10 +388,10 @@
       return graph.packages.keys.toSet();
     }
 
-    var nonDevDependencies = [
+    final nonDevDependencies = [
       for (final package in entrypoint.workspaceRoot.transitiveWorkspace) ...[
         ...package.dependencies.keys,
-        ...package.dependencyOverrides.keys,
+        ...package.pubspec.dependencyOverrides.keys,
       ],
     ];
     return nonDevDependencies
@@ -407,7 +407,7 @@
   /// but it's possible, since [Entrypoint.assertUpToDate]'s modification time
   /// check can return a false negative. This fails gracefully if that happens.
   Future<Package> _getPackage(String name) async {
-    var package = (await entrypoint.packageGraph).packages[name];
+    final package = (await entrypoint.packageGraph).packages[name];
     if (package != null) return package;
     dataError('The pubspec.yaml file has changed since the pubspec.lock file '
         'was generated, please run "$topLevelProgram pub get" again.');
@@ -426,7 +426,7 @@
     };
 
     for (var package in packages) {
-      var executables = package.executableNames;
+      final executables = package.executableNames;
       if (executables.isNotEmpty) {
         buffer.writeln(_formatExecutables(package.name, executables.toList()));
       }
diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart
index 6055d5f..ae724dc 100644
--- a/lib/src/command/downgrade.dart
+++ b/lib/src/command/downgrade.dart
@@ -5,10 +5,10 @@
 import 'dart:async';
 
 import '../command.dart';
+import '../command_runner.dart';
 import '../log.dart' as log;
 import '../solver.dart';
 
-/// Handles the `downgrade` pub command.
 class DowngradeCommand extends PubCommand {
   @override
   String get name => 'downgrade';
@@ -23,6 +23,12 @@
   @override
   bool get isOffline => argResults.flag('offline');
 
+  bool get _dryRun => argResults.flag('dry-run');
+
+  bool get _tighten => argResults.flag('tighten');
+
+  bool get _example => argResults.flag('example');
+
   DowngradeCommand() {
     argParser.addFlag(
       'offline',
@@ -51,6 +57,13 @@
       help: 'Run this in the directory <dir>.',
       valueHelp: 'dir',
     );
+
+    argParser.addFlag(
+      'tighten',
+      help:
+          'Updates lower bounds in pubspec.yaml to match the resolved version.',
+      negatable: false,
+    );
   }
 
   @override
@@ -62,23 +75,32 @@
         ),
       );
     }
-    var dryRun = argResults.flag('dry-run');
 
     await entrypoint.acquireDependencies(
       SolveType.downgrade,
       unlock: argResults.rest,
-      dryRun: dryRun,
+      dryRun: _dryRun,
     );
-    var example = entrypoint.example;
+    final example = entrypoint.example;
     if (argResults.flag('example') && example != null) {
       await example.acquireDependencies(
         SolveType.get,
         unlock: argResults.rest,
-        dryRun: dryRun,
+        dryRun: _dryRun,
         summaryOnly: true,
       );
     }
 
+    if (_tighten) {
+      if (_example && entrypoint.example != null) {
+        log.warning(
+          'Running `downgrade --tighten` only in `${entrypoint.workspaceRoot.dir}`. Run `$topLevelProgram pub upgrade --tighten --directory example/` separately.',
+        );
+      }
+      final changes = entrypoint.tighten();
+      entrypoint.applyChanges(changes, _dryRun);
+    }
+
     if (isOffline) {
       log.warning('Warning: Downgrading when offline may not update you to '
           'the oldest versions of your dependencies.');
diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart
index c795ec7..ce9ea76 100644
--- a/lib/src/command/get.dart
+++ b/lib/src/command/get.dart
@@ -80,7 +80,7 @@
       enforceLockfile: argResults.flag('enforce-lockfile'),
     );
 
-    var example = entrypoint.example;
+    final example = entrypoint.example;
     if ((argResults.flag('example')) && example != null) {
       await example.acquireDependencies(
         SolveType.get,
diff --git a/lib/src/command/global_activate.dart b/lib/src/command/global_activate.dart
index 47d290a..3a2f0f9 100644
--- a/lib/src/command/global_activate.dart
+++ b/lib/src/command/global_activate.dart
@@ -99,15 +99,15 @@
 
     String readArg([String error = '']) {
       if (args.isEmpty) usageException(error);
-      var arg = args.first;
+      final arg = args.first;
       args = args.skip(1);
       return arg;
     }
 
     void validateNoExtraArgs() {
       if (args.isEmpty) return;
-      var unexpected = args.map((arg) => '"$arg"');
-      var arguments = pluralize('argument', unexpected.length);
+      final unexpected = args.map((arg) => '"$arg"');
+      final arguments = pluralize('argument', unexpected.length);
       usageException('Unexpected $arguments ${toSentence(unexpected)}.');
     }
 
@@ -121,7 +121,7 @@
 
     switch (argResults.optionWithDefault('source')) {
       case 'git':
-        var repo = readArg('No Git repository given.');
+        final repo = readArg('No Git repository given.');
         validateNoExtraArgs();
         return globals.activateGit(
           repo,
@@ -132,7 +132,7 @@
         );
 
       case 'hosted':
-        var package = readArg('No package to activate given.');
+        final package = readArg('No package to activate given.');
 
         PackageRef ref;
         try {
@@ -168,7 +168,7 @@
         );
 
       case 'path':
-        var path = readArg('No package to activate given.');
+        final path = readArg('No package to activate given.');
         validateNoExtraArgs();
         return globals.activatePath(
           path,
diff --git a/lib/src/command/global_deactivate.dart b/lib/src/command/global_deactivate.dart
index 7e99e19..1e2ecc1 100644
--- a/lib/src/command/global_deactivate.dart
+++ b/lib/src/command/global_deactivate.dart
@@ -24,8 +24,8 @@
 
     // Don't allow extra arguments.
     if (argResults.rest.length > 1) {
-      var unexpected = argResults.rest.skip(1).map((arg) => '"$arg"');
-      var arguments = pluralize('argument', unexpected.length);
+      final unexpected = argResults.rest.skip(1).map((arg) => '"$arg"');
+      final arguments = pluralize('argument', unexpected.length);
       usageException('Unexpected $arguments ${toSentence(unexpected)}.');
     }
 
diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index c6e996f..31a209f 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -54,7 +54,7 @@
     String package;
     var executable = argResults.rest[0];
     if (executable.contains(':')) {
-      var parts = split1(executable, ':');
+      final parts = split1(executable, ':');
       package = parts[0];
       executable = parts[1];
     } else {
@@ -62,7 +62,7 @@
       package = executable;
     }
 
-    var args = argResults.rest.skip(1).toList();
+    final args = argResults.rest.skip(1).toList();
     if (p.split(executable).length > 1) {
       usageException('Cannot run an executable in a subdirectory of a global '
           'package.');
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index af4a042..d24d09e 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -145,16 +145,16 @@
         final parameters = parseJsonResponse(parametersResponse);
 
         /// 2. Upload package
-        var url = _expectField(parameters, 'url', parametersResponse);
+        final url = _expectField(parameters, 'url', parametersResponse);
         if (url is! String) invalidServerResponse(parametersResponse);
         cloudStorageUrl = Uri.parse(url);
         final uploadResponse =
             await retryForHttp('uploading package', () async {
           // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We
           // should report that error and exit.
-          var request = http.MultipartRequest('POST', cloudStorageUrl!);
+          final request = http.MultipartRequest('POST', cloudStorageUrl!);
 
-          var fields = _expectField(parameters, 'fields', parametersResponse);
+          final fields = _expectField(parameters, 'fields', parametersResponse);
           if (fields is! Map) invalidServerResponse(parametersResponse);
           fields.forEach((key, value) {
             if (value is! String) invalidServerResponse(parametersResponse);
@@ -173,7 +173,7 @@
         });
 
         /// 3. Finalize publish
-        var location = uploadResponse.headers['location'];
+        final location = uploadResponse.headers['location'];
         if (location == null) throw PubHttpResponseException(uploadResponse);
         final finalizeResponse =
             await retryForHttp('finalizing publish', () async {
@@ -201,7 +201,7 @@
       }
       dataError(msg + log.red('Authentication failed!'));
     } on PubHttpResponseException catch (error) {
-      var url = error.response.request!.url;
+      final url = error.response.request!.url;
       if (url == cloudStorageUrl) {
         handleGCSError(error.response);
         fail(log.red('Failed to upload the package.'));
@@ -250,7 +250,7 @@
         });
       }
     } on PubHttpResponseException catch (error) {
-      var url = error.response.request!.url;
+      final url = error.response.request!.url;
       if (Uri.parse(url.origin) == Uri.parse(host.origin)) {
         handleJsonError(error.response);
       } else {
@@ -303,11 +303,11 @@
       await entrypoint.acquireDependencies(SolveType.get);
     }
 
-    var files = entrypoint.workPackage.listFiles();
+    final files = entrypoint.workPackage.listFiles();
     log.fine('Archiving and publishing ${entrypoint.workPackage.name}.');
 
     // Show the package contents so the user can verify they look OK.
-    var package = entrypoint.workPackage;
+    final package = entrypoint.workPackage;
     final host = computeHost(package.pubspec);
     log.message(
       'Publishing ${package.name} ${package.version} to $host:\n'
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index 283cb7b..7ce16fe 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -127,16 +127,24 @@
           'The json report always includes transitive dependencies.');
     }
 
-    final rootPubspec = includeDependencyOverrides
-        ? entrypoint.workspaceRoot.pubspec
-        : stripDependencyOverrides(entrypoint.workspaceRoot.pubspec);
+    /// The workspace root with dependency overrides removed if requested.
+    final baseWorkspace = includeDependencyOverrides
+        ? entrypoint.workspaceRoot
+        : entrypoint.workspaceRoot.transformWorkspace(
+            (package) => stripDependencyOverrides(package.pubspec),
+          );
 
-    final upgradablePubspec = includeDevDependencies
-        ? rootPubspec
-        : stripDevDependencies(rootPubspec);
+    /// [baseWorkspace] with dev-dependencies removed if requested.
+    final upgradableWorkspace = includeDevDependencies
+        ? baseWorkspace
+        : baseWorkspace.transformWorkspace(
+            (package) => stripDevDependencies(package.pubspec),
+          );
 
-    final resolvablePubspec = await mode.resolvablePubspec(upgradablePubspec);
-
+    /// [upgradableWorkspace] with upper bounds removed.
+    final resolvableWorkspace = upgradableWorkspace.transformWorkspace(
+      (package) => mode.resolvablePubspec(package.pubspec),
+    );
     late List<PackageId> upgradablePackages;
     late List<PackageId> resolvablePackages;
     late bool hasUpgradableResolution;
@@ -146,11 +154,7 @@
       'Resolving',
       () async {
         final upgradablePackagesResult = await _tryResolve(
-          Package(
-            upgradablePubspec,
-            entrypoint.workspaceRoot.dir,
-            entrypoint.workspaceRoot.workspaceChildren,
-          ),
+          upgradableWorkspace,
           cache,
           lockFile: entrypoint.lockFile,
         );
@@ -158,11 +162,7 @@
         upgradablePackages = upgradablePackagesResult ?? [];
 
         final resolvablePackagesResult = await _tryResolve(
-          Package(
-            resolvablePubspec,
-            entrypoint.workspaceRoot.dir,
-            entrypoint.workspaceRoot.workspaceChildren,
-          ),
+          resolvableWorkspace,
           cache,
           lockFile: entrypoint.lockFile,
         );
@@ -206,8 +206,7 @@
       var latestIsOverridden = false;
       PackageId? latest;
       // If not overridden in current resolution we can use this
-      if (!entrypoint.workspaceRoot.pubspec.dependencyOverrides
-          .containsKey(name)) {
+      if (!hasOverride(entrypoint.workspaceRoot, name)) {
         latest ??= await cache.getLatest(
           current?.toRef(),
           version: current?.version,
@@ -216,23 +215,27 @@
       }
       // If present as a dependency or dev_dependency we use this
       latest ??= await cache.getLatest(
-        rootPubspec.dependencies[name]?.toRef(),
+        allDependencies(baseWorkspace)
+            .firstWhereOrNull((r) => r.name == name)
+            ?.toRef(),
         allowPrereleases: prereleases,
       );
       latest ??= await cache.getLatest(
-        rootPubspec.devDependencies[name]?.toRef(),
+        allDevDependencies(baseWorkspace)
+            .firstWhereOrNull((r) => r.name == name)
+            ?.toRef(),
         allowPrereleases: prereleases,
       );
       // If not overridden and present in either upgradable or resolvable we
       // use this reference to find the latest
-      if (!upgradablePubspec.dependencyOverrides.containsKey(name)) {
+      if (!hasOverride(upgradableWorkspace, name)) {
         latest ??= await cache.getLatest(
           upgradable?.toRef(),
           version: upgradable?.version,
           allowPrereleases: prereleases,
         );
       }
-      if (!resolvablePubspec.dependencyOverrides.containsKey(name)) {
+      if (!hasOverride(resolvableWorkspace, name)) {
         latest ??= await cache.getLatest(
           resolvable?.toRef(),
           version: resolvable?.version,
@@ -278,12 +281,12 @@
 
       final upgradableVersionDetails = await _describeVersion(
         upgradable,
-        upgradablePubspec.dependencyOverrides.containsKey(name),
+        hasOverride(upgradableWorkspace, name),
       );
 
       final resolvableVersionDetails = await _describeVersion(
         resolvable,
-        resolvablePubspec.dependencyOverrides.containsKey(name),
+        hasOverride(resolvableWorkspace, name),
       );
 
       final latestVersionDetails = await _describeVersion(
@@ -332,8 +335,9 @@
 
     final rows = <_PackageDetails>[];
 
-    final visited = <String>{
-      entrypoint.workspaceRoot.name,
+    final visited = {
+      ...entrypoint.workspaceRoot.transitiveWorkspace
+          .map((package) => package.name),
     };
     // Add all dependencies from the lockfile.
     for (final id in [
@@ -361,6 +365,7 @@
         includeDevDependencies: includeDevDependencies,
       );
     } else {
+      bool isNotFromSdk(PackageRange range) => range.source is! SdkSource;
       await _outputHuman(
         rows,
         mode,
@@ -368,13 +373,13 @@
         showAll: showAll,
         includeDevDependencies: includeDevDependencies,
         lockFileExists: fileExists(entrypoint.lockFilePath),
-        hasDirectDependencies: rootPubspec.dependencies.values.any(
+        hasDirectDependencies: allDependencies(baseWorkspace).any(
           // Test if it contains non-SDK dependencies
-          (c) => c.source is! SdkSource,
+          isNotFromSdk,
         ),
-        hasDevDependencies: rootPubspec.devDependencies.values.any(
+        hasDevDependencies: allDevDependencies(baseWorkspace).any(
           // Test if it contains non-SDK dependencies
-          (c) => c.source is! SdkSource,
+          isNotFromSdk,
         ),
         showTransitiveDependencies: showTransitiveDependencies,
         hasUpgradableResolution: hasUpgradableResolution,
@@ -420,23 +425,27 @@
   }
 
   /// Computes the closure of the graph of dependencies (not including
-  /// `dev_dependencies` from [root], given the package versions
-  /// in [resolution].
+  /// `dev_dependencies`) from all workspace packages in [workspaceRoot], given
+  /// the package versions in [resolution].
   ///
   /// The [resolution] is allowed to be a partial (or empty) resolution not
-  /// satisfying all the dependencies of [root].
+  /// satisfying all the dependencies of [workspaceRoot].
   Future<Set<String>> _nonDevDependencyClosure(
-    Package root,
+    Package workspaceRoot,
     Iterable<PackageId> resolution,
   ) async {
     final nameToId = {for (final id in resolution) id.name: id};
 
-    final nonDevDependencies = <String>{root.name};
-    final queue = [...root.dependencies.keys];
+    final result = <String>{
+      for (final p in workspaceRoot.transitiveWorkspace) p.name,
+    };
+    final queue = [
+      for (final p in workspaceRoot.transitiveWorkspace) ...p.dependencies.keys,
+    ];
 
     while (queue.isNotEmpty) {
       final name = queue.removeLast();
-      if (!nonDevDependencies.add(name)) {
+      if (!result.add(name)) {
         continue;
       }
 
@@ -448,7 +457,7 @@
       queue.addAll(pubspec.dependencies.keys);
     }
 
-    return nonDevDependencies;
+    return result;
   }
 }
 
@@ -622,7 +631,7 @@
     log.message(b.toString());
   }
 
-  var upgradable = rows.where(
+  final upgradable = rows.where(
     (row) {
       final current = row.current;
       final upgradable = row.upgradable;
@@ -636,7 +645,7 @@
     },
   ).length;
 
-  var notAtResolvable = rows.where(
+  final notAtResolvable = rows.where(
     (row) {
       final current = row.current;
       final upgradable = row.upgradable;
@@ -712,7 +721,7 @@
         .toList();
   }
 
-  var advisoriesToDisplay = <String, List<Advisory>>{};
+  final advisoriesToDisplay = <String, List<Advisory>>{};
   for (final package in rows) {
     advisoriesToDisplay[package.name] = advisoriesWithAffectedVersions(package);
   }
@@ -740,7 +749,7 @@
           'See https://dart.dev/go/package-retraction',
         );
       }
-      var displayedAdvisories = advisoriesToDisplay[package.name]!;
+      final displayedAdvisories = advisoriesToDisplay[package.name]!;
       if (displayedAdvisories.isNotEmpty) {
         final advisoriesText = displayedAdvisories.length > 1
             ? 'security advisories'
@@ -752,7 +761,7 @@
         log.message('\n');
 
         for (final advisory in displayedAdvisories) {
-          var displayedVersions = advisory.affectedVersions.intersection(
+          final displayedVersions = advisory.affectedVersions.intersection(
             [
               package.current,
               package.upgradable,
@@ -784,7 +793,7 @@
   String get upgradeConstrained;
   String get allSafe;
 
-  Future<Pubspec> resolvablePubspec(Pubspec pubspec);
+  Pubspec resolvablePubspec(Pubspec pubspec);
 }
 
 class _OutdatedMode implements _Mode {
@@ -873,8 +882,8 @@
   }
 
   @override
-  Future<Pubspec> resolvablePubspec(Pubspec? pubspec) async {
-    return stripVersionBounds(pubspec!);
+  Pubspec resolvablePubspec(Pubspec pubspec) {
+    return stripVersionBounds(pubspec);
   }
 }
 
@@ -972,9 +981,9 @@
   Entrypoint entrypoint,
   Set<String> nonDevTransitive,
 ) {
-  if (entrypoint.workspaceRoot.dependencies.containsKey(name)) {
+  if (hasDependency(entrypoint.workspaceRoot, name)) {
     return _DependencyKind.direct;
-  } else if (entrypoint.workspaceRoot.devDependencies.containsKey(name)) {
+  } else if (hasDevDependency(entrypoint.workspaceRoot, name)) {
     return _DependencyKind.dev;
   } else {
     if (nonDevTransitive.contains(name)) {
@@ -1054,7 +1063,7 @@
   Object? toJson() {
     if (_versionDetails == null) return null;
 
-    var jsonExplanation = _jsonExplanation;
+    final jsonExplanation = _jsonExplanation;
     return jsonExplanation == null
         ? _versionDetails.toJson()
         : (_versionDetails.toJson()..addEntries([jsonExplanation]));
@@ -1093,3 +1102,29 @@
 
   static String _noFormat(String x) => x;
 }
+
+/// Whether the package [name] is overridden anywhere in the workspace rooted at
+/// [workspaceRoot].
+bool hasOverride(Package workspaceRoot, String name) {
+  return workspaceRoot.allOverridesInWorkspace.containsKey(name);
+}
+
+/// Whether the package [name] is depended on directly anywhere in the workspace
+/// rooted at [workspaceRoot].
+bool hasDependency(Package workspaceRoot, String name) {
+  return workspaceRoot.transitiveWorkspace
+      .any((p) => p.dependencies.containsKey(name));
+}
+
+/// Whether the package [name] is dev-depended on directly anywhere in the workspace
+/// rooted at [workspaceRoot].
+bool hasDevDependency(Package workspaceRoot, String name) {
+  return workspaceRoot.transitiveWorkspace
+      .any((p) => p.devDependencies.containsKey(name));
+}
+
+Iterable<PackageRange> allDependencies(Package workspaceRoot) =>
+    workspaceRoot.transitiveWorkspace.expand((p) => p.dependencies.values);
+
+Iterable<PackageRange> allDevDependencies(Package workspaceRoot) =>
+    workspaceRoot.transitiveWorkspace.expand((p) => p.devDependencies.values);
diff --git a/lib/src/command/remove.dart b/lib/src/command/remove.dart
index 0b1c2c6..5c2f84c 100644
--- a/lib/src/command/remove.dart
+++ b/lib/src/command/remove.dart
@@ -96,7 +96,7 @@
           dryRun: isDryRun,
         );
 
-    var example = entrypoint.example;
+    final example = entrypoint.example;
     if (!isDryRun && argResults.flag('example') && example != null) {
       await example.acquireDependencies(
         SolveType.get,
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index c949551..a13597e 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -71,12 +71,12 @@
 
     var package = entrypoint.workspaceRoot.name;
     var executable = argResults.rest[0];
-    var args = argResults.rest.skip(1).toList();
+    final args = argResults.rest.skip(1).toList();
 
     // A command like "foo:bar" runs the "bar" script from the "foo" package.
     // If there is no colon prefix, default to the root package.
     if (executable.contains(':')) {
-      var components = split1(executable, ':');
+      final components = split1(executable, ':');
       package = components[0];
       executable = components[1];
 
@@ -97,7 +97,7 @@
 
     final vmArgs = vmArgsFromArgResults(argResults);
 
-    var exitCode = await runExecutable(
+    final exitCode = await runExecutable(
       entrypoint,
       Executable.adaptProgramName(package, executable),
       args,
diff --git a/lib/src/command/token_add.dart b/lib/src/command/token_add.dart
index a194cbc..c533633 100644
--- a/lib/src/command/token_add.dart
+++ b/lib/src/command/token_add.dart
@@ -60,8 +60,8 @@
     final rawHostedUrl = argResults.rest.first;
 
     try {
-      var hostedUrl = validateAndNormalizeHostedUrl(rawHostedUrl);
-      var isLocalhost =
+      final hostedUrl = validateAndNormalizeHostedUrl(rawHostedUrl);
+      final isLocalhost =
           ['localhost', '127.0.0.1', '::1'].contains(hostedUrl.host);
       if (!hostedUrl.isScheme('HTTPS') && !isLocalhost) {
         throw FormatException('url must be https://, '
diff --git a/lib/src/command/unpack.dart b/lib/src/command/unpack.dart
index 0190d22..6c96f67 100644
--- a/lib/src/command/unpack.dart
+++ b/lib/src/command/unpack.dart
@@ -31,27 +31,27 @@
 
   $topLevelProgram pub unpack foo
 
-Downloads and extracts the latest stable package:foo from pub.dev in a
-directory `foo-<version>`.
+Downloads and extracts the latest stable version of package:foo from pub.dev
+in a directory `foo-<version>`.
 
   $topLevelProgram pub unpack foo:1.2.3-pre --no-resolve
 
 Downloads and extracts package:foo version 1.2.3-pre in a directory
-`foo-1.2.3-pre` without running running implicit `pub get`.
+`foo-1.2.3-pre` without running implicit `pub get`.
 
   $topLevelProgram pub unpack foo --output=archives
 
-Downloads and extracts latest stable version of package:foo in a directory
+Downloads and extracts the latest stable version of package:foo in a directory
 `archives/foo-<version>`.
 
   $topLevelProgram pub unpack 'foo:{hosted:"https://my_repo.org"}'
 
-Downloads and extracts latest stable version of package:foo from my_repo.org
+Downloads and extracts the latest stable version of package:foo from my_repo.org
 in a directory `foo-<version>`.
 ''';
 
   @override
-  String get argumentsDescription => 'package-name[:constraint]';
+  String get argumentsDescription => 'package-name[:descriptor]';
 
   @override
   String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-unpack';
@@ -62,19 +62,19 @@
   UnpackCommand() {
     argParser.addFlag(
       'resolve',
-      help: 'Whether to do pub get in the downloaded folder',
+      help: 'Whether to run pub get in the downloaded folder.',
       defaultsTo: true,
       hide: log.verbosity != log.Verbosity.all,
     );
     argParser.addFlag(
       'force',
       abbr: 'f',
-      help: 'overwrites an existing folder if it exists',
+      help: 'Overwrite the target directory if it already exists.',
     );
     argParser.addOption(
       'output',
       abbr: 'o',
-      help: 'Download and extract the package in this dir',
+      help: 'Download and extract the package in the specified directory.',
       defaultsTo: '.',
     );
   }
@@ -87,15 +87,15 @@
   @override
   Future<void> runProtected() async {
     if (argResults.rest.isEmpty) {
-      usageException('Provide a package name');
+      usageException('Provide a package name.');
     }
     if (argResults.rest.length > 1) {
-      usageException('Please provide only a single package name');
+      usageException('Provide only a single package name.');
     }
     final arg = argResults.rest[0];
     final match = _argRegExp.firstMatch(arg);
     if (match == null) {
-      usageException('Use the form package:constraint to specify the package.');
+      usageException('Use the form package:descriptor to specify the package.');
     }
     final parseResult = _parseDescriptor(
       match.namedGroup('name')!,
@@ -124,7 +124,7 @@
         deleteEntry(destinationDir);
       } else {
         fail(
-          'Target directory `$destinationDir` already exists. Use --force to overwrite',
+          'Target directory `$destinationDir` already exists. Use --force to overwrite.',
         );
       }
     }
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 8d3c966..40131f1 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
-import 'package:yaml_edit/yaml_edit.dart';
 
 import '../command.dart';
 import '../command_runner.dart';
@@ -16,7 +15,6 @@
 import '../package_name.dart';
 import '../pubspec.dart';
 import '../pubspec_utils.dart';
-import '../sdk.dart';
 import '../solver.dart';
 import '../source/hosted.dart';
 import '../utils.dart';
@@ -137,25 +135,14 @@
     } else {
       await _runUpgrade(entrypoint);
       if (_tighten) {
-        final changes = tighten(
-          entrypoint,
-          entrypoint.lockFile.packages.values.toList(),
-        );
-        if (!_dryRun) {
-          for (final package in entrypoint.workspaceRoot.transitiveWorkspace) {
-            final changesForPackage = changes[package];
-            if (changesForPackage == null || changesForPackage.isEmpty) {
-              continue;
-            }
-            final newPubspecText =
-                _updatePubspecText(package, changesForPackage);
-
-            if (changes.isNotEmpty) {
-              writeTextFile(package.pubspecPath, newPubspecText);
-            }
-          }
+        if (argResults.flag('example') && entrypoint.example != null) {
+          log.warning(
+            'Running `upgrade --tighten` only in `${entrypoint.workspaceRoot.dir}`. Run `$topLevelProgram pub upgrade --tighten --directory example/` separately.',
+          );
         }
-        _outputChangeSummary(changes);
+        final changes =
+            entrypoint.tighten(packagesToUpgrade: _packagesToUpgrade);
+        entrypoint.applyChanges(changes, _dryRun);
       }
     }
     if (argResults.flag('example') && entrypoint.example != null) {
@@ -178,75 +165,6 @@
     _showOfflineWarning();
   }
 
-  /// Returns a list of changes to constraints in [pubspec] updated them to
-  ///  have their lower bound match the version in [packages].
-  ///
-  /// The return value is a mapping from the original package range to the updated.
-  ///
-  /// If packages to update where given in [_packagesToUpgrade], only those are
-  /// tightened. Otherwise all packages are tightened.
-  ///
-  /// If a dependency has already been updated in [existingChanges], the update
-  /// will apply on top of that change (eg. preserving the new upper bound).
-  Map<Package, Map<PackageRange, PackageRange>> tighten(
-    Entrypoint entrypoint,
-    List<PackageId> packages, {
-    Map<Package, Map<PackageRange, PackageRange>> existingChanges = const {},
-  }) {
-    final result = {...existingChanges};
-    if (argResults.flag('example') && entrypoint.example != null) {
-      log.warning(
-        'Running `upgrade --tighten` only in `${entrypoint.workspaceRoot.dir}`. Run `$topLevelProgram pub upgrade --tighten --directory example/` separately.',
-      );
-    }
-
-    final toTighten = <(Package, PackageRange)>[];
-
-    for (final package in entrypoint.workspaceRoot.transitiveWorkspace) {
-      if (_packagesToUpgrade.isEmpty) {
-        for (final range in [
-          ...package.dependencies.values,
-          ...package.devDependencies.values,
-        ]) {
-          toTighten.add((package, range));
-        }
-      } else {
-        for (final packageToUpgrade in _packagesToUpgrade) {
-          final range = package.dependencies[packageToUpgrade] ??
-              package.devDependencies[packageToUpgrade];
-          if (range != null) {
-            toTighten.add((package, range));
-          }
-        }
-      }
-    }
-
-    for (final (package, range) in toTighten) {
-      final changesForPackage = result[package] ??= {};
-      final constraint = (changesForPackage[range] ?? range).constraint;
-      final resolvedVersion =
-          packages.firstWhere((p) => p.name == range.name).version;
-      if (range.source is HostedSource && constraint.isAny) {
-        changesForPackage[range] = range
-            .toRef()
-            .withConstraint(VersionConstraint.compatibleWith(resolvedVersion));
-      } else if (constraint is VersionRange) {
-        final min = constraint.min;
-        if (min != null && min < resolvedVersion) {
-          changesForPackage[range] = range.toRef().withConstraint(
-                VersionRange(
-                  min: resolvedVersion,
-                  max: constraint.max,
-                  includeMin: true,
-                  includeMax: constraint.includeMax,
-                ).asCompatibleWithIfPossible(),
-              );
-        }
-      }
-    }
-    return result;
-  }
-
   /// Return names of packages to be upgraded, and throws [UsageException] if
   /// any package names not in the direct dependencies or dev_dependencies are given.
   ///
@@ -279,10 +197,6 @@
 
   Future<void> _runUpgradeMajorVersions() async {
     final toUpgrade = _directDependenciesToUpgrade();
-    final workspace = {
-      for (final package in entrypoint.workspaceRoot.transitiveWorkspace)
-        package.dir: package,
-    };
     // Solve [resolvablePubspec] in-memory and consolidate the resolved
     // versions of the packages into a map for quick searching.
     final resolvedPackages = <String, PackageId>{};
@@ -292,16 +206,8 @@
         return await resolveVersions(
           SolveType.upgrade,
           cache,
-          Package.load(
-            entrypoint.workspaceRoot.dir,
-            entrypoint.cache.sources,
-            withPubspecOverrides: true,
-            loadPubspec: (
-              path, {
-              expectedName,
-              required withPubspecOverrides,
-            }) =>
-                stripVersionBounds(workspace[path]!.pubspec),
+          entrypoint.workspaceRoot.transformWorkspace(
+            (package) => stripVersionBounds(package.pubspec),
           ),
         );
       },
@@ -328,7 +234,7 @@
         }
 
         // Skip [dep] if it has a dependency_override.
-        if (entrypoint.workspaceRoot.dependencyOverrides
+        if (entrypoint.workspaceRoot.pubspec.dependencyOverrides
             .containsKey(dep.name)) {
           dependencyOverriddenDeps.add(dep.name);
           continue;
@@ -354,20 +260,14 @@
       final solveResult = await resolveVersions(
         SolveType.upgrade,
         cache,
-        Package.load(
-          entrypoint.workspaceRoot.dir,
-          entrypoint.cache.sources,
-          loadPubspec: (path, {expectedName, required withPubspecOverrides}) {
-            final package = workspace[path]!;
-            final changesForPackage = changes[package] ?? {};
-            return applyChanges(package.pubspec, changesForPackage);
-          },
-        ),
+        entrypoint.workspaceRoot.transformWorkspace((package) {
+          return applyChanges(package.pubspec, changes[package] ?? {});
+        }),
       );
-      changes = tighten(
-        entrypoint,
-        solveResult.packages,
+      changes = entrypoint.tighten(
+        packagesToUpgrade: _packagesToUpgrade,
         existingChanges: changes,
+        packageVersions: solveResult.packages,
       );
     }
 
@@ -379,16 +279,8 @@
     final solveType =
         _packagesToUpgrade.isEmpty ? SolveType.upgrade : SolveType.get;
 
-    if (!_dryRun) {
-      for (final package in entrypoint.workspaceRoot.transitiveWorkspace) {
-        final changesForPackage = changes[package] ?? {};
-        if (changesForPackage.isNotEmpty) {
-          final newPubspecText = _updatePubspecText(package, changesForPackage);
-          writeTextFile(package.pubspecPath, newPubspecText);
-        }
-      }
-    }
-    await entrypoint.withUpdatedPubspecs({
+    entrypoint.applyChanges(changes, _dryRun);
+    await entrypoint.withUpdatedRootPubspecs({
       for (final MapEntry(key: package, value: changesForPackage)
           in changes.entries)
         package: applyChanges(package.pubspec, changesForPackage),
@@ -398,12 +290,10 @@
       precompile: !_dryRun && _precompile,
     );
 
-    _outputChangeSummary(changes);
-
     // If any of the packages to upgrade are dependency overrides, then we
     // show a warning.
     final toUpgradeOverrides = toUpgrade
-        .where(entrypoint.workspaceRoot.dependencyOverrides.containsKey);
+        .where(entrypoint.workspaceRoot.allOverridesInWorkspace.containsKey);
     if (toUpgradeOverrides.isNotEmpty) {
       log.warning(
         'Warning: dependency_overrides prevents upgrades for: '
@@ -434,65 +324,6 @@
     );
   }
 
-  /// Loads `pubspec.yaml` of [package] and applies [changes] to its
-  /// (dev)-dependencies.
-  ///
-  /// Returns the updated textual representation using yaml-edit to preserve
-  /// structure.
-  String _updatePubspecText(
-    Package package,
-    Map<PackageRange, PackageRange> changes,
-  ) {
-    ArgumentError.checkNotNull(changes, 'changes');
-    final yamlEditor = YamlEditor(readTextFile(package.pubspecPath));
-    final deps = package.dependencies.keys;
-
-    for (final change in changes.values) {
-      final section =
-          deps.contains(change.name) ? 'dependencies' : 'dev_dependencies';
-      yamlEditor.update(
-        [section, change.name],
-        pubspecDescription(change, cache, package),
-      );
-    }
-    return yamlEditor.toString();
-  }
-
-  /// Outputs a summary of changes made to `pubspec.yaml`.
-  void _outputChangeSummary(
-    Map<Package, Map<PackageRange, PackageRange>> changes,
-  ) {
-    if (entrypoint.workspaceRoot.workspaceChildren.isEmpty) {
-      final changesToWorkspaceRoot = changes[entrypoint.workspaceRoot] ?? {};
-      if (changesToWorkspaceRoot.isEmpty) {
-        final wouldBe = _dryRun ? 'would be made to' : 'to';
-        log.message('\nNo changes $wouldBe pubspec.yaml!');
-      } else {
-        final changed = _dryRun ? 'Would change' : 'Changed';
-        log.message('\n$changed ${changesToWorkspaceRoot.length} '
-            '${pluralize('constraint', changesToWorkspaceRoot.length)} in pubspec.yaml:');
-        changesToWorkspaceRoot.forEach((from, to) {
-          log.message('  ${from.name}: ${from.constraint} -> ${to.constraint}');
-        });
-      }
-    } else {
-      if (changes.isEmpty) {
-        final wouldBe = _dryRun ? 'would be made to' : 'to';
-        log.message('\nNo changes $wouldBe any pubspec.yaml!');
-      }
-      for (final package in entrypoint.workspaceRoot.transitiveWorkspace) {
-        final changesToPackage = changes[package] ?? {};
-        if (changesToPackage.isEmpty) continue;
-        final changed = _dryRun ? 'Would change' : 'Changed';
-        log.message('\n$changed ${changesToPackage.length} '
-            '${pluralize('constraint', changesToPackage.length)} in ${package.pubspecPath}:');
-        changesToPackage.forEach((from, to) {
-          log.message('  ${from.name}: ${from.constraint} -> ${to.constraint}');
-        });
-      }
-    }
-  }
-
   void _showOfflineWarning() {
     if (isOffline) {
       log.warning('Warning: Upgrading when offline may not update you to the '
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index ee10f74..03c724b 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -193,11 +193,11 @@
     if (!runningFromDartRepo) return;
     if (!git.isInstalled) return;
 
-    var deps = readTextFile(p.join(dartRepoRoot, 'DEPS'));
-    var pubRevRegExp = RegExp(r'^ +"pub_rev": +"@([^"]+)"', multiLine: true);
-    var match = pubRevRegExp.firstMatch(deps);
+    final deps = readTextFile(p.join(dartRepoRoot, 'DEPS'));
+    final pubRevRegExp = RegExp(r'^ +"pub_rev": +"@([^"]+)"', multiLine: true);
+    final match = pubRevRegExp.firstMatch(deps);
     if (match == null) return;
-    var depsRev = match[1];
+    final depsRev = match[1];
 
     String actualRev;
     final pubRoot = p.dirname(p.dirname(p.fromUri(Platform.script)));
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index 85e3095..56e69f5f 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -54,7 +54,7 @@
   /// Throws [AnalyzerErrorGroup] is the file has parsing errors.
   CompilationUnit parse(String path) {
     path = p.normalize(p.absolute(path));
-    var parseResult = _session.getParsedUnit(path);
+    final parseResult = _session.getParsedUnit(path);
     if (parseResult is ParsedUnitResult) {
       if (parseResult.errors.isNotEmpty) {
         throw AnalyzerErrorGroup(parseResult.errors);
@@ -69,8 +69,8 @@
   ///
   /// Throws [AnalyzerErrorGroup] is the file has parsing errors.
   List<UriBasedDirective> parseImportsAndExports(String path) {
-    var unit = parse(path);
-    var uriDirectives = <UriBasedDirective>[];
+    final unit = parse(path);
+    final uriDirectives = <UriBasedDirective>[];
     for (var directive in unit.directives) {
       if (directive is UriBasedDirective) {
         uriDirectives.add(directive);
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index a9315fb..96c9fe0 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -7,12 +7,12 @@
 import 'dart:io';
 import 'dart:math';
 
-import 'package:collection/collection.dart';
 import 'package:path/path.dart' as p;
 import 'package:pool/pool.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:source_span/source_span.dart';
 import 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
 
 import 'command_runner.dart';
 import 'dart.dart' as dart;
@@ -27,12 +27,14 @@
 import 'package_graph.dart';
 import 'package_name.dart';
 import 'pubspec.dart';
+import 'pubspec_utils.dart';
 import 'sdk.dart';
 import 'sdk/flutter.dart';
 import 'solver.dart';
 import 'solver/report.dart';
 import 'solver/solve_suggestions.dart';
 import 'source/cached.dart';
+import 'source/hosted.dart';
 import 'source/root.dart';
 import 'source/unknown.dart';
 import 'system_cache.dart';
@@ -105,7 +107,6 @@
       if (pubspec.resolution == Resolution.none) {
         root = Package.load(
           dir,
-          cache.sources,
           loadPubspec: (
             path, {
             expectedName,
@@ -123,7 +124,7 @@
         );
         for (final package in root.transitiveWorkspace) {
           if (identical(pubspecsMet.entries.first.value, package.pubspec)) {
-            validateWorkspaceGraph(root);
+            validateWorkspace(root);
             return (root: root, work: package);
           }
         }
@@ -277,12 +278,12 @@
     // return the package-graph, such it by construction will always made from an
     // up-to-date package-config.
     await ensureUpToDate(workspaceRoot.dir, cache: cache);
-    var packages = {
+    final packages = {
       for (var packageEntry in packageConfig.nonInjectedPackages)
         packageEntry.name: Package.load(
           packageEntry.resolvedRootDir(packageConfigPath),
-          cache.sources,
           expectedName: packageEntry.name,
+          loadPubspec: Pubspec.loadRootWithSources(cache.sources),
         ),
     };
     packages[workspaceRoot.name] = workspaceRoot;
@@ -336,28 +337,10 @@
   }
 
   /// Creates an entrypoint at the same location, but with each pubspec in
-  /// [updatedPubspec] replacing the with one for the corresponding package.
-  Entrypoint withUpdatedPubspecs(Map<Package, Pubspec> updatedPubspecs) {
-    final existingPubspecs = <String, Pubspec>{};
-    // First extract all pubspecs from the workspace.
-    for (final package in workspaceRoot.transitiveWorkspace) {
-      existingPubspecs[package.dir] =
-          updatedPubspecs[package] ?? package.pubspec;
-    }
-    final newWorkspaceRoot = Package.load(
-      workspaceRoot.dir,
-      cache.sources,
-      loadPubspec: (
-        dir, {
-        expectedName,
-        required withPubspecOverrides,
-      }) =>
-          existingPubspecs[dir] ??
-          Pubspec.load(
-            dir,
-            cache.sources,
-            containingDescription: RootDescription(dir),
-          ),
+  /// [updatedPubspecs] replacing the with one for the corresponding package.
+  Entrypoint withUpdatedRootPubspecs(Map<Package, Pubspec> updatedPubspecs) {
+    final newWorkspaceRoot = workspaceRoot.transformWorkspace(
+      (package) => updatedPubspecs[package] ?? package.pubspec,
     );
     final newWorkPackage = newWorkspaceRoot.transitiveWorkspace
         .firstWhere((package) => package.dir == workPackage.dir);
@@ -375,7 +358,7 @@
   /// Creates an entrypoint at the same location, that will use [pubspec] for
   /// resolution of the [workPackage].
   Entrypoint withWorkPubspec(Pubspec pubspec) {
-    return withUpdatedPubspecs({workPackage: pubspec});
+    return withUpdatedRootPubspecs({workPackage: pubspec});
   }
 
   /// Creates an entrypoint given package and lockfile objects.
@@ -407,7 +390,12 @@
 
   Entrypoint? _example;
 
-  /// Writes the .dart_tool/package_config.json file
+  /// Writes the .dart_tool/package_config.json file and workspace references to
+  /// it.
+  ///
+  /// If the workspace is non-trivial: For each package in the workspace write:
+  /// `.dart_tool/pub/workspace_ref.json` with a pointer to the workspace root
+  /// package dir.
   Future<void> writePackageConfigFile() async {
     ensureDir(p.dirname(packageConfigPath));
     writeTextFile(
@@ -418,6 +406,21 @@
             .pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint,
       ),
     );
+    if (workspaceRoot.workspaceChildren.isNotEmpty) {
+      for (final package in workspaceRoot.transitiveWorkspace) {
+        final workspaceRefDir = p.join(package.dir, '.dart_tool', 'pub');
+        final workspaceRefPath = p.join(workspaceRefDir, 'workspace_ref.json');
+        ensureDir(workspaceRefDir);
+        final relativeRootPath =
+            p.relative(workspaceRoot.dir, from: workspaceRefDir);
+        writeTextFile(
+          workspaceRefPath,
+          '${JsonEncoder.withIndent('  ').convert({
+                'workspaceRoot': relativeRootPath,
+              })}\n',
+        );
+      }
+    }
   }
 
   /// Returns the contents of the `.dart_tool/package_config` file generated
@@ -516,7 +519,9 @@
   }) async {
     workspaceRoot; // This will throw early if pubspec.yaml could not be found.
     summaryOnly = summaryOnly || _summaryOnlyEnvironment;
-    final suffix = workspaceRoot.dir == '.' ? '' : ' in `${workspaceRoot.dir}`';
+    final suffix = workspaceRoot.dir == '.'
+        ? ''
+        : ' in `${workspaceRoot.presentationDir}`';
 
     if (enforceLockfile && !fileExists(lockFilePath)) {
       throw ApplicationException('''
@@ -562,6 +567,7 @@
       type,
       workspaceRoot.dir,
       workspaceRoot.pubspec,
+      workspaceRoot.allOverridesInWorkspace,
       lockFile,
       newLockFile,
       result.availableVersions,
@@ -754,7 +760,7 @@
       bool isDependencyUpToDate(PackageRange dep) {
         if (dep.name == root.name) return true;
 
-        var locked = lockFile.packages[dep.name];
+        final locked = lockFile.packages[dep.name];
         return locked != null && dep.allows(locked);
       }
 
@@ -789,8 +795,6 @@
         return false;
       }
 
-      var overrides = MapKeySet(root.dependencyOverrides);
-
       // Check that uncached dependencies' pubspecs are also still satisfied,
       // since they're mutable and may have changed since the last get.
       for (var id in lockFile.packages.values) {
@@ -800,7 +804,8 @@
         try {
           if (cache.load(id).dependencies.values.every(
                 (dep) =>
-                    overrides.contains(dep.name) || isDependencyUpToDate(dep),
+                    root.allOverridesInWorkspace.containsKey(dep.name) ||
+                    isDependencyUpToDate(dep),
               )) {
             continue;
           }
@@ -1086,7 +1091,10 @@
       }
       var touchedLockFile = false;
       late final lockFile = _loadLockFile(lockFilePath, cache);
-      late final root = Package.load(dir, cache.sources);
+      late final root = Package.load(
+        dir,
+        loadPubspec: Pubspec.loadRootWithSources(cache.sources),
+      );
 
       if (!lockfileNewerThanPubspecs) {
         if (isLockFileUpToDate(lockFile, root)) {
@@ -1130,7 +1138,7 @@
           );
         }
         return entrypoint.packageConfig;
-      case PackageConfig packageConfig:
+      case final PackageConfig packageConfig:
         log.fine('Package Config up to date.');
         return packageConfig;
     }
@@ -1238,4 +1246,139 @@
       }
     }
   }
+
+  /// Returns a list of changes to constraints of workspace pubspecs updated to
+  /// have their lower bound match the version in [packageVersions] (or
+  /// `this.lockFile`).
+  ///
+  /// The return value for each workspace package is a mapping from the original
+  /// package range to the updated.
+  ///
+  /// If packages to update where given in [packagesToUpgrade], only those are
+  /// tightened. Otherwise all packages are tightened.
+  ///
+  /// If a dependency has already been updated in [existingChanges], the update
+  /// will apply on top of that change (eg. preserving the new upper bound).
+  Map<Package, Map<PackageRange, PackageRange>> tighten({
+    List<String> packagesToUpgrade = const [],
+    Map<Package, Map<PackageRange, PackageRange>> existingChanges = const {},
+    List<PackageId>? packageVersions,
+  }) {
+    final result = {...existingChanges};
+
+    final toTighten = <(Package, PackageRange)>[];
+
+    for (final package in workspaceRoot.transitiveWorkspace) {
+      if (packagesToUpgrade.isEmpty) {
+        for (final range in [
+          ...package.dependencies.values,
+          ...package.devDependencies.values,
+        ]) {
+          toTighten.add((package, range));
+        }
+      } else {
+        for (final packageToUpgrade in packagesToUpgrade) {
+          final range = package.dependencies[packageToUpgrade] ??
+              package.devDependencies[packageToUpgrade];
+          if (range != null) {
+            toTighten.add((package, range));
+          }
+        }
+      }
+    }
+
+    for (final (package, range) in toTighten) {
+      final changesForPackage = result[package] ??= {};
+      final constraint = (changesForPackage[range] ?? range).constraint;
+      final resolvedVersion =
+          (packageVersions?.firstWhere((p) => p.name == range.name) ??
+                  lockFile.packages[range.name])!
+              .version;
+      if (range.source is HostedSource && constraint.isAny) {
+        changesForPackage[range] = range
+            .toRef()
+            .withConstraint(VersionConstraint.compatibleWith(resolvedVersion));
+      } else if (constraint is VersionRange) {
+        final min = constraint.min;
+        if (min != null && min < resolvedVersion) {
+          changesForPackage[range] = range.toRef().withConstraint(
+                VersionRange(
+                  min: resolvedVersion,
+                  max: constraint.max,
+                  includeMin: true,
+                  includeMax: constraint.includeMax,
+                ).asCompatibleWithIfPossible(),
+              );
+        }
+      }
+    }
+    return result;
+  }
+
+  /// Unless [dryRun], loads `pubspec.yaml` of each [package] in [changeSet] and applies the
+  /// changes to its (dev)-dependencies using yaml_edit to preserve textual structure.
+  ///
+  /// Outputs a summary of changes done or would have been done if not [dryRun].
+  void applyChanges(ChangeSet changeSet, bool dryRun) {
+    if (!dryRun) {
+      for (final package in workspaceRoot.transitiveWorkspace) {
+        final changesForPackage = changeSet[package];
+        if (changesForPackage == null || changesForPackage.isEmpty) {
+          continue;
+        }
+        final yamlEditor = YamlEditor(readTextFile(package.pubspecPath));
+        final deps = package.dependencies.keys;
+
+        for (final change in changesForPackage.values) {
+          final section =
+              deps.contains(change.name) ? 'dependencies' : 'dev_dependencies';
+          yamlEditor.update(
+            [section, change.name],
+            pubspecDescription(change, cache, package),
+          );
+        }
+        writeTextFile(package.pubspecPath, yamlEditor.toString());
+      }
+    }
+    _outputChangeSummary(changeSet, dryRun: dryRun);
+  }
+
+  /// Outputs a summary of [changeSet].
+  void _outputChangeSummary(
+    ChangeSet changeSet, {
+    required bool dryRun,
+  }) {
+    if (workspaceRoot.workspaceChildren.isEmpty) {
+      final changesToWorkspaceRoot = changeSet[workspaceRoot] ?? {};
+      if (changesToWorkspaceRoot.isEmpty) {
+        final wouldBe = dryRun ? 'would be made to' : 'to';
+        log.message('\nNo changes $wouldBe pubspec.yaml!');
+      } else {
+        final changed = dryRun ? 'Would change' : 'Changed';
+        log.message('\n$changed ${changesToWorkspaceRoot.length} '
+            '${pluralize('constraint', changesToWorkspaceRoot.length)} in pubspec.yaml:');
+        changesToWorkspaceRoot.forEach((from, to) {
+          log.message('  ${from.name}: ${from.constraint} -> ${to.constraint}');
+        });
+      }
+    } else {
+      if (changeSet.isEmpty) {
+        final wouldBe = dryRun ? 'would be made to' : 'to';
+        log.message('\nNo changes $wouldBe any pubspec.yaml!');
+      }
+      for (final package in workspaceRoot.transitiveWorkspace) {
+        final changesToPackage = changeSet[package] ?? {};
+        if (changesToPackage.isEmpty) continue;
+        final changed = dryRun ? 'Would change' : 'Changed';
+        log.message('\n$changed ${changesToPackage.length} '
+            '${pluralize('constraint', changesToPackage.length)} in ${package.pubspecPath}:');
+        changesToPackage.forEach((from, to) {
+          log.message('  ${from.name}: ${from.constraint} -> ${to.constraint}');
+        });
+      }
+    }
+  }
 }
+
+/// For each package in a workspace, a set of changes to dependencies.
+typedef ChangeSet = Map<Package, Map<PackageRange, PackageRange>>;
diff --git a/lib/src/error_group.dart b/lib/src/error_group.dart
index 27a065f..88ef85d 100644
--- a/lib/src/error_group.dart
+++ b/lib/src/error_group.dart
@@ -71,7 +71,7 @@
           'ErrorGroup.');
     }
 
-    var wrapped = _ErrorGroupFuture(this, future);
+    final wrapped = _ErrorGroupFuture(this, future);
     _futures.add(wrapped);
     return wrapped;
   }
@@ -94,7 +94,7 @@
           'ErrorGroup.');
     }
 
-    var wrapped = _ErrorGroupStream(this, stream);
+    final wrapped = _ErrorGroupStream(this, stream);
     _streams.add(wrapped);
     return wrapped;
   }
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index feaf10c..1fb7180 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -67,7 +67,7 @@
     }
   }
 
-  var snapshotPath = entrypoint.pathOfSnapshot(executable);
+  final snapshotPath = entrypoint.pathOfSnapshot(executable);
 
   // Don't compile snapshots for mutable packages, since their code may
   // change later on.
@@ -75,7 +75,7 @@
   // Also we don't snapshot if we have non-default arguments to the VM, as
   // these would be inconsistent if another set of settings are given in a
   // later invocation.
-  var useSnapshot = vmArgs.isEmpty;
+  final useSnapshot = vmArgs.isEmpty;
 
   var executablePath = executable.resolve(
     entrypoint.packageConfig,
@@ -167,7 +167,7 @@
   // We use Isolate.spawnUri when there are no extra vm-options.
   // That provides better signal handling, and possibly faster startup.
   if ((!alwaysUseSubprocess) && vmArgs.isEmpty) {
-    var argList = args.toList();
+    final argList = args.toList();
     return await isolate.runUri(
       p.toUri(path),
       argList,
diff --git a/lib/src/git.dart b/lib/src/git.dart
index c091a4f..1087034 100644
--- a/lib/src/git.dart
+++ b/lib/src/git.dart
@@ -146,7 +146,7 @@
 bool _tryGitCommand(String command) {
   // If "git --version" prints something familiar, git is working.
   try {
-    var result = runProcessSync(command, ['--version']);
+    final result = runProcessSync(command, ['--version']);
     final output = result.stdout;
 
     // Some users may have configured commands such as autorun, which may
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 9ce849c..3b9c0c6 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -93,7 +93,7 @@
     String? path,
     String? ref,
   }) async {
-    var name = await cache.git.getPackageNameFromRepo(
+    final name = await cache.git.getPackageNameFromRepo(
       repo,
       ref,
       path,
@@ -178,16 +178,16 @@
     List<String>? executables, {
     required bool overwriteBinStubs,
   }) async {
-    var entrypoint = Entrypoint(path, cache);
+    final entrypoint = Entrypoint(path, cache);
 
     // Get the package's dependencies.
     await entrypoint.acquireDependencies(SolveType.get);
-    var name = entrypoint.workspaceRoot.name;
+    final name = entrypoint.workspaceRoot.name;
     _describeActive(name, cache);
 
     // Write a lockfile that points to the local package.
-    var fullPath = canonicalize(entrypoint.workspaceRoot.dir);
-    var id = cache.path.idFor(
+    final fullPath = canonicalize(entrypoint.workspaceRoot.dir);
+    final id = cache.path.idFor(
       name,
       entrypoint.workspaceRoot.version,
       fullPath,
@@ -226,7 +226,7 @@
 
     final tempDir = cache.createTempDir();
     // Create a dummy package with just [dep] so we can do resolution on it.
-    var root = packageForConstraint(dep, tempDir);
+    final root = packageForConstraint(dep, tempDir);
 
     // Resolve it and download its dependencies.
     SolveResult result;
@@ -267,6 +267,7 @@
           SolveType.get,
           null,
           root.pubspec,
+          root.pubspec.dependencyOverrides,
           originalLockFile ?? LockFile.empty(),
           lockFile,
           result.availableVersions,
@@ -320,7 +321,7 @@
       // Couldn't read the lock file. It probably doesn't exist.
       return null;
     }
-    var id = lockFile.packages[name]!;
+    final id = lockFile.packages[name]!;
     final description = id.description.description;
 
     if (description is GitDescription) {
@@ -340,7 +341,7 @@
   ///
   /// Returns `false` if no package with [name] was currently active.
   bool deactivate(String name) {
-    var dir = p.join(_directory, name);
+    final dir = p.join(_directory, name);
     if (!dirExists(_directory)) {
       return false;
     }
@@ -357,8 +358,8 @@
 
     _deleteBinStubs(name);
 
-    var lockFile = LockFile.load(_getLockFilePath(name), cache.sources);
-    var id = lockFile.packages[name]!;
+    final lockFile = LockFile.load(_getLockFilePath(name), cache.sources);
+    final id = lockFile.packages[name]!;
     log.message('Deactivated package ${_formatPackage(id)}.');
 
     deleteEntry(dir);
@@ -370,7 +371,7 @@
   ///
   /// Returns an [Entrypoint] loaded with the active package if found.
   Future<Entrypoint> find(String name) async {
-    var lockFilePath = _getLockFilePath(name);
+    final lockFilePath = _getLockFilePath(name);
     late final LockFile lockFile;
     try {
       lockFile = LockFile.load(lockFilePath, cache.sources);
@@ -401,7 +402,7 @@
 
     // Check that the SDK constraints the lockFile says we have are honored.
     lockFile.sdkConstraints.forEach((sdkName, constraint) {
-      var sdk = sdks[sdkName];
+      final sdk = sdks[sdkName];
       if (sdk == null) {
         dataError('${log.bold(name)} as globally activated requires '
             'unknown SDK "$name".');
@@ -476,10 +477,10 @@
   /// path to a single lockfile or a new-style path to a directory containing a
   /// lockfile.
   PackageId _loadPackageId(String path) {
-    var name = p.basenameWithoutExtension(path);
+    final name = p.basenameWithoutExtension(path);
     if (!fileExists(path)) path = p.join(path, 'pubspec.lock');
 
-    var id =
+    final id =
         LockFile.load(p.join(_directory, path), cache.sources).packages[name];
 
     if (id == null) {
@@ -494,10 +495,10 @@
   String _formatPackage(PackageId id) {
     final description = id.description.description;
     if (description is GitDescription) {
-      var url = GitDescription.prettyUri(description.url);
+      final url = GitDescription.prettyUri(description.url);
       return '${log.bold(id.name)} ${id.version} from Git repository "$url"';
     } else if (description is PathDescription) {
-      var path = description.path;
+      final path = description.path;
       return '${log.bold(id.name)} ${id.version} at path "$path"';
     } else {
       return '${log.bold(id.name)} ${id.version}';
@@ -510,17 +511,17 @@
   /// were successfully re-activated; the second indicates which failed.
   Future<(List<String> successes, List<String> failures)>
       repairActivatedPackages() async {
-    var executables = <String, List<String>>{};
+    final executables = <String, List<String>>{};
     if (dirExists(_binStubDir)) {
       for (var entry in listDir(_binStubDir)) {
         try {
-          var binstub = readTextFile(entry);
-          var package = _binStubProperty(binstub, 'Package');
+          final binstub = readTextFile(entry);
+          final package = _binStubProperty(binstub, 'Package');
           if (package == null) {
             throw ApplicationException("No 'Package' property.");
           }
 
-          var executable = _binStubProperty(binstub, 'Executable');
+          final executable = _binStubProperty(binstub, 'Executable');
           if (executable == null) {
             throw ApplicationException("No 'Executable' property.");
           }
@@ -539,8 +540,8 @@
       }
     }
 
-    var successes = <String>[];
-    var failures = <String>[];
+    final successes = <String>[];
+    final failures = <String>[];
     if (dirExists(_directory)) {
       for (var entry in listDir(_directory)) {
         PackageId? id;
@@ -548,7 +549,7 @@
           id = _loadPackageId(entry);
           log.message('Reactivating ${log.bold(id.name)} ${id.version}...');
 
-          var entrypoint = await find(id.name);
+          final entrypoint = await find(id.name);
           final packageExecutables = executables.remove(id.name) ?? [];
 
           if (entrypoint.isCached) {
@@ -584,7 +585,7 @@
     }
 
     if (executables.isNotEmpty) {
-      var message = StringBuffer('Binstubs exist for non-activated '
+      final message = StringBuffer('Binstubs exist for non-activated '
           'packages:\n');
       executables.forEach((package, executableNames) {
         for (var executable in executableNames) {
@@ -607,9 +608,9 @@
   void _refreshBinStubs(Entrypoint entrypoint, exec.Executable executable) {
     if (!dirExists(_binStubDir)) return;
     for (var file in listDir(_binStubDir, includeDirs: false)) {
-      var contents = readTextFile(file);
-      var binStubPackage = _binStubProperty(contents, 'Package');
-      var binStubScript = _binStubProperty(contents, 'Script');
+      final contents = readTextFile(file);
+      final binStubPackage = _binStubProperty(contents, 'Package');
+      final binStubScript = _binStubProperty(contents, 'Script');
       if (binStubPackage == null || binStubScript == null) {
         log.fine('Could not parse binstub $file:\n$contents');
         continue;
@@ -618,12 +619,12 @@
           binStubScript ==
               p.basenameWithoutExtension(executable.relativePath)) {
         log.fine('Replacing old binstub $file');
-        deleteEntry(file);
         _createBinStub(
           activatedPackage(entrypoint),
           p.basenameWithoutExtension(file),
           binStubScript,
           overwrite: true,
+          isRefreshingBinstub: true,
           snapshot:
               executable.pathOfGlobalSnapshot(entrypoint.workspaceRoot.dir),
         );
@@ -667,19 +668,20 @@
 
     ensureDir(_binStubDir);
 
-    var installed = <String>[];
-    var collided = <String, String>{};
-    var allExecutables = ordered(package.pubspec.executables.keys);
+    final installed = <String>[];
+    final collided = <String, String>{};
+    final allExecutables = ordered(package.pubspec.executables.keys);
     for (var executable in allExecutables) {
       if (executables != null && !executables.contains(executable)) continue;
 
-      var script = package.pubspec.executables[executable]!;
+      final script = package.pubspec.executables[executable]!;
 
-      var previousPackage = _createBinStub(
+      final previousPackage = _createBinStub(
         package,
         executable,
         script,
         overwrite: overwriteBinStubs,
+        isRefreshingBinstub: false,
         snapshot: entrypoint.pathOfSnapshot(
           exec.Executable.adaptProgramName(package.name, script),
         ),
@@ -694,7 +696,7 @@
     }
 
     if (installed.isNotEmpty) {
-      var names = namedSequence('executable', installed.map(log.bold));
+      final names = namedSequence('executable', installed.map(log.bold));
       log.message('Installed $names.');
     }
 
@@ -718,7 +720,7 @@
 
     // Show errors for any unknown executables.
     if (executables != null) {
-      var unknown = ordered(
+      final unknown = ordered(
         executables
             .where((exe) => !package.pubspec.executables.keys.contains(exe)),
       );
@@ -730,10 +732,10 @@
     // 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.executablePaths;
+    final binFiles = package.executablePaths;
     for (var executable in installed) {
-      var script = package.pubspec.executables[executable];
-      var scriptPath = p.join('bin', '$script.dart');
+      final script = package.pubspec.executables[executable];
+      final scriptPath = p.join('bin', '$script.dart');
       if (!binFiles.contains(scriptPath)) {
         log.warning('Warning: Executable "$executable" runs "$scriptPath", '
             'which was not found in ${log.bold(package.name)}.');
@@ -762,15 +764,14 @@
     String script, {
     required bool overwrite,
     required String snapshot,
+    required bool isRefreshingBinstub,
   }) {
     var binStubPath = p.join(_binStubDir, executable);
     if (Platform.isWindows) binStubPath += '.bat';
 
-    // See if the binstub already exists. If so, it's for another package
-    // since we already deleted all of this package's binstubs.
     String? previousPackage;
-    if (fileExists(binStubPath)) {
-      var contents = readTextFile(binStubPath);
+    if (!isRefreshingBinstub && fileExists(binStubPath)) {
+      final contents = readTextFile(binStubPath);
       previousPackage = _binStubProperty(contents, 'Package');
       if (previousPackage == null) {
         log.fine('Could not parse binstub $binStubPath:\n$contents');
@@ -845,7 +846,7 @@
     // it into place afterwards to avoid races.
     final tempDir = cache.createTempDir();
     try {
-      final tmpPath = p.join(tempDir, binStubPath);
+      final tmpPath = p.join(tempDir, p.basename(binStubPath));
 
       // Write this as the system encoding since the system is going to
       // execute it and it might contain non-ASCII characters in the
@@ -854,7 +855,7 @@
 
       if (Platform.isLinux || Platform.isMacOS) {
         // Make it executable.
-        var result = Process.runSync('chmod', ['+x', tmpPath]);
+        final 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 '
@@ -874,8 +875,8 @@
     if (!dirExists(_binStubDir)) return;
 
     for (var file in listDir(_binStubDir, includeDirs: false)) {
-      var contents = readTextFile(file);
-      var binStubPackage = _binStubProperty(contents, 'Package');
+      final contents = readTextFile(file);
+      final binStubPackage = _binStubProperty(contents, 'Package');
       if (binStubPackage == null) {
         log.fine('Could not parse binstub $file:\n$contents');
         continue;
@@ -897,7 +898,7 @@
     if (Platform.isWindows) {
       // See if the shell can find one of the binstubs.
       // "\q" means return exit code 0 if found or 1 if not.
-      var result = runProcessSync('where', [r'\q', '$installed.bat']);
+      final result = runProcessSync('where', [r'\q', '$installed.bat']);
       if (result.exitCode == 0) return;
 
       log.warning("${log.yellow('Warning:')} Pub installs executables into "
@@ -910,7 +911,7 @@
       //
       // The "command" builtin is more reliable than the "which" executable. See
       // http://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then
-      var result =
+      final result =
           runProcessSync('command', ['-v', installed], runInShell: true);
       if (result.exitCode == 0) return;
 
@@ -938,8 +939,8 @@
   /// Returns the value of the property named [name] in the bin stub script
   /// [source].
   String? _binStubProperty(String source, String name) {
-    var pattern = RegExp(RegExp.escape(name) + r': ([a-zA-Z0-9_-]+)');
-    var match = pattern.firstMatch(source);
+    final pattern = RegExp(RegExp.escape(name) + r': ([a-zA-Z0-9_-]+)');
+    final match = pattern.firstMatch(source);
     return match == null ? null : match[1];
   }
 }
diff --git a/lib/src/http.dart b/lib/src/http.dart
index 74efc18..f281434 100644
--- a/lib/src/http.dart
+++ b/lib/src/http.dart
@@ -89,14 +89,14 @@
 
   /// Logs the fact that [request] was sent, and information about it.
   void _logRequest(http.BaseRequest request) {
-    var requestLog = StringBuffer();
+    final requestLog = StringBuffer();
     requestLog.writeln('HTTP ${request.method} ${request.url}');
     request.headers
         .forEach((name, value) => requestLog.writeln(_logField(name, value)));
 
     if (request.method == 'POST') {
-      var contentTypeString = request.headers[HttpHeaders.contentTypeHeader];
-      var contentType = ContentType.parse(contentTypeString ?? '');
+      final contentTypeString = request.headers[HttpHeaders.contentTypeHeader];
+      final contentType = ContentType.parse(contentTypeString ?? '');
       if (request is http.MultipartRequest) {
         requestLog.writeln();
         requestLog.writeln('Body fields:');
@@ -127,9 +127,9 @@
     // TODO(nweiz): Fork the response stream and log the response body. Be
     // careful not to log OAuth2 private data, though.
 
-    var responseLog = StringBuffer();
-    var request = response.request!;
-    var stopwatch = _requestStopwatches.remove(request)!..stop();
+    final responseLog = StringBuffer();
+    final request = response.request!;
+    final stopwatch = _requestStopwatches.remove(request)!..stop();
     responseLog.writeln('HTTP response ${response.statusCode} '
         '${response.reasonPhrase} for ${request.method} ${request.url}');
     responseLog.writeln('took ${stopwatch.elapsed}');
@@ -197,12 +197,12 @@
     headers['X-Pub-Command'] = PubCommand.command;
     headers['X-Pub-Session-ID'] = _sessionId;
 
-    var environment = Platform.environment['PUB_ENVIRONMENT'];
+    final environment = Platform.environment['PUB_ENVIRONMENT'];
     if (environment != null) {
       headers['X-Pub-Environment'] = environment;
     }
 
-    var type = Zone.current[#_dependencyType];
+    final type = Zone.current[#_dependencyType];
     if (type != null && type != DependencyType.none) {
       headers['X-Pub-Reason'] = type.toString();
     }
@@ -222,7 +222,9 @@
       parsed['success']['message'] is! String) {
     invalidServerResponse(response);
   }
-  log.message(log.green(parsed['success']['message'] as String));
+  log.message(
+    'Message from server: ${log.green(sanitizeForTerminal(parsed['success']['message'] as String))}',
+  );
 }
 
 /// Handles an unsuccessful JSON-formatted response from pub.dev.
@@ -236,14 +238,16 @@
     // See https://github.com/dart-lang/pub/pull/3590#discussion_r1012978108
     fail(log.red('Invalid server response'));
   }
-  var errorMap = parseJsonResponse(response);
+  final errorMap = parseJsonResponse(response);
   final error = errorMap['error'];
   if (error is! Map ||
       !error.containsKey('message') ||
       error['message'] is! String) {
     invalidServerResponse(response);
   }
-  fail(log.red(error['message'] as String));
+  fail(
+    'Message from server: ${log.red(sanitizeForTerminal(error['message'] as String))}',
+  );
 }
 
 /// Handles an unsuccessful XML-formatted response from google cloud storage.
@@ -269,13 +273,13 @@
       // `Details` are not specified in the doc above, but have been observed in actual responses.
       final details = getTagText('Details');
       if (code != null) {
-        log.error('Server error code: $code');
+        log.error('Server error code: ${sanitizeForTerminal(code)}');
       }
       if (message != null) {
-        log.error('Server message: $message');
+        log.error('Server message: ${sanitizeForTerminal(message)}');
       }
       if (details != null) {
-        log.error('Server details: $details');
+        log.error('Server details: ${sanitizeForTerminal(details)}');
       }
     }
   }
diff --git a/lib/src/io.dart b/lib/src/io.dart
index fabafa3..ffaa5fd 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -108,7 +108,7 @@
 /// filesystem; nonexistent or unreadable path entries are treated as normal
 /// directories.
 String canonicalize(String pathString) {
-  var seen = <String>{};
+  final seen = <String>{};
   var components =
       Queue<String>.from(p.split(p.normalize(p.absolute(pathString))));
 
@@ -120,15 +120,16 @@
   // resolved in turn.
   while (components.isNotEmpty) {
     seen.add(p.join(newPath, p.joinAll(components)));
-    var resolvedPath = _resolveLink(p.join(newPath, components.removeFirst()));
-    var relative = p.relative(resolvedPath, from: newPath);
+    final resolvedPath =
+        _resolveLink(p.join(newPath, components.removeFirst()));
+    final relative = p.relative(resolvedPath, from: newPath);
 
     // If the resolved path of the component relative to `newPath` is just ".",
     // that means component was a symlink pointing to its parent directory. We
     // can safely ignore such components.
     if (relative == '.') continue;
 
-    var relativeComponents = Queue<String>.from(p.split(relative));
+    final relativeComponents = Queue<String>.from(p.split(relative));
 
     // If the resolved path is absolute relative to `newPath`, that means it's
     // on a different drive. We need to canonicalize the entire target of that
@@ -165,7 +166,7 @@
     // If we've already tried to canonicalize the new path, we've encountered a
     // symlink loop. Avoid going infinite by treating the recursive symlink as
     // the canonical path.
-    var newSubPath = p.join(newPath, p.joinAll(relativeComponents));
+    final newSubPath = p.join(newPath, p.joinAll(relativeComponents));
     if (seen.contains(newSubPath)) {
       newPath = newSubPath;
       continue;
@@ -189,7 +190,7 @@
 ///
 /// This accepts paths to non-links or broken links, and returns them as-is.
 String _resolveLink(String link) {
-  var seen = <String>{};
+  final seen = <String>{};
   while (linkExists(link) && seen.add(link)) {
     link = p.normalize(p.join(p.dirname(link), Link(link).targetSync()));
   }
@@ -207,7 +208,7 @@
 /// Reads the contents of the binary file [file].
 Uint8List readBinaryFile(String file) {
   log.io('Reading binary file $file.');
-  var contents = File(file).readAsBytesSync();
+  final contents = File(file).readAsBytesSync();
   log.io('Read ${contents.length} bytes from $file.');
   return contents;
 }
@@ -215,7 +216,7 @@
 /// Reads the contents of the binary file [file] as a [Stream].
 Stream<List<int>> readBinaryFileAsStream(String file) {
   log.io('Reading binary file $file.');
-  var contents = File(file).openRead();
+  final contents = File(file).openRead();
   return contents;
 }
 
@@ -308,7 +309,7 @@
 ///
 /// Returns the path of the created directory.
 String createTempDir(String base, String prefix) {
-  var tempDir = Directory(base).createTempSync(prefix);
+  final tempDir = Directory(base).createTempSync(prefix);
   log.io('Created temp directory ${tempDir.path}');
   return tempDir.path;
 }
@@ -318,7 +319,7 @@
 ///
 /// Returns the path of the created directory.
 Future<String> _createSystemTempDir() async {
-  var tempDir = await Directory.systemTemp.createTemp('pub_');
+  final tempDir = await Directory.systemTemp.createTemp('pub_');
   log.io('Created temp directory ${tempDir.path}');
   return tempDir.resolveSymbolicLinksSync();
 }
@@ -352,7 +353,7 @@
   bool includeDirs = true,
   Iterable<String> allowed = const <String>[],
 }) {
-  var allowListFilter = createFileFilter(allowed);
+  final allowListFilter = createFileFilter(allowed);
 
   // This is used in some performance-sensitive paths and can list many, many
   // files. As such, it leans more heavily towards optimization as opposed to
@@ -448,7 +449,7 @@
       operation();
       break;
     } on FileSystemException catch (error) {
-      var reason = getErrorReason(error);
+      final reason = getErrorReason(error);
       if (reason == null) rethrow;
 
       if (i < maxRetries - 1) {
@@ -579,7 +580,7 @@
       // If the directory where we're creating the symlink was itself reached
       // by traversing a symlink, we want the relative path to be relative to
       // it's actual location, not the one we went through to get to it.
-      var symlinkDir = canonicalize(p.dirname(symlink));
+      final symlinkDir = canonicalize(p.dirname(symlink));
       target = p.normalize(p.relative(target, from: symlinkDir));
     }
   }
@@ -658,7 +659,7 @@
 
   // Get the URL of the repo root in a way that works when either both running
   // as a test or as a pub executable.
-  var url = Platform.script
+  final url = Platform.script
       .replace(path: Platform.script.path.replaceAll(_dartRepoRegExp, ''));
   return p.fromUri(url);
 })();
@@ -736,8 +737,8 @@
 (EventSink<T> consumerSink, Future done) _consumerToSink<T>(
   StreamConsumer<T> consumer,
 ) {
-  var controller = StreamController<T>(sync: true);
-  var done = controller.stream.pipe(consumer);
+  final controller = StreamController<T>(sync: true);
+  final done = controller.stream.pipe(consumer);
   return (controller.sink, done);
 }
 
@@ -780,7 +781,7 @@
       );
     }
 
-    var pubResult = PubProcessResult(
+    final pubResult = PubProcessResult(
       result.stdout as String,
       result.stderr as String,
       result.exitCode,
@@ -824,7 +825,7 @@
       );
     }
 
-    var process = PubProcess(ioProcess);
+    final process = PubProcess(ioProcess);
     unawaited(process.exitCode.whenComplete(resource.release));
     return process;
   });
@@ -857,7 +858,7 @@
   } on IOException catch (e) {
     throw RunProcessException('Pub failed to run subprocess `$executable`: $e');
   }
-  var pubResult = PubProcessResult(
+  final pubResult = PubProcessResult(
     result.stdout as String,
     result.stderr as String,
     result.exitCode,
@@ -928,18 +929,18 @@
 
   /// Creates a new [PubProcess] wrapping [process].
   PubProcess(Process process) : _process = process {
-    var errorGroup = ErrorGroup();
+    final errorGroup = ErrorGroup();
 
-    var (consumerSink, done) = _consumerToSink(process.stdin);
+    final (consumerSink, done) = _consumerToSink(process.stdin);
     _stdin = consumerSink;
     _stdinClosed = errorGroup.registerFuture(done);
 
     _stdout = ByteStream(errorGroup.registerStream(process.stdout));
     _stderr = ByteStream(errorGroup.registerStream(process.stderr));
 
-    var exitCodeCompleter = Completer<int>();
+    final exitCodeCompleter = Completer<int>();
     _exitCode = errorGroup.registerFuture(exitCodeCompleter.future);
-    _process.exitCode.then((code) => exitCodeCompleter.complete(code));
+    _process.exitCode.then(exitCodeCompleter.complete);
   }
 
   /// Sends [signal] to the underlying process.
@@ -982,7 +983,7 @@
 /// Returns a future that completes to the value that the future returned from
 /// [fn] completes to.
 Future<T> withTempDir<T>(FutureOr<T> Function(String path) fn) async {
-  var tempDir = await _createSystemTempDir();
+  final tempDir = await _createSystemTempDir();
   try {
     return await fn(tempDir);
   } finally {
@@ -995,7 +996,7 @@
 /// If [host] is "localhost", this will automatically listen on both the IPv4
 /// and IPv6 loopback addresses.
 Future<HttpServer> bindServer(String host, int port) async {
-  var server = host == 'localhost'
+  final server = host == 'localhost'
       ? await HttpMultiServer.loopback(port)
       : await HttpServer.bind(host, port);
   server.autoCompress = true;
@@ -1136,7 +1137,7 @@
   List<String> contents, {
   required String baseDir,
 }) {
-  var buffer = StringBuffer();
+  final buffer = StringBuffer();
   buffer.write('Creating .tar.gz stream containing:\n');
   contents.forEach(buffer.writeln);
   log.fine(buffer.toString());
@@ -1197,7 +1198,7 @@
 
   // TODO(rnystrom): Remove this and change to returning one string.
   static List<String> _toLines(String output) {
-    var lines = splitLines(output);
+    final lines = splitLines(output);
     if (lines.isNotEmpty && lines.last == '') lines.removeLast();
     return lines;
   }
diff --git a/lib/src/isolate.dart b/lib/src/isolate.dart
index 0958a6c..49217f4 100644
--- a/lib/src/isolate.dart
+++ b/lib/src/isolate.dart
@@ -25,8 +25,8 @@
   bool automaticPackageResolution = false,
   Uri? packageConfig,
 }) async {
-  var errorPort = ReceivePort();
-  var exitPort = ReceivePort();
+  final errorPort = ReceivePort();
+  final exitPort = ReceivePort();
 
   await Isolate.spawnUri(
     url,
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart
index 6717263..d2a0187 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -170,15 +170,15 @@
           // Parse the source.
           final sourceName = _getStringEntry(spec, 'source');
 
-          var descriptionNode =
+          final descriptionNode =
               _getEntry<YamlNode>(spec, 'description', 'description');
 
-          dynamic description = descriptionNode is YamlScalar
+          final dynamic description = descriptionNode is YamlScalar
               ? descriptionNode.value
               : descriptionNode;
 
           // Let the source parse the description.
-          var source = sources(sourceName);
+          final source = sources(sourceName);
           PackageId id;
           try {
             id = source.parseId(
@@ -337,7 +337,7 @@
   LockFile removePackage(String name) {
     if (!this.packages.containsKey(name)) return this;
 
-    var packages = Map<String, PackageId>.from(this.packages);
+    final packages = Map<String, PackageId>.from(this.packages);
     packages.remove(name);
     return LockFile._(
       packages,
@@ -355,7 +355,7 @@
   /// serialized as absolute.
   String serialize(String? packageDir, SystemCache cache) {
     // Convert the dependencies to a simple object.
-    var packageMap = <String, Object?>{};
+    final packageMap = <String, Object?>{};
     for (final id in packages.values) {
       packageMap[id.name] = {
         'version': id.version.toString(),
@@ -366,7 +366,7 @@
       };
     }
 
-    var data = {
+    final data = {
       'sdks': mapMap<String, SdkConstraint, String, String>(
         sdkConstraints,
         value: (_, constraint) => constraint.effectiveConstraint.toString(),
diff --git a/lib/src/log.dart b/lib/src/log.dart
index a7fdd2b..94db39f 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -233,7 +233,7 @@
 /// Logs [message] at [level].
 void write(Level level, String message) {
   message = message.toString();
-  var lines = splitLines(message);
+  final lines = splitLines(message);
 
   // Discard a trailing newline. This is useful since StringBuffers often end
   // up with an extra newline at the end from using [writeln].
@@ -241,9 +241,9 @@
     lines.removeLast();
   }
 
-  var entry = _Entry(level, lines);
+  final entry = _Entry(level, lines);
 
-  var logFn = verbosity._loggers[level];
+  final logFn = verbosity._loggers[level];
   if (logFn != null) logFn(entry);
 
   _transcript.add(entry);
@@ -263,7 +263,7 @@
 /// Logs the results of running [executable].
 void processResult(String executable, PubProcessResult result) {
   // Log it all as one message so that it shows up as a single unit in the logs.
-  var buffer = StringBuffer();
+  final buffer = StringBuffer();
   buffer.writeln('Finished $executable. Exit code ${result.exitCode}.');
 
   void dumpOutput(String name, List<String> output) {
@@ -294,7 +294,7 @@
 void exception(Object exception, [StackTrace? trace]) {
   if (exception is SilentException) return;
 
-  var chain = trace == null ? Chain.current() : Chain.forTrace(trace);
+  final chain = trace == null ? Chain.current() : Chain.forTrace(trace);
 
   // This is basically the top-level exception handler so that we don't
   // spew a stack trace on our users.
@@ -445,7 +445,7 @@
 Future<T> progress<T>(String message, Future<T> Function() callback) {
   _stopProgress();
 
-  var progress = Progress(message);
+  final progress = Progress(message);
   _animatedProgress = progress;
   return callback().whenComplete(progress.stop);
 }
@@ -459,11 +459,9 @@
   if (condition) {
     _stopProgress();
 
-    var progress = Progress(message);
+    final progress = Progress(message);
     _animatedProgress = progress;
-    return callback().whenComplete(() {
-      progress.stopAndClear();
-    });
+    return callback().whenComplete(progress.stopAndClear);
   }
   return callback();
 }
@@ -614,7 +612,7 @@
   ///
   /// Always prints to stdout.
   void error(Object error, [StackTrace? stackTrace]) {
-    var errorJson = {'error': error.toString()};
+    final errorJson = {'error': error.toString()};
 
     if (stackTrace == null && error is Error) stackTrace = error.stackTrace;
     if (stackTrace != null) {
diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart
index 1688646..9a116ef 100644
--- a/lib/src/oauth2.dart
+++ b/lib/src/oauth2.dart
@@ -62,7 +62,7 @@
 /// This can be controlled externally by setting the `_PUB_TEST_TOKEN_ENDPOINT`
 /// environment variable.
 Uri get tokenEndpoint {
-  var tokenEndpoint = Platform.environment['_PUB_TEST_TOKEN_ENDPOINT'];
+  final tokenEndpoint = Platform.environment['_PUB_TEST_TOKEN_ENDPOINT'];
   if (tokenEndpoint != null) {
     return Uri.parse(tokenEndpoint);
   } else {
@@ -87,7 +87,7 @@
 /// Delete the cached credentials, if they exist.
 void _clearCredentials() {
   _credentials = null;
-  var credentialsFile = _credentialsFile();
+  final credentialsFile = _credentialsFile();
   if (credentialsFile != null && entryExists(credentialsFile)) {
     deleteEntry(credentialsFile);
   }
@@ -95,7 +95,7 @@
 
 /// Try to delete the cached credentials.
 void logout() {
-  var credentialsFile = _credentialsFile();
+  final credentialsFile = _credentialsFile();
   if (credentialsFile != null && entryExists(credentialsFile)) {
     log.message('Logging out of pub.dev.');
     log.message('Deleting $credentialsFile');
@@ -149,10 +149,10 @@
 /// If saved credentials are available, those are used; otherwise, the user is
 /// prompted to authorize the pub client.
 Future<_Client> _getClient() async {
-  var credentials = loadCredentials();
+  final credentials = loadCredentials();
   if (credentials == null) return await _authorize();
 
-  var client = _Client(
+  final client = _Client(
     credentials,
     identifier: _identifier,
     secret: _secret,
@@ -175,10 +175,10 @@
   try {
     if (_credentials != null) return _credentials;
 
-    var path = _credentialsFile();
+    final path = _credentialsFile();
     if (path == null || !fileExists(path)) return null;
 
-    var credentials = Credentials.fromJson(readTextFile(path));
+    final credentials = Credentials.fromJson(readTextFile(path));
     if (credentials.isExpired && !credentials.canRefresh) {
       log.error("Pub's authorization to upload packages has expired and "
           "can't be automatically refreshed.");
@@ -200,7 +200,7 @@
 void _saveCredentials(Credentials credentials) {
   log.fine('Saving OAuth2 credentials.');
   _credentials = credentials;
-  var credentialsPath = _credentialsFile();
+  final credentialsPath = _credentialsFile();
   if (credentialsPath != null) {
     ensureDir(p.dirname(credentialsPath));
     writeTextFile(credentialsPath, credentials.toJson(), dontLogContents: true);
@@ -219,7 +219,7 @@
 ///
 /// Returns a Future that completes to a fully-authorized [_Client].
 Future<_Client> _authorize() async {
-  var grant = _AuthorizationCodeGrant(
+  final grant = _AuthorizationCodeGrant(
     _identifier, _authorizationEndpoint, tokenEndpoint,
     secret: _secret,
     // Google's OAuth2 API doesn't support basic auth.
@@ -230,15 +230,15 @@
   // Spin up a one-shot HTTP server to receive the authorization code from the
   // Google OAuth2 server via redirect. This server will close itself as soon as
   // the code is received.
-  var completer = Completer<_Client>();
-  var server = await bindServer('localhost', 0);
+  final completer = Completer<_Client>();
+  final server = await bindServer('localhost', 0);
   shelf_io.serveRequests(server, (request) {
     if (request.url.path.isNotEmpty) {
       return shelf.Response.notFound('Invalid URI.');
     }
 
     log.message('Authorization received, processing...');
-    var queryString = request.url.query;
+    final queryString = request.url.query;
     // Closing the server here is safe, since it will wait until the response
     // is sent to actually shut down.
     server.close();
@@ -248,7 +248,7 @@
     return shelf.Response.found('https://pub.dev/authorized');
   });
 
-  var authUrl = grant.getAuthorizationUrl(
+  final authUrl = grant.getAuthorizationUrl(
     Uri.parse('http://localhost:${server.port}'),
     scopes: _scopes,
   );
@@ -259,7 +259,7 @@
       'Then click "Allow access".\n\n'
       'Waiting for your authorization...');
 
-  var client = await completer.future;
+  final client = await completer.future;
   log.message('Successfully authorized.\n');
   return client;
 }
@@ -457,15 +457,15 @@
     }
     _state = _State.awaitingResponse;
 
-    var scopeList = scopes?.toList() ?? <String>[];
-    var codeChallenge = base64Url
+    final scopeList = scopes?.toList() ?? <String>[];
+    final codeChallenge = base64Url
         .encode(sha256.convert(ascii.encode(_codeVerifier)).bytes)
         .replaceAll('=', '');
 
     _redirectEndpoint = redirect;
     _scopes = scopeList;
     _stateString = state;
-    var parameters = {
+    final parameters = {
       'response_type': 'code',
       'client_id': identifier,
       'redirect_uri': redirect.toString(),
@@ -520,9 +520,9 @@
     }
 
     if (parameters.containsKey('error')) {
-      var description = parameters['error_description'];
-      var uriString = parameters['error_uri'];
-      var uri = uriString == null ? null : Uri.parse(uriString);
+      final description = parameters['error_description'];
+      final uriString = parameters['error_uri'];
+      final uri = uriString == null ? null : Uri.parse(uriString);
       throw _AuthorizationException(parameters['error']!, description, uri);
     } else if (!parameters.containsKey('code')) {
       throw FormatException('Invalid OAuth response for '
@@ -562,18 +562,18 @@
   /// This works just like [handleAuthorizationCode], except it doesn't validate
   /// the state beforehand.
   Future<_Client> _handleAuthorizationCode(String? authorizationCode) async {
-    var startTime = DateTime.now();
+    final startTime = DateTime.now();
 
-    var headers = <String, String>{};
+    final headers = <String, String>{};
 
-    var body = {
+    final body = {
       'grant_type': 'authorization_code',
       'code': authorizationCode,
       'redirect_uri': _redirectEndpoint.toString(),
       'code_verifier': _codeVerifier,
     };
 
-    var secret = this.secret;
+    final secret = this.secret;
     if (_basicAuth && secret != null) {
       headers['Authorization'] = _basicAuthHeader(identifier, secret);
     } else {
@@ -583,10 +583,10 @@
       if (secret != null) body['client_secret'] = secret;
     }
 
-    var response =
+    final response =
         await _httpClient!.post(tokenEndpoint, headers: headers, body: body);
 
-    var credentials = _handleAccessTokenResponse(
+    final credentials = _handleAccessTokenResponse(
       response,
       tokenEndpoint,
       startTime,
@@ -772,7 +772,7 @@
     }
 
     request.headers['authorization'] = 'Bearer ${credentials.accessToken}';
-    var response = await _httpClient!.send(request);
+    final response = await _httpClient!.send(request);
 
     if (response.statusCode != 401) return response;
     if (!response.headers.containsKey('www-authenticate')) return response;
@@ -786,11 +786,11 @@
       return response;
     }
 
-    var challenge = challenges
+    final challenge = challenges
         .firstWhereOrNull((challenge) => challenge.scheme == 'bearer');
     if (challenge == null) return response;
 
-    var params = challenge.parameters;
+    final params = challenge.parameters;
     if (!params.containsKey('error')) return response;
 
     throw _AuthorizationException(
@@ -919,7 +919,7 @@
   /// called. However, since the client's expiration date is kept a few seconds
   /// earlier than the server's, there should be enough leeway to rely on this.
   bool get isExpired {
-    var expiration = this.expiration;
+    final expiration = this.expiration;
     return expiration != null && DateTime.now().isAfter(expiration);
   }
 
@@ -994,20 +994,20 @@
     );
 
     for (var stringField in ['refreshToken', 'idToken', 'tokenEndpoint']) {
-      var value = parsed[stringField];
+      final value = parsed[stringField];
       validate(
         value == null || value is String,
         'field "$stringField" was not a string, was "$value"',
       );
     }
 
-    var scopes = parsed['scopes'];
+    final scopes = parsed['scopes'];
     validate(
       scopes == null || scopes is List,
       'field "scopes" was not a list, was "$scopes"',
     );
 
-    var tokenEndpoint = parsed['tokenEndpoint'];
+    final tokenEndpoint = parsed['tokenEndpoint'];
     Uri? tokenEndpointUri;
     if (tokenEndpoint != null) {
       tokenEndpointUri = Uri.parse(tokenEndpoint as String);
@@ -1075,8 +1075,8 @@
       throw ArgumentError('secret may not be passed without identifier.');
     }
 
-    var startTime = DateTime.now();
-    var tokenEndpoint = this.tokenEndpoint;
+    final startTime = DateTime.now();
+    final tokenEndpoint = this.tokenEndpoint;
     if (refreshToken == null) {
       throw StateError("Can't refresh credentials without a refresh "
           'token.');
@@ -1085,9 +1085,9 @@
           'endpoint.');
     }
 
-    var headers = <String, String>{};
+    final headers = <String, String>{};
 
-    var body = {'grant_type': 'refresh_token', 'refresh_token': refreshToken};
+    final body = {'grant_type': 'refresh_token', 'refresh_token': refreshToken};
     if (scopes.isNotEmpty) body['scope'] = scopes.join(_delimiter);
 
     if (basicAuth && secret != null) {
@@ -1097,9 +1097,9 @@
       if (secret != null) body['client_secret'] = secret;
     }
 
-    var response =
+    final response =
         await httpClient.post(tokenEndpoint, headers: headers, body: body);
-    var credentials = _handleAccessTokenResponse(
+    final credentials = _handleAccessTokenResponse(
       response,
       tokenEndpoint,
       startTime,
@@ -1173,12 +1173,12 @@
       _handleErrorResponse(response, tokenEndpoint, getParameters);
     }
 
-    var contentTypeString = response.headers['content-type'];
+    final contentTypeString = response.headers['content-type'];
     if (contentTypeString == null) {
       throw const FormatException('Missing Content-Type string.');
     }
 
-    var parameters =
+    final parameters =
         getParameters(MediaType.parse(contentTypeString), response.body);
 
     for (var requiredParameter in ['access_token', 'token_type']) {
@@ -1219,7 +1219,7 @@
     }
 
     for (var name in ['refresh_token', 'id_token', 'scope']) {
-      var value = parameters[name];
+      final value = parameters[name];
       if (value != null && value is! String) {
         throw FormatException(
           'parameter "$name" was not a string, was "$value"',
@@ -1227,10 +1227,10 @@
       }
     }
 
-    var scope = parameters['scope'] as String?;
+    final scope = parameters['scope'] as String?;
     if (scope != null) scopes = scope.split(delimiter);
 
-    var expiration = expiresIn == null
+    final expiration = expiresIn == null
         ? null
         : startTime.add(Duration(seconds: expiresIn as int) - _expirationGrace);
 
@@ -1260,7 +1260,7 @@
   // off-spec.
   if (response.statusCode != 400 && response.statusCode != 401) {
     var reason = '';
-    var reasonPhrase = response.reasonPhrase;
+    final reasonPhrase = response.reasonPhrase;
     if (reasonPhrase != null && reasonPhrase.isNotEmpty) {
       reason = ' $reasonPhrase';
     }
@@ -1268,11 +1268,11 @@
         'with status ${response.statusCode}$reason.\n\n${response.body}');
   }
 
-  var contentTypeString = response.headers['content-type'];
-  var contentType =
+  final contentTypeString = response.headers['content-type'];
+  final contentType =
       contentTypeString == null ? null : MediaType.parse(contentTypeString);
 
-  var parameters = getParameters(contentType, response.body);
+  final parameters = getParameters(contentType, response.body);
 
   if (!parameters.containsKey('error')) {
     throw const FormatException('did not contain required parameter "error"');
@@ -1282,16 +1282,16 @@
   }
 
   for (var name in ['error_description', 'error_uri']) {
-    var value = parameters[name];
+    final value = parameters[name];
 
     if (value != null && value is! String) {
       throw FormatException('parameter "$name" was not a string, was "$value"');
     }
   }
 
-  var uriString = parameters['error_uri'] as String?;
-  var uri = uriString == null ? null : Uri.parse(uriString);
-  var description = parameters['error_description'] as String?;
+  final uriString = parameters['error_uri'] as String?;
+  final uri = uriString == null ? null : Uri.parse(uriString);
+  final description = parameters['error_description'] as String?;
   throw _AuthorizationException(
     parameters['error'] as String,
     description,
@@ -1320,7 +1320,7 @@
     );
   }
 
-  var untypedParameters = jsonDecode(body);
+  final untypedParameters = jsonDecode(body);
   if (untypedParameters is Map<String, dynamic>) {
     return untypedParameters;
   }
@@ -1335,6 +1335,6 @@
     );
 
 String _basicAuthHeader(String identifier, String secret) {
-  var userPass = '${Uri.encodeFull(identifier)}:${Uri.encodeFull(secret)}';
+  final userPass = '${Uri.encodeFull(identifier)}:${Uri.encodeFull(secret)}';
   return 'Basic ${base64Encode(ascii.encode(userPass))}';
 }
diff --git a/lib/src/package.dart b/lib/src/package.dart
index e1c5208..1daf3a6 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -14,8 +14,6 @@
 import 'log.dart' as log;
 import 'package_name.dart';
 import 'pubspec.dart';
-import 'source/root.dart';
-import 'system_cache.dart';
 import 'utils.dart';
 
 /// A Package is a [Pubspec] and a directory where it belongs that can be used
@@ -27,7 +25,7 @@
   /// take a package's description or root directory into account, so multiple
   /// distinct packages may order the same.
   static int orderByNameAndVersion(Package a, Package b) {
-    var name = a.name.compareTo(b.name);
+    final name = a.name.compareTo(b.name);
     if (name != 0) return name;
 
     return a.version.compareTo(b.version);
@@ -36,6 +34,13 @@
   /// The path to the directory containing the package.
   final String dir;
 
+  /// A version of [dir] adapted for presenting in the terminal.
+  ///
+  /// If [dir] is just a parent directory like ../.. it gets replaced with
+  /// the absolute dir.
+  late String presentationDir =
+      p.isWithin(dir, '.') ? p.normalize(p.absolute(dir)) : dir;
+
   /// The name of the package.
   String get name => pubspec.name;
 
@@ -71,26 +76,32 @@
     }
   }
 
+  /// A collection of all overrides in the workspace.
+  ///
+  /// Should only be called on the workspace root.
+  ///
+  /// We only allow each package to be overridden once, so it is ok to collapse
+  /// the overrides into a single map.
+  late final Map<String, PackageRange> allOverridesInWorkspace = {
+    for (final package in transitiveWorkspace)
+      ...package.pubspec.dependencyOverrides,
+  };
+
   /// The immediate dependencies this package specifies in its pubspec.
   Map<String, PackageRange> get dependencies => pubspec.dependencies;
 
   /// The immediate dev dependencies this package specifies in its pubspec.
   Map<String, PackageRange> get devDependencies => pubspec.devDependencies;
 
-  /// The dependency overrides this package specifies in its pubspec or pubspec
-  /// overrides.
-  Map<String, PackageRange> get dependencyOverrides =>
-      pubspec.dependencyOverrides;
-
   /// All immediate dependencies this package specifies.
   ///
-  /// This includes regular, dev dependencies, and overrides.
+  /// This includes regular, dev dependencies, and overrides from this package.
   Map<String, PackageRange> get immediateDependencies {
     // Make sure to add overrides last so they replace normal dependencies.
     return {}
       ..addAll(dependencies)
       ..addAll(devDependencies)
-      ..addAll(dependencyOverrides);
+      ..addAll(pubspec.dependencyOverrides);
   }
 
   /// Returns a list of paths to all Dart executables in this package's bin
@@ -117,7 +128,7 @@
       // If the entire package directory is ignored, don't consider it part of a
       // git repo. `git check-ignore` will return a status code of 0 for
       // ignored, 1 for not ignored, and 128 for not a Git repo.
-      var result = runProcessSync(
+      final result = runProcessSync(
         git.command!,
         ['check-ignore', '--quiet', '.'],
         workingDir: dir,
@@ -144,22 +155,15 @@
   /// used to override a pubspec in memory for trying out an alternative
   /// resolution.
   factory Package.load(
-    String dir,
-    SourceRegistry sources, {
+    String dir, {
     bool withPubspecOverrides = false,
     String? expectedName,
-    Pubspec Function(
+    required Pubspec Function(
       String path, {
       String? expectedName,
       required bool withPubspecOverrides,
-    })? loadPubspec,
+    }) loadPubspec,
   }) {
-    loadPubspec ??=
-        (path, {expectedName, required withPubspecOverrides}) => Pubspec.load(
-              path,
-              sources,
-              containingDescription: RootDescription(path),
-            );
     final pubspec = loadPubspec(
       dir,
       withPubspecOverrides: withPubspecOverrides,
@@ -171,7 +175,6 @@
         try {
           return Package.load(
             p.join(dir, workspacePath),
-            sources,
             loadPubspec: loadPubspec,
             withPubspecOverrides: withPubspecOverrides,
           );
@@ -253,8 +256,8 @@
   ///
   /// To convert them to paths relative to the package root, use [p.relative].
   List<String> listFiles({String? beneath, bool recursive = true}) {
-    var packageDir = dir;
-    var root = git.repoRoot(packageDir) ?? packageDir;
+    final packageDir = dir;
+    final root = git.repoRoot(packageDir) ?? packageDir;
     beneath = p
         .toUri(
           p.normalize(
@@ -358,11 +361,36 @@
       isDir: (dir) => dirExists(resolve(dir)),
     ).map(resolve).toList();
   }
+
+  /// Applies [transform] to each package in the workspace and returns a derived
+  /// package.
+  Package transformWorkspace(
+    Pubspec Function(Package) transform,
+  ) {
+    final workspace = {
+      for (final package in transitiveWorkspace) package.dir: package,
+    };
+    return Package.load(
+      dir,
+      withPubspecOverrides: true,
+      loadPubspec: (
+        path, {
+        expectedName,
+        required withPubspecOverrides,
+      }) =>
+          transform(workspace[path]!),
+    );
+  }
 }
 
-/// Reports an error if the graph of the workspace rooted at [root] is not a
-/// tree.
-void validateWorkspaceGraph(Package root) {
+/// Reports an error if one or more of:
+///
+/// * The graph of the workspace rooted at [root] is not a tree.
+/// * If a package name occurs twice.
+/// * If two packages in the workspace override the same package name.
+void validateWorkspace(Package root) {
+  if (root.workspaceChildren.isEmpty) return;
+
   final includedFrom = <String, String>{};
   final stack = [root];
 
@@ -382,4 +410,34 @@
     }
     stack.addAll(current.workspaceChildren);
   }
+
+  // Check that the workspace doesn't contain two packages with the same name!
+  final namesSeen = <String, Package>{};
+  for (final package in root.transitiveWorkspace) {
+    final collision = namesSeen[package.name];
+    if (collision != null) {
+      fail('''
+Workspace members must have unique names.
+`${collision.pubspecPath}` and `${package.pubspecPath}` are both called "${package.name}".
+''');
+    }
+    namesSeen[package.name] = package;
+  }
+
+  // Check that the workspace doesn't contain two overrides of the same package.
+  final overridesSeen = <String, Package>{};
+  for (final package in root.transitiveWorkspace) {
+    for (final override in package.pubspec.dependencyOverrides.keys) {
+      final collision = overridesSeen[override];
+      if (collision != null) {
+        fail('''
+The package `$override` is overridden in both:
+package `${collision.name}` at `${collision.dir}` and '${package.name}' at `${package.dir}`.
+
+Consider removing one of the overrides.
+''');
+      }
+      overridesSeen[override] = package;
+    }
+  }
 }
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index 5c40123..d10990f 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -50,7 +50,17 @@
     this.generator,
     this.generatorVersion,
     Map<String, dynamic>? additionalProperties,
-  }) : additionalProperties = additionalProperties ?? {};
+  }) : additionalProperties = additionalProperties ?? {} {
+    final names = <String>{};
+    // Sanity check:
+    for (final p in packages) {
+      if (!names.add(p.name)) {
+        throw ArgumentError(
+          'Duplicate name ${p.name} in generated package config',
+        );
+      }
+    }
+  }
 
   /// Create [PackageConfig] from JSON [data].
   ///
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index 158b11a..b749896 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -60,7 +60,7 @@
     }
 
     if (_transitiveDependencies == null) {
-      var closure = transitiveClosure(
+      final closure = transitiveClosure(
         mapMap<String, Package, String, Iterable<String>>(
           packages,
           value: (_, package) => package.dependencies.keys,
@@ -70,7 +70,7 @@
           mapMap<String, Set<String>, String, Set<Package>>(
         closure,
         value: (depender, names) {
-          var set = names.map((name) => packages[name]!).toSet();
+          final set = names.map((name) => packages[name]!).toSet();
           set.add(packages[depender]!);
           return set;
         },
diff --git a/lib/src/package_name.dart b/lib/src/package_name.dart
index dcc7712..d47a446 100644
--- a/lib/src/package_name.dart
+++ b/lib/src/package_name.dart
@@ -34,7 +34,7 @@
     detail ??= PackageDetail.defaults;
     if (isRoot) return name;
 
-    var buffer = StringBuffer(name);
+    final buffer = StringBuffer(name);
     if (detail.showSource ?? description is! HostedDescription) {
       buffer.write(' from ${description.source}');
       if (detail.showDescription) {
@@ -110,7 +110,7 @@
   String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
 
-    var buffer = StringBuffer(name);
+    final buffer = StringBuffer(name);
     if (detail.showVersion ?? !isRoot) buffer.write(' $version');
 
     if (!isRoot &&
@@ -156,7 +156,7 @@
   String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
 
-    var buffer = StringBuffer(name);
+    final buffer = StringBuffer(name);
     if (detail.showVersion ?? _showVersionConstraint) {
       buffer.write(' $constraint');
     }
@@ -183,10 +183,10 @@
     if (constraint is! VersionRange) return this;
     if (constraint.toString().startsWith('^')) return this;
 
-    var range = constraint as VersionRange;
+    final range = constraint as VersionRange;
     if (!range.includeMin) return this;
     if (range.includeMax) return this;
-    var min = range.min;
+    final min = range.min;
     if (min == null) return this;
     if (range.max == min.nextBreaking.firstPreRelease) {
       return PackageRange(_ref, VersionConstraint.compatibleWith(min));
diff --git a/lib/src/progress.dart b/lib/src/progress.dart
index ebddb2f..cff71d3 100644
--- a/lib/src/progress.dart
+++ b/lib/src/progress.dart
@@ -35,7 +35,7 @@
   Progress(this._message, {bool fine = false}) {
     _stopwatch.start();
 
-    var level = fine ? log.Level.fine : log.Level.message;
+    final level = fine ? log.Level.fine : log.Level.message;
 
     // The animation is only shown when it would be meaningful to a human.
     // That means we're writing a visible message to a TTY at normal log levels
@@ -121,7 +121,7 @@
     // rather than using `\r` to erase the entire line ensures that we don't
     // spam progress lines if they're wider than the terminal width.
     stdout.write('\b' * _timeLength);
-    var time = _time;
+    final time = _time;
     _timeLength = time.length;
     stdout.write(log.gray(time));
   }
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 2a951ad..8f14cad 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -58,7 +58,7 @@
   /// [devDependencies].
   ///
   /// This will be null if this was created using [Pubspec] or [Pubspec.empty].
-  final SourceRegistry _sources;
+  final SourceRegistry sources;
 
   /// It is used to resolve relative paths. And to resolve path-descriptions
   /// from a git dependency as git-descriptions.
@@ -121,7 +121,7 @@
       _dependencies ??= _parseDependencies(
         'dependencies',
         fields.nodes['dependencies'],
-        _sources,
+        sources,
         languageVersion,
         _packageName,
         _containingDescription,
@@ -134,7 +134,7 @@
       _devDependencies ??= _parseDependencies(
         'dev_dependencies',
         fields.nodes['dev_dependencies'],
-        _sources,
+        sources,
         languageVersion,
         _packageName,
         _containingDescription,
@@ -166,7 +166,7 @@
         _dependencyOverrides = _parseDependencies(
           'dependency_overrides',
           pubspecOverridesFields.nodes['dependency_overrides'],
-          _sources,
+          sources,
           languageVersion,
           _packageName,
           _containingDescription,
@@ -177,7 +177,7 @@
     return _dependencyOverrides ??= _parseDependencies(
       'dependency_overrides',
       fields.nodes['dependency_overrides'],
-      _sources,
+      sources,
       languageVersion,
       _packageName,
       _containingDescription,
@@ -201,7 +201,7 @@
   /// Parses the "environment" field in [parent] and returns a map from SDK
   /// identifiers to constraints on those SDKs.
   Map<String, SdkConstraint> _parseEnvironment(YamlMap parent) {
-    var yaml = parent['environment'];
+    final yaml = parent['environment'];
     final VersionConstraint originalDartSdkConstraint;
     if (yaml == null) {
       originalDartSdkConstraint = VersionConstraint.any;
@@ -217,7 +217,7 @@
         _FileType.pubspec,
       );
     }
-    var constraints = {
+    final constraints = {
       'dart': SdkConstraint.interpretDartSdkConstraint(
         originalDartSdkConstraint,
         defaultUpperBoundConstraint: _includeDefaultSdkConstraint
@@ -271,8 +271,8 @@
     bool allowOverridesFile = false,
     required Description containingDescription,
   }) {
-    var pubspecPath = p.join(packageDir, pubspecYamlFilename);
-    var overridesPath = p.join(packageDir, pubspecOverridesFilename);
+    final pubspecPath = p.join(packageDir, pubspecYamlFilename);
+    final overridesPath = p.join(packageDir, pubspecOverridesFilename);
     if (!fileExists(pubspecPath)) {
       throw FileException(
         // Make the package dir absolute because for the entrypoint it'll just
@@ -298,6 +298,26 @@
     );
   }
 
+  /// Convenience helper to pass to [Package.load].
+  static Pubspec Function(
+    String dir, {
+    String? expectedName,
+    required bool withPubspecOverrides,
+  }) loadRootWithSources(SourceRegistry sources) {
+    return (
+      String dir, {
+      String? expectedName,
+      required bool withPubspecOverrides,
+    }) =>
+        Pubspec.load(
+          dir,
+          sources,
+          expectedName: expectedName,
+          allowOverridesFile: withPubspecOverrides,
+          containingDescription: RootDescription(dir),
+        );
+  }
+
   Pubspec(
     String name, {
     Version? version,
@@ -322,7 +342,7 @@
         _givenSdkConstraints = sdkConstraints ??
             UnmodifiableMapView({'dart': SdkConstraint(VersionConstraint.any)}),
         _includeDefaultSdkConstraint = false,
-        _sources = sources ??
+        sources = sources ??
             ((String? name) => throw StateError('No source registry given')),
         _overridesFileFields = null,
         // This is a dummy value.
@@ -343,7 +363,7 @@
   /// [location] is the location from which this pubspec was loaded.
   Pubspec.fromMap(
     Map fields,
-    this._sources, {
+    this.sources, {
     YamlMap? overridesFields,
     String? expectedName,
     Uri? location,
@@ -453,7 +473,7 @@
   List<SourceSpanApplicationException> _collectErrorsFor(
     List<dynamic Function()> toCheck,
   ) {
-    var errors = <SourceSpanApplicationException>[];
+    final errors = <SourceSpanApplicationException>[];
     void collectError(void Function() fn) {
       try {
         fn();
@@ -528,7 +548,7 @@
   Description containingDescription, {
   _FileType fileType = _FileType.pubspec,
 }) {
-  var dependencies = <String, PackageRange>{};
+  final dependencies = <String, PackageRange>{};
 
   // Allow an empty dependencies key.
   if (node == null || node.value == null) return dependencies;
@@ -537,7 +557,7 @@
     _error('"$field" field must be a map.', node.span);
   }
 
-  var nonStringNode = node.nodes.keys
+  final nonStringNode = node.nodes.keys
       .firstWhereOrNull((e) => e is YamlScalar && e.value is! String);
   if (nonStringNode != null) {
     _error(
@@ -548,14 +568,14 @@
 
   node.nodes.forEach(
     (nameNode, specNode) {
-      var name = (nameNode as YamlNode).value;
+      final name = (nameNode as YamlNode).value;
       if (name is! String) {
         _error('A dependency name must be a string.', nameNode.span);
       }
       if (!packageNameRegExp.hasMatch(name)) {
         _error('Not a valid package name.', nameNode.span);
       }
-      var spec = specNode.value;
+      final spec = specNode.value;
       if (packageName != null && name == packageName) {
         _error('A package may not list itself as a dependency.', nameNode.span);
       }
@@ -587,7 +607,10 @@
           sourceName = 'hosted';
         } else {
           switch (otherEntries.single) {
-            case MapEntry(key: YamlScalar(value: String s), value: final d):
+            case MapEntry(
+                key: YamlScalar(value: final String s),
+                value: final d
+              ):
               sourceName = s;
               descriptionNode = d;
             case MapEntry(key: final k, value: _):
@@ -605,7 +628,7 @@
       }
 
       // Let the source validate the description.
-      var ref = _wrapFormatException(
+      final ref = _wrapFormatException(
         'description',
         descriptionNode?.span,
         () {
@@ -650,7 +673,7 @@
     'version constraint',
     node.span,
     () {
-      var constraint = VersionConstraint.parse(value);
+      final constraint = VersionConstraint.parse(value);
       return constraint;
     },
     packageName,
diff --git a/lib/src/pubspec_parse.dart b/lib/src/pubspec_parse.dart
index 48a7f3d..df5343f 100644
--- a/lib/src/pubspec_parse.dart
+++ b/lib/src/pubspec_parse.dart
@@ -197,7 +197,7 @@
     if (_executables != null) return _executables!;
 
     _executables = {};
-    var yaml = fields['executables'];
+    final yaml = fields['executables'];
     if (yaml == null) return _executables!;
 
     if (yaml is! YamlMap) {
@@ -207,7 +207,7 @@
       );
     }
 
-    var yamlMap = yaml;
+    final yamlMap = yaml;
 
     yamlMap.nodes.forEach((key, value) {
       key = key as YamlNode;
@@ -228,11 +228,11 @@
       final valuePattern = RegExp(r'[/\\]');
       _executables![keyValue] = switch (value.value) {
         null => keyValue,
-        String s when valuePattern.hasMatch(s) => _error(
+        final String s when valuePattern.hasMatch(s) => _error(
             '"executables" values may not contain path separators.',
             value.span,
           ),
-        String s => s,
+        final String s => s,
         _ => _error('"executables" values must be strings or null.', value.span)
       };
     });
diff --git a/lib/src/sdk/dart.dart b/lib/src/sdk/dart.dart
index f98477b..39e364d 100644
--- a/lib/src/sdk/dart.dart
+++ b/lib/src/sdk/dart.dart
@@ -27,7 +27,7 @@
 
   static final String _rootDirectory = () {
     // If DART_ROOT is specified, then this always points to the Dart SDK
-    if (Platform.environment['DART_ROOT'] case var root?) {
+    if (Platform.environment['DART_ROOT'] case final root?) {
       return root;
     }
 
@@ -35,18 +35,18 @@
 
     // The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is
     // "/path/to/sdk".
-    var aboveExecutable = p.dirname(p.dirname(Platform.resolvedExecutable));
+    final aboveExecutable = p.dirname(p.dirname(Platform.resolvedExecutable));
     assert(fileExists(p.join(aboveExecutable, 'version')));
     return aboveExecutable;
   }();
 
   /// The loaded `sdk_packages.yaml` file if present.
   static final SdkPackageConfig? _sdkPackages = () {
-    var path = p.join(_rootDirectory, 'sdk_packages.yaml');
+    final path = p.join(_rootDirectory, 'sdk_packages.yaml');
     if (!fileExists(path)) return null;
     final text = readTextFile(path);
     final yaml = loadYaml(text) as YamlMap;
-    var config = SdkPackageConfig.fromYaml(yaml);
+    final config = SdkPackageConfig.fromYaml(yaml);
     if (config.sdk != 'dart') {
       throw FormatException(
         'Expected a configuration for the `dart` sdk but got one for '
@@ -63,7 +63,7 @@
     // Some of the pub integration tests require an SDK version number, but the
     // tests on the bots are not run from a built SDK so this lets us avoid
     // parsing the missing version file.
-    var sdkVersion = Platform.environment['_PUB_TEST_SDK_VERSION'] ??
+    final sdkVersion = Platform.environment['_PUB_TEST_SDK_VERSION'] ??
         Platform.version.split(' ').first;
 
     return Version.parse(sdkVersion);
@@ -79,12 +79,12 @@
   @override
   String? packagePath(String name) {
     if (!isAvailable) return null;
-    var sdkPackages = _sdkPackages;
+    final sdkPackages = _sdkPackages;
     if (sdkPackages == null) return null;
 
-    var package = sdkPackages.packages[name];
+    final package = sdkPackages.packages[name];
     if (package == null) return null;
-    var packagePath = p.joinAll([_rootDirectory, ...package.path.split('/')]);
+    final packagePath = p.joinAll([_rootDirectory, ...package.path.split('/')]);
     if (dirExists(packagePath)) return packagePath;
 
     return null;
diff --git a/lib/src/sdk/flutter.dart b/lib/src/sdk/flutter.dart
index bb3cf12..993f4ac 100644
--- a/lib/src/sdk/flutter.dart
+++ b/lib/src/sdk/flutter.dart
@@ -106,10 +106,10 @@
     // `$flutter/bin/cache/pkg`. This checks both locations in order. If [name]
     // exists in neither place, it returns the `$flutter/packages` location
     // which is more human-readable for error messages.
-    var packagePath = p.join(rootDirectory!, 'packages', name);
+    final packagePath = p.join(rootDirectory!, 'packages', name);
     if (dirExists(packagePath)) return packagePath;
 
-    var cachePath = p.join(rootDirectory!, 'bin', 'cache', 'pkg', name);
+    final cachePath = p.join(rootDirectory!, 'bin', 'cache', 'pkg', name);
     if (dirExists(cachePath)) return cachePath;
 
     return null;
diff --git a/lib/src/sdk/fuchsia.dart b/lib/src/sdk/fuchsia.dart
index cdd41bd..8e40188 100644
--- a/lib/src/sdk/fuchsia.dart
+++ b/lib/src/sdk/fuchsia.dart
@@ -42,7 +42,7 @@
   String? packagePath(String name) {
     if (!isAvailable) return null;
 
-    var packagePath = p.join(_rootDirectory!, 'packages', name);
+    final packagePath = p.join(_rootDirectory!, 'packages', name);
     if (dirExists(packagePath)) return packagePath;
 
     return null;
diff --git a/lib/src/solver/failure.dart b/lib/src/solver/failure.dart
index d3d4293..ab136f9 100644
--- a/lib/src/solver/failure.dart
+++ b/lib/src/solver/failure.dart
@@ -31,7 +31,7 @@
   /// which one is returned.
   PackageNotFoundException? get packageNotFound {
     for (var incompatibility in incompatibility.externalIncompatibilities) {
-      var cause = incompatibility.cause;
+      final cause = incompatibility.cause;
       if (cause is PackageNotFoundIncompatibilityCause) return cause.exception;
     }
     return null;
@@ -90,7 +90,7 @@
       incompatibility,
       (value) => value + 1,
       ifAbsent: () {
-        var cause = incompatibility.cause;
+        final cause = incompatibility.cause;
         if (cause is ConflictCause) {
           _countDerivations(cause.conflict);
           _countDerivations(cause.other);
@@ -101,7 +101,7 @@
   }
 
   String write() {
-    var buffer = StringBuffer();
+    final buffer = StringBuffer();
 
     // Find all notices from incompatibility causes. This allows an
     // [IncompatibilityCause] to provide a notice that is printed before the
@@ -126,7 +126,7 @@
 
     // Only add line numbers if the derivation actually needs to refer to a line
     // by number.
-    var padding =
+    final padding =
         _lineNumbers.isEmpty ? 0 : '(${_lineNumbers.values.last}) '.length;
 
     var lastWasEmpty = false;
@@ -179,7 +179,7 @@
     bool numbered = false,
   }) {
     if (numbered) {
-      var number = _lineNumbers.length + 1;
+      final number = _lineNumbers.length + 1;
       _lineNumbers[incompatibility] = number;
       _lines.add((message, number));
     } else {
@@ -202,18 +202,18 @@
   }) {
     // Add explicit numbers for incompatibilities that are written far away
     // from their successors or that are used for multiple derivations.
-    var numbered = conclusion || _derivations[incompatibility]! > 1;
-    var conjunction = conclusion || incompatibility == _root ? 'So,' : 'And';
-    var incompatibilityString =
+    final numbered = conclusion || _derivations[incompatibility]! > 1;
+    final conjunction = conclusion || incompatibility == _root ? 'So,' : 'And';
+    final incompatibilityString =
         log.bold(incompatibility.toString(detailsForIncompatibility));
 
-    var conflictClause = incompatibility.cause as ConflictCause;
+    final conflictClause = incompatibility.cause as ConflictCause;
     var detailsForCause = _detailsForCause(conflictClause);
-    var cause = conflictClause.conflict.cause;
-    var otherCause = conflictClause.other.cause;
+    final cause = conflictClause.conflict.cause;
+    final otherCause = conflictClause.other.cause;
     if (cause is ConflictCause && otherCause is ConflictCause) {
-      var conflictLine = _lineNumbers[conflictClause.conflict];
-      var otherLine = _lineNumbers[conflictClause.other];
+      final conflictLine = _lineNumbers[conflictClause.conflict];
+      final otherLine = _lineNumbers[conflictClause.other];
       if (conflictLine != null && otherLine != null) {
         _write(
           incompatibility,
@@ -242,12 +242,12 @@
           numbered: numbered,
         );
       } else {
-        var singleLineConflict = _isSingleLine(cause);
-        var singleLineOther = _isSingleLine(otherCause);
+        final singleLineConflict = _isSingleLine(cause);
+        final singleLineOther = _isSingleLine(otherCause);
         if (singleLineOther || singleLineConflict) {
-          var first =
+          final first =
               singleLineOther ? conflictClause.conflict : conflictClause.other;
-          var second =
+          final second =
               singleLineOther ? conflictClause.other : conflictClause.conflict;
           _visit(first, detailsForCause);
           _visit(second, detailsForCause);
@@ -272,14 +272,14 @@
         }
       }
     } else if (cause is ConflictCause || otherCause is ConflictCause) {
-      var derived = cause is ConflictCause
+      final derived = cause is ConflictCause
           ? conflictClause.conflict
           : conflictClause.other;
-      var ext = cause is ConflictCause
+      final ext = cause is ConflictCause
           ? conflictClause.other
           : conflictClause.conflict;
 
-      var derivedLine = _lineNumbers[derived];
+      final derivedLine = _lineNumbers[derived];
       if (derivedLine != null) {
         _write(
           incompatibility,
@@ -287,11 +287,11 @@
           numbered: numbered,
         );
       } else if (_isCollapsible(derived)) {
-        var derivedCause = derived.cause as ConflictCause;
-        var collapsedDerived = derivedCause.conflict.cause is ConflictCause
+        final derivedCause = derived.cause as ConflictCause;
+        final collapsedDerived = derivedCause.conflict.cause is ConflictCause
             ? derivedCause.conflict
             : derivedCause.other;
-        var collapsedExt = derivedCause.conflict.cause is ConflictCause
+        final collapsedExt = derivedCause.conflict.cause is ConflictCause
             ? derivedCause.other
             : derivedCause.conflict;
 
@@ -359,7 +359,7 @@
     // line number and so will need to be written explicitly.
     if (_derivations[incompatibility]! > 1) return false;
 
-    var cause = incompatibility.cause as ConflictCause;
+    final cause = incompatibility.cause as ConflictCause;
     // If [incompatibility] is derived from two derived incompatibilities,
     // there are too many transitive causes to display concisely.
     if (cause.conflict.cause is ConflictCause &&
@@ -376,7 +376,7 @@
 
     // If [incompatibility]'s internal cause is numbered, collapsing it would
     // get too noisy.
-    var complex =
+    final complex =
         cause.conflict.cause is ConflictCause ? cause.conflict : cause.other;
     return !_lineNumbers.containsKey(complex);
   }
@@ -394,15 +394,15 @@
   /// but each has a different source, those incompatibilities should explicitly
   /// print their sources, and similarly for differing descriptions.
   Map<String, PackageDetail> _detailsForCause(ConflictCause cause) {
-    var conflictPackages = <String, PackageRange>{};
+    final conflictPackages = <String, PackageRange>{};
     for (var term in cause.conflict.terms) {
       if (term.package.isRoot) continue;
       conflictPackages[term.package.name] = term.package;
     }
 
-    var details = <String, PackageDetail>{};
+    final details = <String, PackageDetail>{};
     for (var term in cause.other.terms) {
-      var conflictPackage = conflictPackages[term.package.name];
+      final conflictPackage = conflictPackages[term.package.name];
       if (term.package.isRoot) continue;
       if (conflictPackage == null) continue;
       if (conflictPackage.description.source !=
diff --git a/lib/src/solver/incompatibility.dart b/lib/src/solver/incompatibility.dart
index 15178b3..9e94968 100644
--- a/lib/src/solver/incompatibility.dart
+++ b/lib/src/solver/incompatibility.dart
@@ -25,7 +25,7 @@
   /// derivation graph.
   Iterable<Incompatibility> get externalIncompatibilities sync* {
     if (cause is ConflictCause) {
-      var cause = this.cause as ConflictCause;
+      final cause = this.cause as ConflictCause;
       yield* cause.conflict.externalIncompatibilities;
       yield* cause.other.externalIncompatibilities;
     } else {
@@ -58,10 +58,10 @@
     }
 
     // Coalesce multiple terms about the same package if possible.
-    var byName = <String, Map<PackageRef, Term>>{};
+    final byName = <String, Map<PackageRef, Term>>{};
     for (var term in terms) {
-      var byRef = byName.putIfAbsent(term.package.name, () => {});
-      var ref = term.package.toRef();
+      final byRef = byName.putIfAbsent(term.package.name, () => {});
+      final ref = term.package.toRef();
       if (byRef.containsKey(ref)) {
         // If we have two terms that refer to the same package but have a null
         // intersection, they're mutually exclusive, making this incompatibility
@@ -78,7 +78,7 @@
       byName.values.expand((byRef) {
         // If there are any positive terms for a given package, we can discard
         // any negative terms.
-        var positiveTerms =
+        final positiveTerms =
             byRef.values.where((term) => term.isPositive).toList();
         if (positiveTerms.isNotEmpty) return positiveTerms;
 
@@ -99,8 +99,8 @@
     if (cause is DependencyIncompatibilityCause) {
       assert(terms.length == 2);
 
-      var depender = terms.first;
-      var dependee = terms.last;
+      final depender = terms.first;
+      final dependee = terms.last;
       assert(depender.isPositive);
       assert(!dependee.isPositive);
 
@@ -110,8 +110,9 @@
       assert(terms.length == 1);
       assert(terms.first.isPositive);
 
-      var cause = this.cause as SdkIncompatibilityCause;
-      var buffer = StringBuffer(_terse(terms.first, details, allowEvery: true));
+      final cause = this.cause as SdkIncompatibilityCause;
+      final buffer =
+          StringBuffer(_terse(terms.first, details, allowEvery: true));
       if (cause.noNullSafetyCause) {
         buffer.write(' doesn\'t support null safety');
       } else {
@@ -133,7 +134,7 @@
       assert(terms.length == 1);
       assert(terms.first.isPositive);
 
-      var cause = this.cause as PackageNotFoundIncompatibilityCause;
+      final cause = this.cause as PackageNotFoundIncompatibilityCause;
       return "${_terseRef(terms.first, details)} doesn't exist "
           '(${cause.exception.message})';
     } else if (cause is UnknownSourceIncompatibilityCause) {
@@ -154,7 +155,7 @@
     }
 
     if (terms.length == 1) {
-      var term = terms.single;
+      final term = terms.single;
       if (term.constraint.isAny) {
         return '${_terseRef(term, details)} is '
             "${term.isPositive ? 'forbidden' : 'required'}";
@@ -165,14 +166,14 @@
     }
 
     if (terms.length == 2) {
-      var term1 = terms.first;
-      var term2 = terms.last;
+      final term1 = terms.first;
+      final term2 = terms.last;
       if (term1.isPositive == term2.isPositive) {
         if (term1.isPositive) {
-          var package1 = term1.constraint.isAny
+          final package1 = term1.constraint.isAny
               ? _terseRef(term1, details)
               : _terse(term1, details);
-          var package2 = term2.constraint.isAny
+          final package2 = term2.constraint.isAny
               ? _terseRef(term2, details)
               : _terse(term2, details);
           return '$package1 is incompatible with $package2';
@@ -183,15 +184,15 @@
       }
     }
 
-    var positive = <String>[];
-    var negative = <String>[];
+    final positive = <String>[];
+    final negative = <String>[];
     for (var term in terms) {
       (term.isPositive ? positive : negative).add(_terse(term, details));
     }
 
     if (positive.isNotEmpty && negative.isNotEmpty) {
       if (positive.length == 1) {
-        var positiveTerm = terms.firstWhere((term) => term.isPositive);
+        final positiveTerm = terms.firstWhere((term) => term.isPositive);
         return '${_terse(positiveTerm, details, allowEvery: true)} requires '
             "${negative.join(' or ')}";
       } else {
@@ -218,18 +219,18 @@
     int? thisLine,
     int? otherLine,
   ]) {
-    var requiresBoth = _tryRequiresBoth(other, details, thisLine, otherLine);
+    final requiresBoth = _tryRequiresBoth(other, details, thisLine, otherLine);
     if (requiresBoth != null) return requiresBoth;
 
-    var requiresThrough =
+    final requiresThrough =
         _tryRequiresThrough(other, details, thisLine, otherLine);
     if (requiresThrough != null) return requiresThrough;
 
-    var requiresForbidden =
+    final requiresForbidden =
         _tryRequiresForbidden(other, details, thisLine, otherLine);
     if (requiresForbidden != null) return requiresForbidden;
 
-    var buffer = StringBuffer(toString(details));
+    final buffer = StringBuffer(toString(details));
     if (thisLine != null) buffer.write(' $thisLine');
     buffer.write(' and ${other.toString(details)}');
     if (otherLine != null) buffer.write(' $thisLine');
@@ -248,24 +249,24 @@
   ]) {
     if (terms.length == 1 || other.terms.length == 1) return null;
 
-    var thisPositive = _singleTermWhere((term) => term.isPositive);
+    final thisPositive = _singleTermWhere((term) => term.isPositive);
     if (thisPositive == null) return null;
-    var otherPositive = other._singleTermWhere((term) => term.isPositive);
+    final otherPositive = other._singleTermWhere((term) => term.isPositive);
     if (otherPositive == null) return null;
     if (thisPositive.package != otherPositive.package) return null;
 
-    var thisNegatives = terms
+    final thisNegatives = terms
         .where((term) => !term.isPositive)
         .map((term) => _terse(term, details))
         .join(' or ');
-    var otherNegatives = other.terms
+    final otherNegatives = other.terms
         .where((term) => !term.isPositive)
         .map((term) => _terse(term, details))
         .join(' or ');
 
-    var buffer =
+    final buffer =
         StringBuffer('${_terse(thisPositive, details, allowEvery: true)} ');
-    var isDependency = cause is DependencyIncompatibilityCause &&
+    final isDependency = cause is DependencyIncompatibilityCause &&
         other.cause is DependencyIncompatibilityCause;
     buffer.write(isDependency ? 'depends on' : 'requires');
     buffer.write(' both $thisNegatives');
@@ -287,12 +288,12 @@
   ]) {
     if (terms.length == 1 || other.terms.length == 1) return null;
 
-    var thisNegative = _singleTermWhere((term) => !term.isPositive);
-    var otherNegative = other._singleTermWhere((term) => !term.isPositive);
+    final thisNegative = _singleTermWhere((term) => !term.isPositive);
+    final otherNegative = other._singleTermWhere((term) => !term.isPositive);
     if (thisNegative == null && otherNegative == null) return null;
 
-    var thisPositive = _singleTermWhere((term) => term.isPositive);
-    var otherPositive = other._singleTermWhere((term) => term.isPositive);
+    final thisPositive = _singleTermWhere((term) => term.isPositive);
+    final otherPositive = other._singleTermWhere((term) => term.isPositive);
 
     Incompatibility prior;
     Term priorNegative;
@@ -321,15 +322,15 @@
       return null;
     }
 
-    var priorPositives = prior.terms.where((term) => term.isPositive);
+    final priorPositives = prior.terms.where((term) => term.isPositive);
 
-    var buffer = StringBuffer();
+    final buffer = StringBuffer();
     if (priorPositives.length > 1) {
-      var priorString =
+      final priorString =
           priorPositives.map((term) => _terse(term, details)).join(' or ');
       buffer.write('if $priorString then ');
     } else {
-      var verb = prior.cause is DependencyIncompatibilityCause
+      final verb = prior.cause is DependencyIncompatibilityCause
           ? 'depends on'
           : 'requires';
       buffer.write('${_terse(priorPositives.first, details, allowEvery: true)} '
@@ -386,15 +387,15 @@
       latterLine = otherLine;
     }
 
-    var negative = prior._singleTermWhere((term) => !term.isPositive);
+    final negative = prior._singleTermWhere((term) => !term.isPositive);
     if (negative == null) return null;
     if (!negative.inverse.satisfies(latter.terms.first)) return null;
 
-    var positives = prior.terms.where((term) => term.isPositive);
+    final positives = prior.terms.where((term) => term.isPositive);
 
-    var buffer = StringBuffer();
+    final buffer = StringBuffer();
     if (positives.length > 1) {
-      var priorString =
+      final priorString =
           positives.map((term) => _terse(term, details)).join(' or ');
       buffer.write('if $priorString then ');
     } else {
@@ -407,7 +408,7 @@
     }
 
     if (latter.cause is UnknownSourceIncompatibilityCause) {
-      var package = latter.terms.first.package;
+      final package = latter.terms.first.package;
       buffer.write('${package.name} ');
       if (priorLine != null) buffer.write('($priorLine) ');
       buffer.write('from unknown source "${package.source}"');
@@ -419,7 +420,7 @@
     if (priorLine != null) buffer.write('($priorLine) ');
 
     if (latter.cause is SdkIncompatibilityCause) {
-      var cause = latter.cause as SdkIncompatibilityCause;
+      final cause = latter.cause as SdkIncompatibilityCause;
       if (cause.noNullSafetyCause) {
         buffer.write('which doesn\'t support null safety');
       } else {
diff --git a/lib/src/solver/package_lister.dart b/lib/src/solver/package_lister.dart
index c46df0a..c38d300 100644
--- a/lib/src/solver/package_lister.dart
+++ b/lib/src/solver/package_lister.dart
@@ -85,7 +85,7 @@
 
   /// All versions of the package, sorted by [Version.compareTo].
   Future<List<PackageId>> get _versions => _versionsMemo.runOnce(() async {
-        var cachedVersions = _ref.isRoot
+        final cachedVersions = _ref.isRoot
             ? [
                 PackageId(
                   _ref.name,
@@ -131,6 +131,7 @@
   PackageLister.root(
     Package package,
     this._systemCache, {
+    required Set<String> overriddenPackages,
     required Map<String, Version>? sdkOverrides,
   })  : _ref = PackageRef.root(package),
         // Treat the package as locked so we avoid the logic for finding the
@@ -138,8 +139,7 @@
         // package.
         _locked = PackageId.root(package),
         _dependencyType = DependencyType.none,
-        _overriddenPackages =
-            Set.unmodifiable(package.dependencyOverrides.keys),
+        _overriddenPackages = overriddenPackages,
         _isDowngrade = false,
         _allowedRetractedVersion = null,
         sdkOverrides = sdkOverrides ?? {},
@@ -170,17 +170,17 @@
     final locked = _locked;
     if (locked != null && constraint.allows(locked.version)) return locked;
 
-    var versions = await _versions;
+    final versions = await _versions;
 
     // If [constraint] has a minimum (or a maximum in downgrade mode), we can
     // bail early once we're past it.
     var isPastLimit = (Version _) => false;
     if (constraint is VersionRange) {
       if (_isDowngrade) {
-        var max = constraint.max;
+        final max = constraint.max;
         if (max != null) isPastLimit = (version) => version > max;
       } else {
-        var min = constraint.min;
+        final min = constraint.min;
         if (min != null) isPastLimit = (version) => version < min;
       }
     }
@@ -251,7 +251,7 @@
         id.version == _locked.version) {
       if (_listedLockedVersion) return const [];
 
-      var depender = id.toRange();
+      final depender = id.toRange();
       _listedLockedVersion = true;
       for (var sdk in sdks.values) {
         if (!_matchesSdkConstraint(pubspec, sdk)) {
@@ -286,8 +286,8 @@
       return entries.map((range) => _dependency(depender, range)).toList();
     }
 
-    var versions = await _versions;
-    var index = lowerBound(
+    final versions = await _versions;
+    final index = lowerBound(
       versions,
       id,
       compare: (PackageId id1, PackageId id2) =>
@@ -297,29 +297,29 @@
     assert(versions[index].version == id.version);
 
     for (var sdk in sdks.values) {
-      var sdkIncompatibility = await _checkSdkConstraint(index, sdk);
+      final sdkIncompatibility = await _checkSdkConstraint(index, sdk);
       if (sdkIncompatibility != null) return [sdkIncompatibility];
     }
 
     // Don't recompute dependencies that have already been emitted.
-    var dependencies = Map<String, PackageRange>.from(pubspec.dependencies);
+    final dependencies = Map<String, PackageRange>.from(pubspec.dependencies);
     for (var package in dependencies.keys.toList()) {
       if (_overriddenPackages.contains(package)) {
         dependencies.remove(package);
         continue;
       }
 
-      var constraint = _alreadyListedDependencies[package];
+      final constraint = _alreadyListedDependencies[package];
       if (constraint != null && constraint.allows(id.version)) {
         dependencies.remove(package);
       }
     }
 
-    var lower = await _dependencyBounds(dependencies, index, upper: false);
-    var upper = await _dependencyBounds(dependencies, index);
+    final lower = await _dependencyBounds(dependencies, index, upper: false);
+    final upper = await _dependencyBounds(dependencies, index);
 
     return ordered(dependencies.keys).map((package) {
-      var constraint = VersionRange(
+      final constraint = VersionRange(
         min: lower[package],
         includeMin: true,
         max: upper[package],
@@ -351,15 +351,15 @@
   ///
   /// Otherwise, returns `null`.
   Future<Incompatibility?> _checkSdkConstraint(int index, Sdk sdk) async {
-    var versions = await _versions;
+    final versions = await _versions;
 
     bool allowsSdk(Pubspec pubspec) => _matchesSdkConstraint(pubspec, sdk);
 
     if (allowsSdk(await _describeSafe(versions[index]))) return null;
 
-    var (boundsFirstIndex, boundsLastIndex) =
+    final (boundsFirstIndex, boundsLastIndex) =
         await _findBounds(index, (pubspec) => !allowsSdk(pubspec));
-    var incompatibleVersions = VersionRange(
+    final incompatibleVersions = VersionRange(
       min: boundsFirstIndex == 0 ? null : versions[boundsFirstIndex].version,
       includeMin: true,
       max: boundsLastIndex == versions.length - 1
@@ -369,10 +369,10 @@
     );
     _knownInvalidVersions = incompatibleVersions.union(_knownInvalidVersions);
 
-    var sdkConstraint = await foldAsync<VersionConstraint, PackageId>(
+    final sdkConstraint = await foldAsync<VersionConstraint, PackageId>(
         slice(versions, boundsFirstIndex, boundsLastIndex + 1),
         VersionConstraint.empty, (previous, version) async {
-      var pubspec = await _describeSafe(version);
+      final pubspec = await _describeSafe(version);
       return previous.union(
         pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint ??
             VersionConstraint.any,
@@ -393,7 +393,7 @@
     int start,
     bool Function(Pubspec) match,
   ) async {
-    var versions = await _versions;
+    final versions = await _versions;
 
     var first = start - 1;
     while (first > 0) {
@@ -423,19 +423,19 @@
     int index, {
     bool upper = true,
   }) async {
-    var versions = await _versions;
-    var bounds = <String, Version>{};
+    final versions = await _versions;
+    final bounds = <String, Version>{};
     var previous = versions[index];
     outer:
     for (var id in upper
         ? versions.skip(index + 1)
         : versions.reversed.skip(versions.length - index)) {
-      var pubspec = await _describeSafe(id);
+      final pubspec = await _describeSafe(id);
 
       // The upper bound is exclusive and so is the first package with a
       // different dependency. The lower bound is inclusive, and so is the last
       // package with the same dependency.
-      var boundary = (upper ? id : previous).version;
+      final boundary = (upper ? id : previous).version;
 
       // Once we hit an incompatible version, it doesn't matter whether it has
       // the same dependencies.
@@ -482,7 +482,7 @@
   bool _matchesSdkConstraint(Pubspec pubspec, Sdk sdk) {
     if (_overriddenPackages.contains(pubspec.name)) return true;
 
-    var constraint = pubspec.sdkConstraints[sdk.identifier];
+    final constraint = pubspec.sdkConstraints[sdk.identifier];
     if (constraint == null) return true;
 
     return sdk.isAvailable &&
diff --git a/lib/src/solver/partial_solution.dart b/lib/src/solver/partial_solution.dart
index cc3aeaa..2e6387a 100644
--- a/lib/src/solver/partial_solution.dart
+++ b/lib/src/solver/partial_solution.dart
@@ -91,9 +91,9 @@
   void backtrack(int decisionLevel) {
     _backtracking = true;
 
-    var packages = <String>{};
+    final packages = <String>{};
     while (_assignments.last.decisionLevel > decisionLevel) {
-      var removed = _assignments.removeLast();
+      final removed = _assignments.removeLast();
       packages.add(removed.package.name);
       if (removed.isDecision) _decisions.remove(removed.package.name);
     }
@@ -113,17 +113,17 @@
 
   /// Registers [assignment] in [_positive] or [_negative].
   void _register(Assignment assignment) {
-    var name = assignment.package.name;
-    var oldPositive = _positive[name];
+    final name = assignment.package.name;
+    final oldPositive = _positive[name];
     if (oldPositive != null) {
       _positive[name] = oldPositive.intersect(assignment);
       return;
     }
 
-    var ref = assignment.package.toRef();
-    var negativeByRef = _negative[name];
-    var oldNegative = negativeByRef == null ? null : negativeByRef[ref];
-    var term =
+    final ref = assignment.package.toRef();
+    final negativeByRef = _negative[name];
+    final oldNegative = negativeByRef == null ? null : negativeByRef[ref];
+    final term =
         oldNegative == null ? assignment : assignment.intersect(oldNegative)!;
 
     if (term.isPositive) {
@@ -173,18 +173,18 @@
   /// Returns the relationship between the package versions allowed by all
   /// assignments in [this] and those allowed by [term].
   SetRelation relation(Term term) {
-    var positive = _positive[term.package.name];
+    final positive = _positive[term.package.name];
     if (positive != null) return positive.relation(term);
 
     // If there are no assignments related to [term], that means the
     // assignments allow any version of any package, which is a superset of
     // [term].
-    var byRef = _negative[term.package.name];
+    final byRef = _negative[term.package.name];
     if (byRef == null) return SetRelation.overlapping;
 
     // not foo from git is a superset of foo from hosted
     // not foo from git overlaps not foo from hosted
-    var negative = byRef[term.package.toRef()];
+    final negative = byRef[term.package.toRef()];
     if (negative == null) return SetRelation.overlapping;
 
     return negative.relation(term);
diff --git a/lib/src/solver/reformat_ranges.dart b/lib/src/solver/reformat_ranges.dart
index fe0f003..bce2d15 100644
--- a/lib/src/solver/reformat_ranges.dart
+++ b/lib/src/solver/reformat_ranges.dart
@@ -40,18 +40,18 @@
 /// Returns [term] with the upper and lower bounds of its package range
 /// reformatted if necessary.
 Term _reformatTerm(Map<PackageRef, PackageLister> packageListers, Term term) {
-  var versions = packageListers[term.package.toRef()]?.cachedVersions ?? [];
+  final versions = packageListers[term.package.toRef()]?.cachedVersions ?? [];
 
   if (term.package.constraint is! VersionRange) return term;
   if (term.package.constraint is Version) return term;
-  var range = term.package.constraint as VersionRange;
+  final range = term.package.constraint as VersionRange;
 
-  var min = _reformatMin(versions, range);
-  var maxInfo = reformatMax(versions, range);
+  final min = _reformatMin(versions, range);
+  final maxInfo = reformatMax(versions, range);
 
   if (min == null && maxInfo == null) return term;
 
-  var (max, includeMax) = maxInfo ?? (range.max, range.includeMax);
+  final (max, includeMax) = maxInfo ?? (range.max, range.includeMax);
 
   return Term(
     term.package
@@ -73,13 +73,13 @@
 /// Returns the new minimum version to use for [range], or `null` if it doesn't
 /// need to be reformatted.
 Version? _reformatMin(List<PackageId> versions, VersionRange range) {
-  var min = range.min;
+  final min = range.min;
   if (min == null) return null;
   if (!range.includeMin) return null;
   if (!min.isFirstPreRelease) return null;
 
-  var index = _lowerBound(versions, min);
-  var next = index == versions.length ? null : versions[index].version;
+  final index = _lowerBound(versions, min);
+  final next = index == versions.length ? null : versions[index].version;
 
   // If there's a real pre-release version of [range.min], use that as the min.
   // Otherwise, use the release version.
@@ -99,8 +99,8 @@
   // `alwaysIncludeMaxPreRelease = false` for discovering when a max-bound
   // should not include prereleases.
 
-  var max = range.max;
-  var min = range.min;
+  final max = range.max;
+  final min = range.min;
   if (max == null) return null;
   if (range.includeMax) return null;
   if (max.isPreRelease) return null;
@@ -109,8 +109,8 @@
     return null;
   }
 
-  var index = _lowerBound(versions, max);
-  var previous = index == 0 ? null : versions[index - 1].version;
+  final index = _lowerBound(versions, max);
+  final previous = index == 0 ? null : versions[index - 1].version;
 
   return previous != null && equalsIgnoringPreRelease(previous, max)
       ? (previous, true)
@@ -128,8 +128,8 @@
   var min = 0;
   var max = ids.length;
   while (min < max) {
-    var mid = min + ((max - min) >> 1);
-    var id = ids[mid];
+    final mid = min + ((max - min) >> 1);
+    final id = ids[mid];
     if (id.version.compareTo(version) < 0) {
       min = mid + 1;
     } else {
diff --git a/lib/src/solver/report.dart b/lib/src/solver/report.dart
index 6154d32..4d2aa9b 100644
--- a/lib/src/solver/report.dart
+++ b/lib/src/solver/report.dart
@@ -31,6 +31,7 @@
   final LockFile _newLockFile;
   final SystemCache _cache;
   final bool _dryRun;
+  final Map<String, PackageRange> _overriddenPackages;
 
   /// If quiet only a single summary line is output.
   final bool _quiet;
@@ -52,6 +53,7 @@
     this._type,
     this._location,
     this._rootPubspec,
+    this._overriddenPackages,
     this._previousLockFile,
     this._newLockFile,
     this._availableVersions,
@@ -145,7 +147,7 @@
   /// packages.
   Future<void> summarize(int changes) async {
     // Count how many dependencies actually changed.
-    var dependencies = _newLockFile.packages.keys.toSet();
+    final dependencies = _newLockFile.packages.keys.toSet();
     dependencies.addAll(_previousLockFile.packages.keys);
     dependencies.remove(_rootPubspec.name);
 
@@ -210,7 +212,7 @@
   Future<int> _reportChanges() async {
     final output = StringBuffer();
     // Show the new set of dependencies ordered by name.
-    var names = _newLockFile.packages.keys.toList();
+    final names = _newLockFile.packages.keys.toList();
     names.remove(_rootPubspec.name);
     names.sort();
     var changes = 0;
@@ -218,7 +220,7 @@
       changes += await _reportPackage(name, output) ? 1 : 0;
     }
     // Show any removed ones.
-    var removed = _previousLockFile.packages.keys.toSet();
+    final removed = _previousLockFile.packages.keys.toSet();
     removed.removeAll(names);
     removed.remove(_rootPubspec.name); // Never consider root.
     if (removed.isNotEmpty) {
@@ -335,11 +337,11 @@
     StringBuffer output, {
     bool alwaysShow = false,
   }) async {
-    var newId = _newLockFile.packages[name];
-    var oldId = _previousLockFile.packages[name];
-    var id = newId ?? oldId!;
+    final newId = _newLockFile.packages[name];
+    final oldId = _previousLockFile.packages[name];
+    final id = newId ?? oldId!;
 
-    var isOverridden = _rootPubspec.dependencyOverrides.containsKey(id.name);
+    final isOverridden = _overriddenPackages.containsKey(id.name);
 
     // If the package was previously a dependency but the dependency has
     // changed in some way.
@@ -388,7 +390,7 @@
     // See if there are any newer versions of the package that we were
     // unable to upgrade to.
     if (newId != null && _type != SolveType.downgrade) {
-      var versions = _availableVersions[newId.name]!;
+      final versions = _availableVersions[newId.name]!;
 
       var newerStable = false;
       var newerUnstable = false;
@@ -487,7 +489,7 @@
     final oldDependencyType = dependencyType(_previousLockFile, name);
     final newDependencyType = dependencyType(_newLockFile, name);
 
-    var dependencyTypeChanged = oldId != null &&
+    final dependencyTypeChanged = oldId != null &&
         newId != null &&
         oldDependencyType != newDependencyType;
 
@@ -545,7 +547,7 @@
     output.write(id.version);
 
     if (id.source != _cache.defaultSource) {
-      var description = id.description.format();
+      final description = id.description.format();
       output.write(' from ${id.source} $description');
     }
   }
diff --git a/lib/src/solver/result.dart b/lib/src/solver/result.dart
index a53c17c..2d3ea06 100644
--- a/lib/src/solver/result.dart
+++ b/lib/src/solver/result.dart
@@ -20,6 +20,9 @@
   /// reachable from the root.
   final List<PackageId> packages;
 
+  /// Names of all dependency overrides in the workspace.
+  final Set<String> _overriddenPackages;
+
   /// The root package of this resolution.
   final Package _root;
 
@@ -80,13 +83,11 @@
 
     // Don't factor in overridden dependencies' SDK constraints, because we'll
     // accept those packages even if their constraints don't match.
-    var nonOverrides = pubspecs.values
-        .where(
-          (pubspec) => !_root.dependencyOverrides.containsKey(pubspec.name),
-        )
+    final nonOverrides = pubspecs.values
+        .where((pubspec) => !_overriddenPackages.contains(pubspec.name))
         .toList();
 
-    var sdkConstraints = <String, VersionConstraint>{};
+    final sdkConstraints = <String, VersionConstraint>{};
     for (var pubspec in nonOverrides) {
       pubspec.sdkConstraints.forEach((identifier, constraint) {
         sdkConstraints[identifier] = constraint.effectiveConstraint
@@ -101,7 +102,7 @@
       },
       mainDependencies: MapKeySet(_root.dependencies),
       devDependencies: MapKeySet(_root.devDependencies),
-      overriddenDependencies: MapKeySet(_root.dependencyOverrides),
+      overriddenDependencies: _overriddenPackages,
     );
   }
 
@@ -111,7 +112,7 @@
   ///
   /// This includes packages that were added or removed.
   Set<String> get changedPackages {
-    var changed = packages
+    final changed = packages
         .where((id) => _previousLockFile.packages[id.name] != id)
         .map((id) => id.name)
         .toSet();
@@ -125,6 +126,7 @@
 
   SolveResult(
     this._root,
+    this._overriddenPackages,
     this._previousLockFile,
     this.packages,
     this.pubspecs,
diff --git a/lib/src/solver/term.dart b/lib/src/solver/term.dart
index ac20cdc..787511e 100644
--- a/lib/src/solver/term.dart
+++ b/lib/src/solver/term.dart
@@ -51,7 +51,7 @@
       );
     }
 
-    var otherConstraint = other.constraint;
+    final otherConstraint = other.constraint;
     if (other.isPositive) {
       if (isPositive) {
         // foo from hosted is disjoint with foo from git
@@ -127,8 +127,8 @@
     if (_compatiblePackage(other.package)) {
       if (isPositive != other.isPositive) {
         // foo ^1.0.0 ∩ not foo ^1.5.0 → foo >=1.0.0 <1.5.0
-        var positive = isPositive ? this : other;
-        var negative = isPositive ? other : this;
+        final positive = isPositive ? this : other;
+        final negative = isPositive ? other : this;
         return _nonEmptyTerm(
           positive.constraint.difference(negative.constraint),
           true,
diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart
index 3f12427..89019c3 100644
--- a/lib/src/solver/version_solver.dart
+++ b/lib/src/solver/version_solver.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:math' as math;
 
+import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
 import '../exceptions.dart';
@@ -77,6 +78,11 @@
   /// name anywhere in the dependency graph.
   final Map<String, PackageRange> _dependencyOverrides;
 
+  /// Names of packages that are overridden in this resolution as a [Set] for
+  /// convenience.
+  late final Set<String> _overriddenPackages =
+      MapKeySet(_root.allOverridesInWorkspace);
+
   /// The set of packages for which the lockfile should be ignored.
   final Set<String> _unlock;
 
@@ -94,7 +100,7 @@
     Iterable<String> unlock, {
     Map<String, Version> sdkOverrides = const {},
   })  : _sdkOverrides = sdkOverrides,
-        _dependencyOverrides = _root.dependencyOverrides,
+        _dependencyOverrides = _root.allOverridesInWorkspace,
         _unlock = {...unlock};
 
   /// Prime the solver with [constraints].
@@ -142,10 +148,10 @@
   ///
   /// [unit propagation]: https://github.com/dart-lang/pub/tree/master/doc/solver.md#unit-propagation
   void _propagate(String package) {
-    var changed = {package};
+    final changed = {package};
 
     while (changed.isNotEmpty) {
-      var package = changed.first;
+      final package = changed.first;
       changed.remove(package);
 
       // Iterate in reverse because conflict resolution tends to produce more
@@ -153,14 +159,14 @@
       // we can derive stronger assignments sooner and more eagerly find
       // conflicts.
       for (var incompatibility in _incompatibilities[package]!.reversed) {
-        var result = _propagateIncompatibility(incompatibility);
+        final result = _propagateIncompatibility(incompatibility);
         if (result == #conflict) {
           // If [incompatibility] is satisfied by [_solution], we use
           // [_resolveConflict] to determine the root cause of the conflict as a
           // new incompatibility. It also backjumps to a point in [_solution]
           // where that incompatibility will allow us to derive new assignments
           // that avoid the conflict.
-          var rootCause = _resolveConflict(incompatibility);
+          final rootCause = _resolveConflict(incompatibility);
 
           // Backjumping erases all the assignments we did at the previous
           // decision level, so we clear [changed] and refill it with the
@@ -192,8 +198,8 @@
     Term? unsatisfied;
 
     for (var i = 0; i < incompatibility.terms.length; i++) {
-      var term = incompatibility.terms[i];
-      var relation = _solution.relation(term);
+      final term = incompatibility.terms[i];
+      final relation = _solution.relation(term);
 
       if (relation == SetRelation.disjoint) {
         // If [term] is already contradicted by [_solution], then
@@ -265,7 +271,7 @@
       var previousSatisfierLevel = 1;
 
       for (var term in incompatibility.terms) {
-        var satisfier = _solution.satisfier(term);
+        final satisfier = _solution.satisfier(term);
         if (mostRecentSatisfier == null) {
           mostRecentTerm = term;
           mostRecentSatisfier = satisfier;
@@ -314,7 +320,7 @@
       // true (that is, we know for sure no solution will satisfy the
       // incompatibility) while also approximating the intuitive notion of the
       // "root cause" of the conflict.
-      var newTerms = <Term>[
+      final newTerms = <Term>[
         for (var term in incompatibility.terms)
           if (term != mostRecentTerm) term,
         for (var term in mostRecentSatisfier.cause!.terms)
@@ -341,8 +347,8 @@
       );
       newIncompatibility = true;
 
-      var partially = difference == null ? '' : ' partially';
-      var bang = log.red('!');
+      final partially = difference == null ? '' : ' partially';
+      final bang = log.red('!');
       _log('$bang $mostRecentTerm is$partially satisfied by '
           '$mostRecentSatisfier');
       _log('$bang which is caused by "${mostRecentSatisfier.cause}"');
@@ -358,7 +364,7 @@
   /// propagated by [_propagate], or `null` indicating that version solving is
   /// complete and a solution has been found.
   Future<String?> _choosePackageVersion() async {
-    var unsatisfied = _solution.unsatisfied.toList();
+    final unsatisfied = _solution.unsatisfied.toList();
     if (unsatisfied.isEmpty) return null;
 
     // If we require a package from an unknown source, add an incompatibility
@@ -376,7 +382,7 @@
 
     /// Prefer packages with as few remaining versions as possible, so that if a
     /// conflict is necessary it's forced quickly.
-    var package = await minByAsync(unsatisfied, (PackageRange package) async {
+    final package = await minByAsync(unsatisfied, (PackageRange package) async {
       return await _packageLister(package).countVersions(package.constraint);
     });
     if (package == null) {
@@ -458,8 +464,8 @@
 
   /// Creates a [SolveResult] from the decisions in [_solution].
   Future<SolveResult> _result() async {
-    var decisions = _solution.decisions.toList();
-    var pubspecs = <String, Pubspec>{};
+    final decisions = _solution.decisions.toList();
+    final pubspecs = <String, Pubspec>{};
     for (var id in decisions) {
       if (id.isRoot) {
         pubspecs[id.name] = _rootPackages[id.name]!.pubspec;
@@ -470,6 +476,7 @@
 
     return SolveResult(
       _root,
+      _overriddenPackages,
       _lockFile,
       decisions,
       pubspecs,
@@ -490,7 +497,7 @@
   Future<Map<String, List<Version>>> _getAvailableVersions(
     List<PackageId> packages,
   ) async {
-    var availableVersions = <String, List<Version>>{};
+    final availableVersions = <String, List<Version>>{};
     for (var package in packages) {
       // If the version list was never requested, use versions from cached
       // version listings if the package is "hosted".
@@ -517,12 +524,13 @@
 
   /// Returns the package lister for [package], creating it if necessary.
   PackageLister _packageLister(PackageRange package) {
-    var ref = package.toRef();
+    final ref = package.toRef();
     return _packageListers.putIfAbsent(ref, () {
       if (ref.isRoot) {
         return PackageLister.root(
           _rootPackages[ref.name]!,
           _systemCache,
+          overriddenPackages: _overriddenPackages,
           sdkOverrides: _sdkOverrides,
         );
       }
@@ -531,12 +539,12 @@
       if (locked != null && locked.toRef() != ref) locked = null;
 
       final overridden = <String>{
-        ..._dependencyOverrides.keys,
+        ..._overriddenPackages,
         // If the package is overridden, ignore its dependencies back onto the
         // root package.
-        if (_dependencyOverrides.containsKey(package.name)) ...[
+        if (_overriddenPackages.contains(package.name)) ...[
           _root.name,
-          ..._root.workspaceChildren.map((e) => e.name),
+          ..._root.transitiveWorkspace.map((e) => e.name),
         ],
       };
 
@@ -568,7 +576,7 @@
     // non-hosted packages, since they don't support multiple versions and thus
     // can't be downgraded.
     if (_type == SolveType.downgrade) {
-      var locked = _lockFile.packages[package];
+      final locked = _lockFile.packages[package];
       if (locked != null && !locked.source.hasMultipleVersions) return locked;
     }
 
@@ -584,7 +592,7 @@
   /// `pubspec.lock` or pinned in `dependency_overrides`.
   Version? _getAllowedRetracted(String? package) {
     if (_dependencyOverrides.containsKey(package)) {
-      var range = _dependencyOverrides[package]!;
+      final range = _dependencyOverrides[package]!;
       if (range.constraint is Version) {
         // We have a pinned dependency.
         return range.constraint as Version?;
diff --git a/lib/src/source/cached.dart b/lib/src/source/cached.dart
index 478ecee..a83f96d 100644
--- a/lib/src/source/cached.dart
+++ b/lib/src/source/cached.dart
@@ -27,7 +27,7 @@
   /// Otherwise, defers to the subclass.
   @override
   Future<Pubspec> doDescribe(PackageId id, SystemCache cache) async {
-    var packageDir = getDirectoryInCache(id, cache);
+    final packageDir = getDirectoryInCache(id, cache);
     if (fileExists(p.join(packageDir, 'pubspec.yaml'))) {
       return Pubspec.load(
         packageDir,
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 7f863aa..917eb75 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -101,7 +101,7 @@
           'key.');
     }
 
-    var ref = description['ref'];
+    final ref = description['ref'];
     if (ref is! String?) {
       throw FormatException("The 'ref' field of the description must be a "
           'string.');
@@ -235,8 +235,8 @@
     );
     return await _pool.withResource(() async {
       await _ensureRepoCache(description, cache);
-      var path = _repoCachePath(description, cache);
-      var revision = await _firstRevision(path, description.ref);
+      final path = _repoCachePath(description, cache);
+      final revision = await _firstRevision(path, description.ref);
       final resolvedDescription = ResolvedGitDescription(description, revision);
 
       return Pubspec.parse(
@@ -295,9 +295,9 @@
     }
     return await _pool.withResource(() async {
       await _ensureRepoCache(description, cache);
-      var path = _repoCachePath(description, cache);
-      var revision = await _firstRevision(path, description.ref);
-      var pubspec = await _describeUncached(ref, revision, cache);
+      final path = _repoCachePath(description, cache);
+      final revision = await _firstRevision(path, description.ref);
+      final pubspec = await _describeUncached(ref, revision, cache);
 
       return [
         PackageId(
@@ -386,7 +386,7 @@
 
       didUpdate |= await _ensureRevision(description, resolvedRef, cache);
 
-      var revisionCachePath = _revisionCachePath(id, cache);
+      final revisionCachePath = _revisionCachePath(id, cache);
       final path = description.path;
       await _revisionCacheClones.putIfAbsent(revisionCachePath, () async {
         if (!entryExists(revisionCachePath)) {
@@ -433,7 +433,7 @@
 
     final result = <RepairResult>[];
 
-    var packages = listDir(rootDir)
+    final packages = listDir(rootDir)
         .where((entry) => dirExists(p.join(entry, '.git')))
         .expand((revisionCachePath) {
           return _readPackageList(revisionCachePath).map((relative) {
@@ -441,12 +441,15 @@
             // repository, ignore it.
             if (!dirExists(revisionCachePath)) return null;
 
-            var packageDir = p.join(revisionCachePath, relative);
+            final packageDir = p.join(revisionCachePath, relative);
             try {
-              return Package.load(packageDir, cache.sources);
+              return Package.load(
+                packageDir,
+                loadPubspec: Pubspec.loadRootWithSources(cache.sources),
+              );
             } catch (error, stackTrace) {
               log.error('Failed to load package', error, stackTrace);
-              var name = p.basename(revisionCachePath).split('-').first;
+              final name = p.basename(revisionCachePath).split('-').first;
               result.add(
                 RepairResult(name, Version.none, this, success: false),
               );
@@ -505,7 +508,7 @@
     String revision,
     SystemCache cache,
   ) async {
-    var path = _repoCachePath(description, cache);
+    final path = _repoCachePath(description, cache);
     if (_updatedRepos.contains(path)) return false;
 
     await _deleteGitRepoIfInvalid(path);
@@ -531,7 +534,7 @@
     GitDescription description,
     SystemCache cache,
   ) async {
-    var path = _repoCachePath(description, cache);
+    final path = _repoCachePath(description, cache);
     if (_updatedRepos.contains(path)) return false;
 
     await _deleteGitRepoIfInvalid(path);
@@ -551,7 +554,7 @@
     GitDescription description,
     SystemCache cache,
   ) async {
-    var path = _repoCachePath(description, cache);
+    final path = _repoCachePath(description, cache);
     assert(!_updatedRepos.contains(path));
     try {
       await _cloneViaTemp(description.url, path, cache, mirror: true);
@@ -572,7 +575,7 @@
     GitDescription description,
     SystemCache cache,
   ) async {
-    var path = _repoCachePath(description, cache);
+    final path = _repoCachePath(description, cache);
     if (_updatedRepos.contains(path)) return false;
     await git.run([_gitDirArg(path), 'fetch'], workingDir: path);
     _updatedRepos.add(path);
@@ -611,7 +614,7 @@
   ///
   /// Returns `true` if it had to update anything.
   bool _updatePackageList(String revisionCachePath, String path) {
-    var packages = _readPackageList(revisionCachePath);
+    final packages = _readPackageList(revisionCachePath);
     if (packages.contains(path)) return false;
 
     _writePackageList(revisionCachePath, packages..add(path));
@@ -620,7 +623,7 @@
 
   /// Returns the list of packages in [revisionCachePath].
   List<String> _readPackageList(String revisionCachePath) {
-    var path = _packageListPath(revisionCachePath);
+    final path = _packageListPath(revisionCachePath);
 
     // If there's no package list file, this cache was created by an older
     // version of pub where pubspecs were only allowed at the root of the
@@ -679,7 +682,7 @@
     // Git on Windows does not seem to automatically create the destination
     // directory.
     ensureDir(to);
-    var args = ['clone', if (mirror) '--mirror', from, to];
+    final args = ['clone', if (mirror) '--mirror', from, to];
 
     await git.run(args);
   }
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index b51baa9..693a4e9 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -396,7 +396,7 @@
       if (pubspecData is! Map) {
         throw FormatException('pubspec must be a map');
       }
-      var pubspec = Pubspec.fromMap(
+      final pubspec = Pubspec.fromMap(
         pubspecData,
         cache.sources,
         expectedName: ref.name,
@@ -647,7 +647,7 @@
         throw FormatException('summary must be a String');
       }
 
-      var aliasIDs = <String>[];
+      final aliasIDs = <String>[];
       final aliases = advisory['aliases'];
       if (aliases is! List) {
         throw FormatException('aliases must be a list');
@@ -828,7 +828,7 @@
             // Too old according to internal timestamp - delete.
             tryDeleteEntry(cachePath);
           } else {
-            var res = _versionInfoFromPackageListing(
+            final res = _versionInfoFromPackageListing(
               cachedDoc,
               ref,
               Uri.file(cachePath),
@@ -1213,7 +1213,7 @@
     }
     final rootDir = cache.rootDirForSource(this);
 
-    var dir = _urlToDirectory(description.url);
+    final dir = _urlToDirectory(description.url);
     return p.join(rootDir, dir, '${id.name}-${id.version}');
   }
 
@@ -1229,7 +1229,7 @@
     }
     final rootDir = cache.rootDir;
 
-    var serverDir = _urlToDirectory(description.url);
+    final serverDir = _urlToDirectory(description.url);
     return p.join(
       rootDir,
       'hosted-hashes',
@@ -1274,10 +1274,15 @@
         }
 
         final results = <RepairResult>[];
-        var packages = <Package>[];
+        final packages = <Package>[];
         for (var entry in listDir(serverDir)) {
           try {
-            packages.add(Package.load(entry, cache.sources));
+            packages.add(
+              Package.load(
+                entry,
+                loadPubspec: Pubspec.loadRootWithSources(cache.sources),
+              ),
+            );
           } catch (error, stackTrace) {
             log.error('Failed to load package', error, stackTrace);
             final id = _idForBasename(
@@ -1305,7 +1310,7 @@
           ..addAll(
             await Future.wait(
               packages.map((package) async {
-                var id = PackageId(
+                final id = PackageId(
                   package.name,
                   package.version,
                   ResolvedHostedDescription(
@@ -1343,7 +1348,7 @@
   /// Returns the best-guess package ID for [basename], which should be a
   /// subdirectory in a hosted cache.
   PackageId _idForBasename(String basename, String url) {
-    var components = split1(basename, '-');
+    final components = split1(basename, '-');
     var version = Version.none;
     if (components.length > 1) {
       try {
@@ -1361,7 +1366,7 @@
   }
 
   bool _looksLikePackageDir(String path) {
-    var components = split1(p.basename(path), '-');
+    final components = split1(p.basename(path), '-');
     if (components.length < 2) return false;
     try {
       Version.parse(components.last);
@@ -1376,7 +1381,7 @@
   @override
   List<Package> getCachedPackages(SystemCache cache) {
     final root = cache.rootDirForSource(HostedSource.instance);
-    var cacheDir =
+    final cacheDir =
         p.join(root, _urlToDirectory(HostedSource.instance.defaultUrl));
     if (!dirExists(cacheDir)) return [];
 
@@ -1384,7 +1389,10 @@
         .where(_looksLikePackageDir)
         .map((entry) {
           try {
-            return Package.load(entry, cache.sources);
+            return Package.load(
+              entry,
+              loadPubspec: Pubspec.loadRootWithSources(cache.sources),
+            );
           } catch (error, stackTrace) {
             log.fine('Failed to load package from $entry:\n'
                 '$error\n'
@@ -1447,8 +1455,8 @@
 
     // Download and extract the archive to a temp directory.
     return await withTempDir((tempDirForArchive) async {
-      var fileName = '$packageName-$version.tar.gz';
-      var archivePath = p.join(tempDirForArchive, fileName);
+      final fileName = '$packageName-$version.tar.gz';
+      final archivePath = p.join(tempDirForArchive, fileName);
 
       Stream<List<int>> validateSha256(
         Stream<List<int>> stream,
@@ -1522,7 +1530,7 @@
         _throwFriendlyError(error, stackTrace, id.name, description.url);
       }
 
-      var tempDir = cache.createTempDir();
+      final tempDir = cache.createTempDir();
       try {
         try {
           await extractTarGz(readBinaryFileAsStream(archivePath), tempDir);
@@ -1568,7 +1576,7 @@
     // of the cache.
     late final Uint8List contentHash;
 
-    var tempDir = cache.createTempDir();
+    final tempDir = cache.createTempDir();
     final PackageId id;
     try {
       try {
@@ -1877,8 +1885,9 @@
     // Don't include the scheme for HTTPS URLs. This makes the directory names
     // nice for the default and most recommended scheme. We also don't include
     // it for localhost URLs, since they're always known to be HTTP.
-    var localhost = match[2] == null ? '' : 'localhost';
-    var scheme = match[1] == 'https://' || localhost.isNotEmpty ? '' : match[1];
+    final localhost = match[2] == null ? '' : 'localhost';
+    final scheme =
+        match[1] == 'https://' || localhost.isNotEmpty ? '' : match[1];
     return '$scheme$localhost';
   });
   return replace(
@@ -1897,9 +1906,9 @@
 /// "https" for all others.
 String _directoryToUrl(String directory) {
   // Decode the pseudo-URL-encoded characters.
-  var chars = '<>:"\\/|?*%';
+  const chars = '<>:"\\/|?*%';
   for (var i = 0; i < chars.length; i++) {
-    var c = chars.substring(i, i + 1);
+    final c = chars.substring(i, i + 1);
     directory = directory.replaceAll('%${c.codeUnitAt(0)}', c);
   }
 
@@ -1909,7 +1918,7 @@
   }
 
   // Otherwise, default to http for localhost and https for everything else.
-  var scheme =
+  final scheme =
       isLoopback(directory.replaceAll(RegExp(':.*'), '')) ? 'http' : 'https';
   return Uri.parse('$scheme://$directory').toString();
 }
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index e0451ef..3e2cc7b 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -69,10 +69,10 @@
     if (description is! String) {
       throw FormatException('The description must be a path string.');
     }
-    var dir = description;
+    final dir = description;
     // Resolve the path relative to the containing file path, and remember
     // whether the original path was relative or absolute.
-    var isRelative = p.isRelative(dir);
+    final isRelative = p.isRelative(dir);
 
     if (containingDescription is PathDescription) {
       return PackageRef(
@@ -199,8 +199,8 @@
     }
     // There's only one package ID for a given path. We just need to find the
     // version.
-    var pubspec = _loadPubspec(ref, cache);
-    var id = PackageId(
+    final pubspec = _loadPubspec(ref, cache);
+    final id = PackageId(
       ref.name,
       pubspec.version,
       ResolvedPathDescription(description),
@@ -219,7 +219,7 @@
     if (description is! PathDescription) {
       throw ArgumentError('Wrong source');
     }
-    var dir = _validatePath(ref.name, description);
+    final dir = _validatePath(ref.name, description);
     return Pubspec.load(
       dir,
       cache.sources,
diff --git a/lib/src/source/sdk.dart b/lib/src/source/sdk.dart
index 24cb658..aa78e86 100644
--- a/lib/src/source/sdk.dart
+++ b/lib/src/source/sdk.dart
@@ -67,8 +67,8 @@
     if (description is! SdkDescription) {
       throw ArgumentError('Wrong source');
     }
-    var pubspec = _loadPubspec(ref, cache);
-    var id = PackageId(
+    final pubspec = _loadPubspec(ref, cache);
+    final id = PackageId(
       ref.name,
       pubspec.version,
       ResolvedSdkDescription(description),
@@ -90,7 +90,7 @@
   /// Throws a [PackageNotFoundException] if [ref]'s SDK is unavailable or
   /// doesn't contain the package.
   Pubspec _loadPubspec(PackageRef ref, SystemCache cache) {
-    var pubspec = Pubspec.load(
+    final pubspec = Pubspec.load(
       _verifiedPackagePath(ref),
       cache.sources,
       expectedName: ref.name,
@@ -99,7 +99,7 @@
 
     /// Validate that there are no non-sdk dependencies if the SDK does not
     /// allow them.
-    if (ref.description case SdkDescription description) {
+    if (ref.description case final SdkDescription description) {
       if (sdks[description.sdk]
           case Sdk(allowsNonSdkDepsInSdkPackages: false)) {
         for (var dep in pubspec.dependencies.entries) {
@@ -126,8 +126,8 @@
     if (description is! SdkDescription) {
       throw ArgumentError('Wrong source');
     }
-    var sdkName = description.sdk;
-    var sdk = sdks[sdkName];
+    final sdkName = description.sdk;
+    final sdk = sdks[sdkName];
     if (sdk == null) {
       throw PackageNotFoundException('unknown SDK "$sdkName"');
     } else if (!sdk.isAvailable) {
@@ -137,7 +137,7 @@
       );
     }
 
-    var path = sdk.packagePath(ref.name);
+    final path = sdk.packagePath(ref.name);
     if (path != null) return path;
 
     throw PackageNotFoundException(
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index e20a60b..468a18a 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -111,20 +111,11 @@
   ///
   /// Throws an [ArgumentError] if [id] has an invalid source.
   Package load(PackageId id) {
-    return Package.load(getDirectory(id), sources, expectedName: id.name);
-  }
-
-  Package loadCached(PackageId id) {
-    final source = id.description.description.source;
-    if (source is CachedSource) {
-      return Package.load(
-        source.getDirectoryInCache(id, this),
-        sources,
-        expectedName: id.name,
-      );
-    } else {
-      throw ArgumentError('Call only on Cached ids.');
-    }
+    return Package.load(
+      getDirectory(id),
+      loadPubspec: Pubspec.loadRootWithSources(sources),
+      expectedName: id.name,
+    );
   }
 
   /// Create a new temporary directory within the system cache.
@@ -134,7 +125,7 @@
   /// system temp directory to ensure that it's on the same volume as the pub
   /// system cache so that it can move the directory from it.
   String createTempDir() {
-    var temp = ensureDir(tempDir);
+    final temp = ensureDir(tempDir);
     return io.createTempDir(temp, 'dir');
   }
 
@@ -156,7 +147,7 @@
   /// Throws a [DataException] if the pubspec's version doesn't match [id]'s
   /// version.
   Future<Pubspec> describe(PackageId id) async {
-    var pubspec = cachedPubspecs[id] ??= await id.source.doDescribe(id, this);
+    final pubspec = cachedPubspecs[id] ??= await id.source.doDescribe(id, this);
     if (pubspec.version != id.version) {
       throw PackageNotFoundException(
         'the pubspec for $id has version ${pubspec.version}',
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 2912b5f..758f3e4 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -132,7 +132,7 @@
   Future<T> Function() callback, {
   bool captureStackChains = false,
 }) {
-  var completer = Completer<T>();
+  final completer = Completer<T>();
   void wrappedCallback() {
     Future.sync(callback)
         .then(completer.complete)
@@ -259,7 +259,7 @@
 
 /// Returns a list containing the sorted elements of [iter].
 List<T> ordered<T extends Comparable<T>>(Iterable<T> iter) {
-  var list = iter.toList();
+  final list = iter.toList();
   list.sort();
   return list;
 }
@@ -271,7 +271,7 @@
 /// and only if that path's basename is in [files].
 Set<String> createFileFilter(Iterable<String> files) {
   return files.expand<String>((file) {
-    var result = ['/$file'];
+    final result = ['/$file'];
     if (Platform.isWindows) result.add('\\$file');
     return result;
   }).toSet();
@@ -284,7 +284,7 @@
 /// and only if one of that path's components is in [dirs].
 Set<String> createDirectoryFilter(Iterable<String> dirs) {
   return dirs.expand<String>((dir) {
-    var result = ['/$dir/'];
+    final result = ['/$dir/'];
     if (Platform.isWindows) {
       result
         ..add('/$dir\\')
@@ -316,7 +316,7 @@
 ) async {
   int? minIndex;
   T? minOrderBy;
-  var valuesList = values.toList();
+  final valuesList = values.toList();
   final orderByResults = await Future.wait(values.map(orderBy));
   for (var i = 0; i < orderByResults.length; i++) {
     final elementOrderBy = orderByResults[i];
@@ -361,7 +361,7 @@
 /// Replace each instance of [matcher] in [source] with the return value of
 /// [fn].
 String replace(String source, Pattern matcher, String Function(Match) fn) {
-  var buffer = StringBuffer();
+  final buffer = StringBuffer();
   var start = 0;
   for (var match in matcher.allMatches(source)) {
     buffer.write(source.substring(start, match.start));
@@ -395,7 +395,7 @@
 List<String> split1(String toSplit, String pattern) {
   if (toSplit.isEmpty) return <String>[];
 
-  var index = toSplit.indexOf(pattern);
+  final index = toSplit.indexOf(pattern);
   if (index == -1) return [toSplit];
   return [
     toSplit.substring(0, index),
@@ -406,12 +406,12 @@
 /// Convert a URL query string (or `application/x-www-form-urlencoded` body)
 /// into a [Map] from parameter names to values.
 Map<String, String> queryToMap(String queryList) {
-  var map = <String, String>{};
+  final map = <String, String>{};
   for (var pair in queryList.split('&')) {
-    var split = split1(pair, '=');
+    final split = split1(pair, '=');
     if (split.isEmpty) continue;
-    var key = _urlDecode(split[0]);
-    var value = split.length > 1 ? _urlDecode(split[1]) : '';
+    final key = _urlDecode(split[0]);
+    final value = split.length > 1 ? _urlDecode(split[1]) : '';
     map[key] = value;
   }
   return map;
@@ -419,15 +419,15 @@
 
 /// Returns a human-friendly representation of [duration].
 String niceDuration(Duration duration) {
-  var hasMinutes = duration.inMinutes > 0;
-  var result = hasMinutes ? '${duration.inMinutes}:' : '';
+  final hasMinutes = duration.inMinutes > 0;
+  final result = hasMinutes ? '${duration.inMinutes}:' : '';
 
-  var s = duration.inSeconds % 60;
-  var ms = duration.inMilliseconds % 1000;
+  final s = duration.inSeconds % 60;
+  final ms = duration.inMilliseconds % 1000;
 
   // If we're using verbose logging, be more verbose but more accurate when
   // reporting timing information.
-  var msString = log.verbosity.isLevelVisible(log.Level.fine)
+  final msString = log.verbosity.isLevelVisible(log.Level.fine)
       ? _padLeft(ms.toString(), 3, '0')
       : (ms ~/ 100).toString();
 
@@ -498,7 +498,7 @@
     return lines.map((line) => '$prefix$line').join('\n');
   }
 
-  var firstLine = '$firstPrefix${lines.first}';
+  final firstLine = '$firstPrefix${lines.first}';
   lines = lines.skip(1).map((line) => '$prefix$line').toList();
   lines.insert(0, firstLine);
   return lines.join('\n');
@@ -513,7 +513,7 @@
 /// Converts [data], which is a parsed YAML object, to a pretty-printed string,
 /// using indentation for maps.
 String yamlToString(Object? data) {
-  var buffer = StringBuffer();
+  final buffer = StringBuffer();
 
   void stringify(bool isMapValue, String indent, Object? data) {
     // TODO(nweiz): Serialize using the YAML library once it supports
@@ -527,7 +527,7 @@
       }
 
       // Sort the keys. This minimizes deltas in diffs.
-      var keys = data.keys.toList();
+      final keys = data.keys.toList();
       keys.sort((a, b) => a.toString().compareTo(b.toString()));
 
       var first = true;
@@ -589,14 +589,14 @@
 ///
 /// If [bytes] is not provided, it is generated using `Random.secure`.
 String createUuid([List<int>? bytes]) {
-  var rnd = math.Random.secure();
+  final rnd = math.Random.secure();
 
   // See http://www.cryptosys.net/pki/uuid-rfc4122.html for notes
   bytes ??= List<int>.generate(16, (_) => rnd.nextInt(256));
   bytes[6] = (bytes[6] & 0x0F) | 0x40;
   bytes[8] = (bytes[8] & 0x3f) | 0x80;
 
-  var chars = bytes
+  final chars = bytes
       .map((b) => b.toRadixString(16).padLeft(2, '0'))
       .join()
       .toUpperCase();
@@ -620,11 +620,11 @@
   }
 
   return text.split('\n').map((originalLine) {
-    var buffer = StringBuffer();
+    final buffer = StringBuffer();
     var lengthSoFar = 0;
     var firstLine = true;
     for (var word in originalLine.split(' ')) {
-      var wordLength = _withoutColors(word).length;
+      final wordLength = _withoutColors(word).length;
       if (wordLength > lineLength) {
         if (lengthSoFar != 0) buffer.writeln();
         if (!firstLine) buffer.write(prefix);
@@ -826,7 +826,7 @@
   /// not have a value of type [T].
   List<T> expectElements<T extends Object?>() => [
         for (var node in nodes)
-          if (node.value case T value)
+          if (node.value case final T value)
             value
           else
             throw SourceSpanApplicationException(
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index 38b4024..c4fcdbc 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -92,7 +92,7 @@
       firstSdkVersion = firstSdkVersion.nextPatch;
     }
 
-    var allowedSdks = VersionRange(
+    final allowedSdks = VersionRange(
       min: firstSdkVersion,
       includeMin: true,
       max: firstSdkVersion.isPreRelease
@@ -138,7 +138,7 @@
     required List<String> warnings,
     required List<String> errors,
   }) async {
-    var validators = [
+    final validators = [
       FileCaseValidator(),
       AnalyzeValidator(),
       GitignoreValidator(),
diff --git a/lib/src/validator/changelog.dart b/lib/src/validator/changelog.dart
index 65d7fdc..129c1fd 100644
--- a/lib/src/validator/changelog.dart
+++ b/lib/src/validator/changelog.dart
@@ -32,7 +32,7 @@
           'See https://dart.dev/tools/pub/publishing#important-files.');
     }
 
-    var bytes = readBinaryFile(changelog);
+    final bytes = readBinaryFile(changelog);
     String contents;
 
     try {
diff --git a/lib/src/validator/compiled_dartdoc.dart b/lib/src/validator/compiled_dartdoc.dart
index 27f1ebc..e74628e 100644
--- a/lib/src/validator/compiled_dartdoc.dart
+++ b/lib/src/validator/compiled_dartdoc.dart
@@ -16,10 +16,10 @@
     return Future.sync(() {
       for (var entry in files) {
         if (p.basename(entry) != 'nav.json') continue;
-        var dir = p.dirname(entry);
+        final dir = p.dirname(entry);
 
         // Look for tell-tale Dartdoc output files all in the same directory.
-        var files = [
+        final files = [
           entry,
           p.join(dir, 'index.html'),
           p.join(dir, 'styles.css'),
diff --git a/lib/src/validator/dependency.dart b/lib/src/validator/dependency.dart
index f6134e2..7423324 100644
--- a/lib/src/validator/dependency.dart
+++ b/lib/src/validator/dependency.dart
@@ -25,8 +25,8 @@
     /// Emit an error for dependencies from unknown SDKs or without appropriate
     /// constraints on the Dart SDK.
     void warnAboutSdkSource(PackageRange dep) {
-      var identifier = (dep.description as SdkDescription).sdk;
-      var sdk = sdks[identifier];
+      final identifier = (dep.description as SdkDescription).sdk;
+      final sdk = sdks[identifier];
       if (sdk == null) {
         errors.add('Unknown SDK "$identifier" for dependency "${dep.name}".');
         return;
@@ -37,7 +37,7 @@
     Future warnAboutSource(PackageRange dep) async {
       List<Version> versions;
       try {
-        var ids = await cache.getVersions(cache.hosted.refFor(dep.name));
+        final ids = await cache.getVersions(cache.hosted.refFor(dep.name));
         versions = ids.map((id) => id.version).toList();
       } on ApplicationException catch (_) {
         versions = [];
@@ -54,7 +54,7 @@
       }
 
       // Path sources are errors. Other sources are just warnings.
-      var messages = dep.source is PathSource ? errors : warnings;
+      final messages = dep.source is PathSource ? errors : warnings;
 
       messages.add('Don\'t depend on "${dep.name}" from the ${dep.source} '
           'source. Use the hosted source instead. For example:\n'
@@ -87,7 +87,7 @@
     void warnAboutNoConstraint(PackageRange dep) {
       var message = 'Your dependency on "${dep.name}" should have a version '
           'constraint.';
-      var locked = context.entrypoint.lockFile.packages[dep.name];
+      final locked = context.entrypoint.lockFile.packages[dep.name];
       if (locked != null) {
         message = '$message For example:\n'
             '\n'
@@ -117,7 +117,7 @@
     void warnAboutNoConstraintLowerBound(PackageRange dep) {
       var message =
           'Your dependency on "${dep.name}" should have a lower bound.';
-      var locked = context.entrypoint.lockFile.packages[dep.name];
+      final locked = context.entrypoint.lockFile.packages[dep.name];
       if (locked != null) {
         String constraint;
         if (locked.version == (dep.constraint as VersionRange).max) {
@@ -175,7 +175,7 @@
     /// Validates all dependencies in [dependencies].
     Future validateDependencies(Iterable<PackageRange> dependencies) async {
       for (var dependency in dependencies) {
-        var constraint = dependency.constraint;
+        final constraint = dependency.constraint;
         if (dependency.name == 'flutter') {
           warnAboutFlutterSdk(dependency);
         } else if (dependency.source is SdkSource) {
diff --git a/lib/src/validator/dependency_override.dart b/lib/src/validator/dependency_override.dart
index 1b875ab..edefa8c 100644
--- a/lib/src/validator/dependency_override.dart
+++ b/lib/src/validator/dependency_override.dart
@@ -13,8 +13,9 @@
 class DependencyOverrideValidator extends Validator {
   @override
   Future validate() {
-    var overridden = MapKeySet(package.dependencyOverrides);
-    var dev = MapKeySet(package.devDependencies);
+    final overridden =
+        MapKeySet(context.entrypoint.workspaceRoot.allOverridesInWorkspace);
+    final dev = MapKeySet(package.devDependencies);
     if (overridden.difference(dev).isNotEmpty) {
       final overridesFile = package.pubspec.dependencyOverridesFromOverridesFile
           ? package.pubspecOverridesPath
diff --git a/lib/src/validator/directory.dart b/lib/src/validator/directory.dart
index 63c3929..71f5598 100644
--- a/lib/src/validator/directory.dart
+++ b/lib/src/validator/directory.dart
@@ -36,7 +36,7 @@
       final dirName = p.basename(dir);
       if (_pluralNames.contains(dirName)) {
         // Cut off the "s"
-        var singularName = dirName.substring(0, dirName.length - 1);
+        final singularName = dirName.substring(0, dirName.length - 1);
         warnings.add('Rename the top-level "$dirName" directory to '
             '"$singularName".\n'
             'The Pub layout convention is to use singular directory '
diff --git a/lib/src/validator/executable.dart b/lib/src/validator/executable.dart
index ff178c8..2f29cc2 100644
--- a/lib/src/validator/executable.dart
+++ b/lib/src/validator/executable.dart
@@ -17,7 +17,7 @@
         filesBeneath('bin', recursive: false).map(package.relative);
 
     package.pubspec.executables.forEach((executable, script) {
-      var scriptPath = p.join('bin', '$script.dart');
+      final scriptPath = p.join('bin', '$script.dart');
       if (binFiles.contains(scriptPath)) return;
 
       warnings.add('Your pubspec.yaml lists an executable "$executable" that '
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index 6afb25d..ff82e67 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -60,7 +60,7 @@
       final unignoredByGitignore = Ignore.listFiles(
         beneath: beneath,
         listDir: (dir) {
-          var contents = Directory(resolve(dir)).listSync();
+          final contents = Directory(resolve(dir)).listSync();
           return contents
               .where((e) => !(linkExists(e.path) && dirExists(e.path)))
               .map(
diff --git a/lib/src/validator/name.dart b/lib/src/validator/name.dart
index e0b7fee..20c2a80 100644
--- a/lib/src/validator/name.dart
+++ b/lib/src/validator/name.dart
@@ -17,10 +17,10 @@
     return Future.sync(() {
       _checkName(package.name);
 
-      var libraries = _libraries(files);
+      final libraries = _libraries(files);
 
       if (libraries.length == 1) {
-        var libName = p.basenameWithoutExtension(libraries[0]);
+        final libName = p.basenameWithoutExtension(libraries[0]);
         if (libName == package.name) return;
         warnings.add('The name of "${libraries[0]}", "$libName", should match '
             'the name of the package, "${package.name}".\n'
@@ -32,7 +32,7 @@
   /// Returns a list of all libraries in the current package as paths relative
   /// to the package's root directory.
   List<String> _libraries(List<String> files) {
-    var libDir = package.path('lib');
+    final libDir = package.path('lib');
     return filesBeneath('lib', recursive: true)
         .map((file) => p.relative(file, from: p.dirname(libDir)))
         .where(
@@ -63,7 +63,7 @@
   }
 
   String _unCamelCase(String source) {
-    var builder = StringBuffer();
+    final builder = StringBuffer();
     var lastMatchEnd = 0;
     for (var match in RegExp(r'[a-z]([A-Z])').allMatches(source)) {
       builder
diff --git a/lib/src/validator/pubspec_field.dart b/lib/src/validator/pubspec_field.dart
index 385fbd5..1bab65d 100644
--- a/lib/src/validator/pubspec_field.dart
+++ b/lib/src/validator/pubspec_field.dart
@@ -40,7 +40,7 @@
 
   /// Adds an error if [field] doesn't exist or isn't a string.
   void _validateFieldIsString(String field) {
-    var value = package.pubspec.fields[field];
+    final value = package.pubspec.fields[field];
     if (value == null) {
       errors.add('Your pubspec.yaml is missing a "$field" field.');
     } else if (value is! String) {
@@ -51,7 +51,7 @@
 
   /// Adds an error if the URL for [field] is invalid.
   void _validateFieldUrl(String field) {
-    var url = package.pubspec.fields[field];
+    final url = package.pubspec.fields[field];
     if (url == null) return;
 
     if (url is! String) {
@@ -60,7 +60,7 @@
       return;
     }
 
-    var goodScheme = RegExp(r'^https?:');
+    final goodScheme = RegExp(r'^https?:');
     if (!goodScheme.hasMatch(url)) {
       errors.add('Your pubspec.yaml\'s "$field" field must be an "http:" or '
           '"https:" URL, but it was "$url".');
diff --git a/lib/src/validator/readme.dart b/lib/src/validator/readme.dart
index 3604232..fbcc7c5 100644
--- a/lib/src/validator/readme.dart
+++ b/lib/src/validator/readme.dart
@@ -42,7 +42,7 @@
           'See https://dart.dev/tools/pub/publishing#important-files.');
     }
 
-    var bytes = readBinaryFile(readme);
+    final bytes = readBinaryFile(readme);
     try {
       // utf8.decode doesn't allow invalid UTF-8.
       utf8.decode(bytes);
diff --git a/lib/src/validator/size.dart b/lib/src/validator/size.dart
index fac5e16..b25a842 100644
--- a/lib/src/validator/size.dart
+++ b/lib/src/validator/size.dart
@@ -15,11 +15,11 @@
   @override
   Future<void> validate() async {
     if (packageSize <= _maxSize) return;
-    var sizeInMb = (packageSize / (1 << 20)).toStringAsPrecision(4);
+    final sizeInMb = (packageSize / (1 << 20)).toStringAsPrecision(4);
     // Current implementation of Package.listFiles skips hidden files
-    var ignoreExists = fileExists(package.path('.gitignore'));
+    final ignoreExists = fileExists(package.path('.gitignore'));
 
-    var hint = StringBuffer('''
+    final hint = StringBuffer('''
 Your package is $sizeInMb MB.
 
 Consider the impact large downloads can have on the package consumer.''');
diff --git a/lib/src/validator/strict_dependencies.dart b/lib/src/validator/strict_dependencies.dart
index ea0e36f..0bf3385 100644
--- a/lib/src/validator/strict_dependencies.dart
+++ b/lib/src/validator/strict_dependencies.dart
@@ -28,7 +28,7 @@
 
     for (var file in files) {
       List<UriBasedDirective> directives;
-      var contents = readTextFile(file);
+      final contents = readTextFile(file);
       try {
         directives = analysisContextManager.parseImportsAndExports(file);
       } on AnalyzerErrorGroup catch (e, s) {
@@ -63,8 +63,8 @@
 
   @override
   Future validate() async {
-    var dependencies = package.dependencies.keys.toSet()..add(package.name);
-    var devDependencies = MapKeySet(package.devDependencies);
+    final dependencies = package.dependencies.keys.toSet()..add(package.name);
+    final devDependencies = MapKeySet(package.devDependencies);
     _validateLibBin(dependencies, devDependencies);
     _validateBenchmarkTestTool(dependencies, devDependencies);
   }
@@ -89,7 +89,7 @@
   /// Validates that no Dart files in `benchmark/`, `test/` or
   /// `tool/` have dependencies that aren't in [deps] or [devDeps].
   void _validateBenchmarkTestTool(Set<String> deps, Set<String> devDeps) {
-    var directories = ['benchmark', 'test', 'tool'];
+    final directories = ['benchmark', 'test', 'tool'];
     for (var usage in _usagesBeneath(directories)) {
       if (!deps.contains(usage.package) && !devDeps.contains(usage.package)) {
         warnings.add(usage.dependenciesMissingMessage());
@@ -161,7 +161,7 @@
 
   /// Returns an error message saying the package should be in `dependencies`.
   String dependencyMisplaceMessage() {
-    var shortFile = p.split(p.relative(_file)).first;
+    final shortFile = p.split(p.relative(_file)).first;
     return _toMessage(
         '$package is in the `dev_dependencies` section of `pubspec.yaml`. '
         'Packages used in $shortFile/ must be declared in the `dependencies` '
diff --git a/pubspec.yaml b/pubspec.yaml
index 4a3f3b8..d6d87ab 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -29,7 +29,7 @@
 
 dev_dependencies:
   checks: ^0.3.0
-  lints: ^3.0.0
+  lints: ^4.0.0
   shelf_test_handler: ^2.0.2
   test: ^1.24.9
   test_descriptor: ^2.0.1
diff --git a/test/ascii_tree_test.dart b/test/ascii_tree_test.dart
index 115cbc6..018875b 100644
--- a/test/ascii_tree_test.dart
+++ b/test/ascii_tree_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:pub/src/ascii_tree.dart' as tree;
 import 'package:pub/src/package.dart';
+import 'package:pub/src/pubspec.dart';
 import 'package:pub/src/utils.dart';
 import 'package:test/test.dart';
 
@@ -61,9 +62,11 @@
         file('path.dart', bytes(100)),
       ]),
     ]).create();
-    var files =
-        Package.load(path(appPath), (name) => throw UnimplementedError())
-            .listFiles();
+    final files = Package.load(
+      path(appPath),
+      loadPubspec:
+          Pubspec.loadRootWithSources((name) => throw UnimplementedError()),
+    ).listFiles();
     ctx.expectNextSection(
       tree.fromFiles(files, baseDir: path(appPath), showFileSizes: true),
     );
@@ -73,7 +76,7 @@
   });
 
   testWithGolden('tree.fromMap a complex example', colors: true, (ctx) {
-    var map = {
+    final map = {
       '.gitignore': <String, Map>{},
       'README.md': <String, Map>{},
       'TODO': <String, Map>{},
diff --git a/test/cache/repair/git_test.dart b/test/cache/repair/git_test.dart
index 04176ab..cadfcf8 100644
--- a/test/cache/repair/git_test.dart
+++ b/test/cache/repair/git_test.dart
@@ -36,8 +36,8 @@
 
     test('reinstalls previously cached git packages', () async {
       // Find the cached foo packages for each revision.
-      var gitCacheDir = p.join(d.sandbox, cachePath, 'git');
-      var fooDirs = listDir(gitCacheDir)
+      final gitCacheDir = p.join(d.sandbox, cachePath, 'git');
+      final fooDirs = listDir(gitCacheDir)
           .where((dir) => p.basename(dir).startsWith('foo-'))
           .toList();
 
@@ -56,8 +56,8 @@
       );
 
       // The missing libraries should have been replaced.
-      var fooLibs = fooDirs.map((dir) {
-        var fooDirName = p.basename(dir);
+      final fooLibs = fooDirs.map((dir) {
+        final fooDirName = p.basename(dir);
         return d.dir(fooDirName, [
           d.dir('lib', [d.file('foo.dart', 'main() => "foo";')]),
         ]);
@@ -67,8 +67,8 @@
     });
 
     test('deletes packages without pubspecs', () async {
-      var gitCacheDir = p.join(d.sandbox, cachePath, 'git');
-      var fooDirs = listDir(gitCacheDir)
+      final gitCacheDir = p.join(d.sandbox, cachePath, 'git');
+      final fooDirs = listDir(gitCacheDir)
           .where((dir) => p.basename(dir).startsWith('foo-'))
           .toList();
 
@@ -97,8 +97,8 @@
     });
 
     test('deletes packages with invalid pubspecs', () async {
-      var gitCacheDir = p.join(d.sandbox, cachePath, 'git');
-      var fooDirs = listDir(gitCacheDir)
+      final gitCacheDir = p.join(d.sandbox, cachePath, 'git');
+      final fooDirs = listDir(gitCacheDir)
           .where((dir) => p.basename(dir).startsWith('foo-'))
           .toList();
 
@@ -152,8 +152,8 @@
 
     test('reinstalls previously cached git packages', () async {
       // Find the cached foo packages for each revision.
-      var gitCacheDir = p.join(d.sandbox, cachePath, 'git');
-      var fooDirs = listDir(gitCacheDir)
+      final gitCacheDir = p.join(d.sandbox, cachePath, 'git');
+      final fooDirs = listDir(gitCacheDir)
           .where((dir) => p.basename(dir).startsWith('foo-'))
           .toList();
 
@@ -172,8 +172,8 @@
       );
 
       // The missing libraries should have been replaced.
-      var fooLibs = fooDirs.map((dir) {
-        var fooDirName = p.basename(dir);
+      final fooLibs = fooDirs.map((dir) {
+        final fooDirName = p.basename(dir);
         return d.dir(fooDirName, [
           d.dir('subdir', [
             d.dir('lib', [d.file('sub.dart', 'main() => "sub";')]),
@@ -185,8 +185,8 @@
     });
 
     test('deletes packages without pubspecs', () async {
-      var gitCacheDir = p.join(d.sandbox, cachePath, 'git');
-      var fooDirs = listDir(gitCacheDir)
+      final gitCacheDir = p.join(d.sandbox, cachePath, 'git');
+      final fooDirs = listDir(gitCacheDir)
           .where((dir) => p.basename(dir).startsWith('foo-'))
           .toList();
 
diff --git a/test/cache/repair/handles_failure_test.dart b/test/cache/repair/handles_failure_test.dart
index d59fab7..14847ee 100644
--- a/test/cache/repair/handles_failure_test.dart
+++ b/test/cache/repair/handles_failure_test.dart
@@ -36,7 +36,7 @@
     ]).create();
 
     // Repair them.
-    var pub = await startPub(args: ['cache', 'repair']);
+    final pub = await startPub(args: ['cache', 'repair']);
 
     expect(pub.stderr, emits(startsWith('Failed to repair foo 1.2.4. Error:')));
     expect(
diff --git a/test/cache/repair/recompiles_snapshots_test.dart b/test/cache/repair/recompiles_snapshots_test.dart
index 46588a3..167675c 100644
--- a/test/cache/repair/recompiles_snapshots_test.dart
+++ b/test/cache/repair/recompiles_snapshots_test.dart
@@ -38,7 +38,7 @@
           Reactivated 1 package.''',
     );
 
-    var pub = await pubRun(global: true, args: ['foo:script']);
+    final pub = await pubRun(global: true, args: ['foo:script']);
     expect(pub.stdout, emits('ok'));
     await pub.shouldExit();
   });
diff --git a/test/dependency_override_test.dart b/test/dependency_override_test.dart
index 8c60017..7c3d8e1 100644
--- a/test/dependency_override_test.dart
+++ b/test/dependency_override_test.dart
@@ -121,7 +121,7 @@
         }),
       ]).create();
 
-      var bazPath = p.join('..', 'baz');
+      final bazPath = p.join('..', 'baz');
 
       await runPub(
         args: [command.name],
diff --git a/test/descriptor.dart b/test/descriptor.dart
index 7749e92..7984f40 100644
--- a/test/descriptor.dart
+++ b/test/descriptor.dart
@@ -92,7 +92,7 @@
 /// Describes a file named `pubspec.yaml` for an application package with the
 /// given [dependencies].
 Descriptor appPubspec({Map? dependencies, Map<String, Object>? extras}) {
-  var map = <String, Object>{
+  final map = <String, Object>{
     'name': 'myapp',
     ...?extras,
   };
@@ -115,7 +115,7 @@
   Map<String, Object?>? extras,
   bool resolutionWorkspace = false,
 }) {
-  var map = packageMap(name, version, deps, devDeps);
+  final map = packageMap(name, version, deps, devDeps);
   if (resolutionWorkspace && sdk == null) {
     sdk = '3.5.0';
   }
@@ -197,11 +197,11 @@
   int? port,
   bool includePubspecs = false,
 }) {
-  var contents = <Descriptor>[];
+  final contents = <Descriptor>[];
   packages.forEach((name, versions) {
     if (versions is! List) versions = [versions];
     for (var version in versions) {
-      var packageContents = [libDir(name, '$name $version')];
+      final packageContents = [libDir(name, '$name $version')];
       if (includePubspecs) {
         packageContents.add(libPubspec(name, version as String));
       }
diff --git a/test/descriptor/git.dart b/test/descriptor/git.dart
index 0b35423..addde73 100644
--- a/test/descriptor/git.dart
+++ b/test/descriptor/git.dart
@@ -45,7 +45,7 @@
   ///
   /// [parent] defaults to [sandbox].
   Future<String> revParse(String ref, [String? parent]) async {
-    var output = await _runGit(['rev-parse', ref], parent);
+    final output = await _runGit(['rev-parse', ref], parent);
     return output[0];
   }
 
@@ -57,7 +57,7 @@
   Future<List<String>> _runGit(List<String> args, String? parent) {
     // Explicitly specify the committer information. Git needs this to commit
     // and we don't want to rely on the buildbots having this already set up.
-    var environment = {
+    final environment = {
       'GIT_AUTHOR_NAME': 'Pub Test',
       'GIT_AUTHOR_EMAIL': 'pub@dartlang.org',
       'GIT_COMMITTER_NAME': 'Pub Test',
diff --git a/test/descriptor/tar.dart b/test/descriptor/tar.dart
index a02f97d..f8570e5 100644
--- a/test/descriptor/tar.dart
+++ b/test/descriptor/tar.dart
@@ -25,16 +25,16 @@
     return withTempDir((tempDir) async {
       await Future.wait(contents.map((entry) => entry.create(tempDir)));
 
-      var createdContents = listDir(
+      final createdContents = listDir(
         tempDir,
         recursive: true,
         includeHidden: true,
         includeDirs: false,
       );
-      var bytes =
+      final bytes =
           await createTarGz(createdContents, baseDir: tempDir).toBytes();
 
-      var file = p.join(parent ?? sandbox, name);
+      final file = p.join(parent ?? sandbox, name);
       _writeBinaryFile(file, bytes);
       return file;
     });
diff --git a/test/descriptor/yaml.dart b/test/descriptor/yaml.dart
index 6afb9d0..a9b4d77 100644
--- a/test/descriptor/yaml.dart
+++ b/test/descriptor/yaml.dart
@@ -28,12 +28,12 @@
 
   @override
   Future validate([String? parent]) async {
-    var fullPath = p.join(parent ?? sandbox, name);
+    final fullPath = p.join(parent ?? sandbox, name);
     if (!await File(fullPath).exists()) {
       fail("File not found: '$fullPath'.");
     }
 
-    var bytes = await File(fullPath).readAsBytes();
+    final bytes = await File(fullPath).readAsBytes();
 
     final actualContentsText = utf8.decode(bytes);
     final actual = loadYaml(actualContentsText);
diff --git a/test/downgrade/doesnt_change_git_dependencies_test.dart b/test/downgrade/doesnt_change_git_dependencies_test.dart
index 33ab4ed..9d832d8 100644
--- a/test/downgrade/doesnt_change_git_dependencies_test.dart
+++ b/test/downgrade/doesnt_change_git_dependencies_test.dart
@@ -24,7 +24,7 @@
 
     await pubGet();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     await d.git(
       'foo.git',
diff --git a/test/downgrade/tighten_test.dart b/test/downgrade/tighten_test.dart
new file mode 100644
index 0000000..7505b83
--- /dev/null
+++ b/test/downgrade/tighten_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2024, 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('--tighten will set lower bounds to the actually achieved version',
+      () async {
+    await servePackages()
+      ..serve(
+        'foo',
+        '1.0.0',
+      ) // Because of the bar constraint, this is not achievable.
+      ..serve('foo', '2.0.0')
+      ..serve('foo', '3.0.0')
+      ..serve('bar', '1.0.0', deps: {'foo': '>=2.0.0'});
+
+    await d.appDir(dependencies: {'foo': '>=1.0.0', 'bar': '^1.0.0'}).create();
+
+    await pubGet(output: contains('foo 3.0.0'));
+    await pubDowngrade(
+      args: ['--tighten'],
+      output: allOf(
+        contains('< foo 2.0.0 (was 3.0.0)'),
+        contains('foo: >=1.0.0 -> >=2.0.0'),
+      ),
+    );
+  });
+}
diff --git a/test/embedding/ensure_pubspec_resolved.dart b/test/embedding/ensure_pubspec_resolved.dart
index b025a76..75d4d2d 100644
--- a/test/embedding/ensure_pubspec_resolved.dart
+++ b/test/embedding/ensure_pubspec_resolved.dart
@@ -542,11 +542,11 @@
   expect(output, isNot(contains('Resolving dependencies')));
   // If pub determines that everything is up-to-date, it should set the
   // mtimes to indicate that.
-  var pubspecModified =
+  final pubspecModified =
       File(p.join(d.sandbox, 'myapp/pubspec.yaml')).lastModifiedSync();
-  var lockFileModified =
+  final lockFileModified =
       File(p.join(d.sandbox, 'myapp/pubspec.lock')).lastModifiedSync();
-  var packageConfigModified =
+  final packageConfigModified =
       File(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json'))
           .lastModifiedSync();
 
diff --git a/test/error_group_test.dart b/test/error_group_test.dart
index ec673bb..92ee749 100644
--- a/test/error_group_test.dart
+++ b/test/error_group_test.dart
@@ -241,7 +241,7 @@
     });
 
     test('should pass through values from the stream', () {
-      var iter = StreamIterator(stream);
+      final iter = StreamIterator(stream);
       iter.moveNext().then((hasNext) {
         expect(hasNext, isTrue);
         expect(iter.current, equals(1));
@@ -407,7 +407,7 @@
     test(
         "shouldn't throw a top-level exception if a stream receives an error "
         'after the other listened stream completes', () {
-      var signal = Completer<void>();
+      final signal = Completer<void>();
       expect(
         stream1.toList().whenComplete(signal.complete),
         completion(equals(['value1', 'value2'])),
@@ -429,7 +429,7 @@
     test(
         "shouldn't throw a top-level exception if an error is signaled after "
         'one listened stream completes', () {
-      var signal = Completer<void>();
+      final signal = Completer<void>();
       expect(
         stream1.toList().whenComplete(signal.complete),
         completion(equals(['value1', 'value2'])),
@@ -511,7 +511,7 @@
     test(
         "shouldn't throw a top-level exception if the future receives an "
         'error after the listened stream completes', () {
-      var signal = Completer<void>();
+      final signal = Completer<void>();
       expect(
         stream.toList().whenComplete(signal.complete),
         completion(equals(['value1', 'value2'])),
diff --git a/test/get/git/check_out_and_upgrade_test.dart b/test/get/git/check_out_and_upgrade_test.dart
index c654e2b..32a320b 100644
--- a/test/get/git/check_out_and_upgrade_test.dart
+++ b/test/get/git/check_out_and_upgrade_test.dart
@@ -40,7 +40,7 @@
       ]),
     ]).validate();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     await d.git(
       'foo.git',
diff --git a/test/get/git/check_out_branch_test.dart b/test/get/git/check_out_branch_test.dart
index 62d5706..b98ac8e 100644
--- a/test/get/git/check_out_branch_test.dart
+++ b/test/get/git/check_out_branch_test.dart
@@ -11,7 +11,7 @@
   test('checks out a package at a specific branch from Git', () async {
     ensureGit();
 
-    var repo = d.git(
+    final repo = d.git(
       'foo.git',
       [d.libDir('foo', 'foo 1'), d.libPubspec('foo', '1.0.0')],
     );
diff --git a/test/get/git/check_out_revision_test.dart b/test/get/git/check_out_revision_test.dart
index 61646cd..591f257 100644
--- a/test/get/git/check_out_revision_test.dart
+++ b/test/get/git/check_out_revision_test.dart
@@ -11,12 +11,12 @@
   test('checks out a package at a specific revision from Git', () async {
     ensureGit();
 
-    var repo = d.git(
+    final repo = d.git(
       'foo.git',
       [d.libDir('foo', 'foo 1'), d.libPubspec('foo', '1.0.0')],
     );
     await repo.create();
-    var commit = await repo.revParse('HEAD');
+    final commit = await repo.revParse('HEAD');
 
     await d.git(
       'foo.git',
diff --git a/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart b/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
index 7e56b86..30bc0fd 100644
--- a/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
+++ b/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
@@ -32,7 +32,7 @@
 
     await pubGet();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     // Switch to a new cache.
     renameInSandbox(cachePath, '$cachePath.old');
@@ -46,7 +46,7 @@
     await pubUpgrade(output: contains('Changed 1 dependency!'));
 
     // Switch back to the old cache.
-    var cacheDir = p.join(d.sandbox, cachePath);
+    final cacheDir = p.join(d.sandbox, cachePath);
     deleteEntry(cacheDir);
     renameInSandbox('$cachePath.old', cacheDir);
 
diff --git a/test/get/git/clean_invalid_git_repo_cache_test.dart b/test/get/git/clean_invalid_git_repo_cache_test.dart
index a6aa58e..42a7a3c 100644
--- a/test/get/git/clean_invalid_git_repo_cache_test.dart
+++ b/test/get/git/clean_invalid_git_repo_cache_test.dart
@@ -54,7 +54,7 @@
   test('Clean-up invalid git repo cache at a specific branch', () async {
     ensureGit();
 
-    var repo =
+    final repo =
         d.git('foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]);
     await repo.create();
     await repo.runGit(['branch', 'old']);
@@ -84,10 +84,10 @@
   test('Clean-up invalid git repo cache at a specific commit', () async {
     ensureGit();
 
-    var repo =
+    final repo =
         d.git('foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]);
     await repo.create();
-    var commit = await repo.revParse('HEAD');
+    final commit = await repo.revParse('HEAD');
 
     await d.appDir(
       dependencies: {
diff --git a/test/get/git/doesnt_fetch_if_nothing_changes_test.dart b/test/get/git/doesnt_fetch_if_nothing_changes_test.dart
index f2212e4..a258027 100644
--- a/test/get/git/doesnt_fetch_if_nothing_changes_test.dart
+++ b/test/get/git/doesnt_fetch_if_nothing_changes_test.dart
@@ -28,7 +28,7 @@
 
     await pubGet();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     // Delete the repo. This will cause "pub get" to fail if it tries to
     // re-fetch.
diff --git a/test/get/git/lock_version_test.dart b/test/get/git/lock_version_test.dart
index 003e080..3fa063e 100644
--- a/test/get/git/lock_version_test.dart
+++ b/test/get/git/lock_version_test.dart
@@ -36,7 +36,7 @@
       ]),
     ]).validate();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     // Delete the package spec to simulate a new checkout of the application.
     deleteEntry(p.join(d.sandbox, packageConfigFilePath));
diff --git a/test/get/git/locked_revision_without_repo_test.dart b/test/get/git/locked_revision_without_repo_test.dart
index 6fe9ef1..b6b2503 100644
--- a/test/get/git/locked_revision_without_repo_test.dart
+++ b/test/get/git/locked_revision_without_repo_test.dart
@@ -38,7 +38,7 @@
       ]),
     ]).validate();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     // Delete the package spec and the cache to simulate a brand new checkout
     // of the application.
diff --git a/test/get/git/path_test.dart b/test/get/git/path_test.dart
index eae19ed..957bdeb 100644
--- a/test/get/git/path_test.dart
+++ b/test/get/git/path_test.dart
@@ -17,7 +17,7 @@
   test('depends on a package in a subdirectory', () async {
     ensureGit();
 
-    var repo = d.git('foo.git', [
+    final repo = d.git('foo.git', [
       d.dir('subdir', [d.libPubspec('sub', '1.0.0'), d.libDir('sub', '1.0.0')]),
     ]);
     await repo.create();
@@ -52,7 +52,7 @@
   test('depends on a package in a deep subdirectory', () async {
     ensureGit();
 
-    var repo = d.git('foo.git', [
+    final repo = d.git('foo.git', [
       d.dir('sub', [
         d.dir('dir%', [d.libPubspec('sub', '1.0.0'), d.libDir('sub', '1.0.0')]),
       ]),
@@ -192,7 +192,7 @@
       () async {
     ensureGit();
 
-    var repo = d.git('foo.git', [
+    final repo = d.git('foo.git', [
       d.dir('sub', [
         d.dir('dir%', [d.libPubspec('sub', '1.0.0'), d.libDir('sub', '1.0.0')]),
       ]),
@@ -246,7 +246,7 @@
   test('depends on multiple packages in subdirectories', () async {
     ensureGit();
 
-    var repo = d.git('foo.git', [
+    final repo = d.git('foo.git', [
       d.dir(
         'subdir1',
         [d.libPubspec('sub1', '1.0.0'), d.libDir('sub1', '1.0.0')],
@@ -297,14 +297,14 @@
       () async {
     ensureGit();
 
-    var repo = d.git('foo.git', [
+    final repo = d.git('foo.git', [
       d.dir(
         'subdir',
         [d.libPubspec('sub1', '1.0.0'), d.libDir('sub1', '1.0.0')],
       ),
     ]);
     await repo.create();
-    var oldRevision = await repo.revParse('HEAD');
+    final oldRevision = await repo.revParse('HEAD');
 
     deleteEntry(p.join(d.sandbox, 'foo.git', 'subdir'));
 
@@ -314,7 +314,7 @@
         [d.libPubspec('sub2', '1.0.0'), d.libDir('sub2', '1.0.0')],
       ),
     ]).commit();
-    var newRevision = await repo.revParse('HEAD');
+    final newRevision = await repo.revParse('HEAD');
 
     await d.appDir(
       dependencies: {
diff --git a/test/get/git/stay_locked_if_compatible_test.dart b/test/get/git/stay_locked_if_compatible_test.dart
index 3f52179..6bde0c2 100644
--- a/test/get/git/stay_locked_if_compatible_test.dart
+++ b/test/get/git/stay_locked_if_compatible_test.dart
@@ -26,7 +26,7 @@
 
     await pubGet();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     await d.git(
       'foo.git',
diff --git a/test/get/git/unlock_if_incompatible_test.dart b/test/get/git/unlock_if_incompatible_test.dart
index 56a779c..5bdbccd 100644
--- a/test/get/git/unlock_if_incompatible_test.dart
+++ b/test/get/git/unlock_if_incompatible_test.dart
@@ -35,7 +35,7 @@
       ]),
     ]).validate();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     await d.git(
       'foo.git',
diff --git a/test/get/hosted/get_test.dart b/test/get/hosted/get_test.dart
index 60d7e24..c45371e 100644
--- a/test/get/hosted/get_test.dart
+++ b/test/get/hosted/get_test.dart
@@ -102,7 +102,7 @@
     // be accessed.
     (await servePackages()).serveErrors();
 
-    var server = await startPackageServer();
+    final server = await startPackageServer();
     server.serve('foo', '1.2.3');
 
     await d.appDir(
@@ -124,7 +124,7 @@
 
   test('recognizes and retries a package with a CRC32C checksum mismatch',
       () async {
-    var server = await startPackageServer();
+    final server = await startPackageServer();
 
     server.serve(
       'foo',
@@ -312,7 +312,7 @@
 
       await pubGet();
 
-      var packages = loadYaml(
+      final packages = loadYaml(
         readTextFile(p.join(d.sandbox, appPath, 'pubspec.lock')),
       )['packages'];
       expect(
@@ -353,7 +353,7 @@
 
       await pubGet();
 
-      var packages = loadYaml(
+      final packages = loadYaml(
         readTextFile(p.join(d.sandbox, appPath, 'pubspec.lock')),
       )['packages'];
       expect(
diff --git a/test/get/path/absolute_symlink_test.dart b/test/get/path/absolute_symlink_test.dart
index ba42da3..1f373ea 100644
--- a/test/get/path/absolute_symlink_test.dart
+++ b/test/get/path/absolute_symlink_test.dart
@@ -15,7 +15,7 @@
     await d
         .dir('foo', [d.libDir('foo'), d.libPubspec('foo', '0.0.1')]).create();
 
-    var fooPath = d.path('foo');
+    final fooPath = d.path('foo');
     await d.dir(appPath, [
       d.appPubspec(
         dependencies: {
diff --git a/test/get/path/no_pubspec_test.dart b/test/get/path/no_pubspec_test.dart
index 3649907..076db44 100644
--- a/test/get/path/no_pubspec_test.dart
+++ b/test/get/path/no_pubspec_test.dart
@@ -13,7 +13,7 @@
   test('path dependency to non-package directory', () async {
     // Make an empty directory.
     await d.dir('foo').create();
-    var fooPath = p.join(d.sandbox, 'foo');
+    final fooPath = p.join(d.sandbox, 'foo');
 
     await d.dir(appPath, [
       d.appPubspec(
diff --git a/test/get/path/nonexistent_dir_test.dart b/test/get/path/nonexistent_dir_test.dart
index 1990a1a..df942dd 100644
--- a/test/get/path/nonexistent_dir_test.dart
+++ b/test/get/path/nonexistent_dir_test.dart
@@ -11,7 +11,7 @@
 
 void main() {
   test('path dependency to non-existent directory', () async {
-    var badPath = p.join(d.sandbox, 'bad_path');
+    final badPath = p.join(d.sandbox, 'bad_path');
 
     await d.dir(appPath, [
       d.appPubspec(
diff --git a/test/get/path/path_is_file_test.dart b/test/get/path/path_is_file_test.dart
index 88eaceb..8fbae78 100644
--- a/test/get/path/path_is_file_test.dart
+++ b/test/get/path/path_is_file_test.dart
@@ -15,7 +15,7 @@
         .dir('foo', [d.libDir('foo'), d.libPubspec('foo', '0.0.1')]).create();
 
     await d.file('dummy.txt', '').create();
-    var dummyPath = p.join(d.sandbox, 'dummy.txt');
+    final dummyPath = p.join(d.sandbox, 'dummy.txt');
 
     await d.dir(appPath, [
       d.appPubspec(
diff --git a/test/get/path/relative_path_test.dart b/test/get/path/relative_path_test.dart
index 4b379f9..1e46aa3 100644
--- a/test/get/path/relative_path_test.dart
+++ b/test/get/path/relative_path_test.dart
@@ -115,15 +115,15 @@
 
     await pubGet();
 
-    var lockfilePath = p.join(d.sandbox, appPath, 'pubspec.lock');
+    final lockfilePath = p.join(d.sandbox, appPath, 'pubspec.lock');
     final lockfileJson = loadYaml(File(lockfilePath).readAsStringSync());
     expect(
       lockfileJson['packages']['foo']['description']['path'],
       '../foo',
       reason: 'Should use `/` as separator on all platforms',
     );
-    var lockfile = LockFile.load(lockfilePath, SystemCache().sources);
-    var description =
+    final lockfile = LockFile.load(lockfilePath, SystemCache().sources);
+    final description =
         lockfile.packages['foo']!.description.description as PathDescription;
 
     expect(description.relative, isTrue);
diff --git a/test/global/activate/activate_git_after_hosted_test.dart b/test/global/activate/activate_git_after_hosted_test.dart
index 567f45d..aa4689b 100644
--- a/test/global/activate/activate_git_after_hosted_test.dart
+++ b/test/global/activate/activate_git_after_hosted_test.dart
@@ -43,7 +43,7 @@
     );
 
     // Should now run the git one.
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emits('git'));
     await pub.shouldExit();
   });
diff --git a/test/global/activate/activate_hosted_after_git_test.dart b/test/global/activate/activate_hosted_after_git_test.dart
index ea71b68..fcdff96 100644
--- a/test/global/activate/activate_hosted_after_git_test.dart
+++ b/test/global/activate/activate_hosted_after_git_test.dart
@@ -38,7 +38,7 @@
     );
 
     // Should now run the hosted one.
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emits('hosted'));
     await pub.shouldExit();
   });
diff --git a/test/global/activate/activate_hosted_after_path_test.dart b/test/global/activate/activate_hosted_after_path_test.dart
index 8aef7be..8fe4a49 100644
--- a/test/global/activate/activate_hosted_after_path_test.dart
+++ b/test/global/activate/activate_hosted_after_path_test.dart
@@ -27,7 +27,7 @@
 
     await runPub(args: ['global', 'activate', '-spath', '../foo']);
 
-    var path = canonicalize(p.join(d.sandbox, 'foo'));
+    final path = canonicalize(p.join(d.sandbox, 'foo'));
     await runPub(
       args: ['global', 'activate', 'foo'],
       output: '''
@@ -41,7 +41,7 @@
     );
 
     // Should now run the hosted one.
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emits('hosted'));
     await pub.shouldExit();
   });
diff --git a/test/global/activate/activate_hosted_twice_test.dart b/test/global/activate/activate_hosted_twice_test.dart
index ce6cbc1..2d5eb9c 100644
--- a/test/global/activate/activate_hosted_twice_test.dart
+++ b/test/global/activate/activate_hosted_twice_test.dart
@@ -44,7 +44,7 @@
 Activated foo 1.0.0.''',
     );
 
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emits('bar 1.0.0'));
     await pub.shouldExit();
 
@@ -70,7 +70,7 @@
 Activated foo 1.0.0.''',
     );
 
-    var pub2 = await pubRun(global: true, args: ['foo']);
+    final pub2 = await pubRun(global: true, args: ['foo']);
     expect(pub2.stdout, emits('bar 2.0.0'));
     await pub2.shouldExit();
   });
diff --git a/test/global/activate/activate_path_after_hosted_test.dart b/test/global/activate/activate_path_after_hosted_test.dart
index b6d95ca..ad0effe 100644
--- a/test/global/activate/activate_path_after_hosted_test.dart
+++ b/test/global/activate/activate_path_after_hosted_test.dart
@@ -27,7 +27,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var path = canonicalize(p.join(d.sandbox, 'foo'));
+    final path = canonicalize(p.join(d.sandbox, 'foo'));
     await runPub(
       args: ['global', 'activate', '-spath', '../foo'],
       output: allOf([
@@ -37,7 +37,7 @@
     );
 
     // Should now run the path one.
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emitsThrough('path'));
     await pub.shouldExit();
   });
diff --git a/test/global/activate/installs_dependencies_for_path_test.dart b/test/global/activate/installs_dependencies_for_path_test.dart
index 7c9f11e..556b6d2 100644
--- a/test/global/activate/installs_dependencies_for_path_test.dart
+++ b/test/global/activate/installs_dependencies_for_path_test.dart
@@ -18,7 +18,8 @@
       d.dir('bin', [d.file('foo.dart', "main() => print('ok');")]),
     ]).create();
 
-    var pub = await startPub(args: ['global', 'activate', '-spath', '../foo']);
+    final pub =
+        await startPub(args: ['global', 'activate', '-spath', '../foo']);
     expect(pub.stdout, emitsThrough('Resolving dependencies in `../foo`...'));
     expect(pub.stdout, emitsThrough(startsWith('Activated foo 0.0.0 at path')));
     await pub.shouldExit();
diff --git a/test/global/activate/path_package_test.dart b/test/global/activate/path_package_test.dart
index 0ec71d0..29162bd 100644
--- a/test/global/activate/path_package_test.dart
+++ b/test/global/activate/path_package_test.dart
@@ -16,7 +16,7 @@
       d.dir('bin', [d.file('foo.dart', "main() => print('ok');")]),
     ]).create();
 
-    var path = canonicalize(p.join(d.sandbox, 'foo'));
+    final path = canonicalize(p.join(d.sandbox, 'foo'));
     await runPub(
       args: ['global', 'activate', '--source', 'path', '../foo'],
       output: endsWith('Activated foo 1.0.0 at path "$path".'),
@@ -48,7 +48,7 @@
       d.dir('lib', [d.file('bar.dart', "final value = 'ok';")]),
     ]).create();
 
-    var path = canonicalize(p.join(d.sandbox, 'foo'));
+    final path = canonicalize(p.join(d.sandbox, 'foo'));
     await runPub(
       args: ['global', 'activate', '--source', 'path', '../foo'],
       output: endsWith('Activated foo 1.0.0 at path "$path".'),
diff --git a/test/global/binstubs/binstub_runs_executable_test.dart b/test/global/binstubs/binstub_runs_executable_test.dart
index 3e0fc91..9e36701 100644
--- a/test/global/binstubs/binstub_runs_executable_test.dart
+++ b/test/global/binstubs/binstub_runs_executable_test.dart
@@ -29,7 +29,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var process = await TestProcess.start(
+    final process = await TestProcess.start(
       p.join(d.sandbox, cachePath, 'bin', binStubName('foo-script')),
       ['arg1', 'arg2'],
       environment: getEnvironment(),
@@ -54,7 +54,7 @@
 
     await runPub(args: ['global', 'activate', '-spath', '../foo']);
 
-    var process = await TestProcess.start(
+    final process = await TestProcess.start(
       p.join(d.sandbox, cachePath, 'bin', binStubName('foo-script')),
       ['arg1', 'arg2'],
       environment: getEnvironment(),
diff --git a/test/global/binstubs/does_not_warn_if_on_path_test.dart b/test/global/binstubs/does_not_warn_if_on_path_test.dart
index f3575c3..dbe6654 100644
--- a/test/global/binstubs/does_not_warn_if_on_path_test.dart
+++ b/test/global/binstubs/does_not_warn_if_on_path_test.dart
@@ -28,9 +28,9 @@
     );
 
     // Add the test's cache bin directory to the path.
-    var binDir = p.dirname(Platform.executable);
-    var separator = Platform.isWindows ? ';' : ':';
-    var path = "${Platform.environment["PATH"]}$separator$binDir";
+    final binDir = p.dirname(Platform.executable);
+    final separator = Platform.isWindows ? ';' : ':';
+    final path = "${Platform.environment["PATH"]}$separator$binDir";
 
     await runPub(
       args: ['global', 'activate', 'foo'],
diff --git a/test/global/binstubs/missing_script_test.dart b/test/global/binstubs/missing_script_test.dart
index 9dfd978..61915dc 100644
--- a/test/global/binstubs/missing_script_test.dart
+++ b/test/global/binstubs/missing_script_test.dart
@@ -17,7 +17,8 @@
       }),
     ]).create();
 
-    var pub = await startPub(args: ['global', 'activate', '-spath', '../foo']);
+    final pub =
+        await startPub(args: ['global', 'activate', '-spath', '../foo']);
 
     expect(
       pub.stderr,
diff --git a/test/global/binstubs/name_collision_test.dart b/test/global/binstubs/name_collision_test.dart
index 1f811a7..7e1c33f 100644
--- a/test/global/binstubs/name_collision_test.dart
+++ b/test/global/binstubs/name_collision_test.dart
@@ -27,7 +27,8 @@
 
     await runPub(args: ['global', 'activate', '-spath', '../foo']);
 
-    var pub = await startPub(args: ['global', 'activate', '-spath', '../bar']);
+    final pub =
+        await startPub(args: ['global', 'activate', '-spath', '../bar']);
     expect(pub.stdout, emitsThrough('Installed executable bar.'));
     expect(
       pub.stderr,
diff --git a/test/global/binstubs/name_collision_with_overwrite_test.dart b/test/global/binstubs/name_collision_with_overwrite_test.dart
index 491256c..3588b2c 100644
--- a/test/global/binstubs/name_collision_with_overwrite_test.dart
+++ b/test/global/binstubs/name_collision_with_overwrite_test.dart
@@ -27,7 +27,7 @@
 
     await runPub(args: ['global', 'activate', '-spath', '../foo']);
 
-    var pub = await startPub(
+    final pub = await startPub(
       args: ['global', 'activate', '-spath', '../bar', '--overwrite'],
     );
     expect(
diff --git a/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart b/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart
index b86e42a..8325c16 100644
--- a/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart
+++ b/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart
@@ -95,7 +95,7 @@
       ]),
     ]).create();
 
-    var process = await TestProcess.start(
+    final process = await TestProcess.start(
       p.join(d.sandbox, cachePath, 'bin', binStubName('foo-script')),
       ['arg1', 'arg2'],
       environment: getEnvironment(),
diff --git a/test/global/binstubs/outdated_snapshot_test.dart b/test/global/binstubs/outdated_snapshot_test.dart
index a4471ac..4511a0a 100644
--- a/test/global/binstubs/outdated_snapshot_test.dart
+++ b/test/global/binstubs/outdated_snapshot_test.dart
@@ -51,7 +51,7 @@
       ),
     );
 
-    var process = await TestProcess.start(
+    final process = await TestProcess.start(
       p.join(d.sandbox, cachePath, 'bin', binStubName('foo-script')),
       ['arg1', 'arg2'],
       environment: getEnvironment(),
diff --git a/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart b/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart
index 79d57d2..777cc84 100644
--- a/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart
+++ b/test/global/binstubs/runs_once_even_when_dart_is_batch_test.dart
@@ -38,7 +38,7 @@
         ],
       ).create();
 
-      var process = await Process.run(
+      final process = await Process.run(
         p.join(d.sandbox, cachePath, 'bin', 'script.bat'),
         ['hi'],
         environment: {
diff --git a/test/global/binstubs/unknown_explicit_executable_test.dart b/test/global/binstubs/unknown_explicit_executable_test.dart
index 8f47678..b58dff5 100644
--- a/test/global/binstubs/unknown_explicit_executable_test.dart
+++ b/test/global/binstubs/unknown_explicit_executable_test.dart
@@ -18,7 +18,7 @@
       d.dir('bin', [d.file('one.dart', "main() => print('ok');")]),
     ]).create();
 
-    var pub = await startPub(
+    final pub = await startPub(
       args: [
         'global', 'activate', '--source', 'path', '../foo', //
         '-x', 'who', '-x', 'one', '--executable', 'wat',
diff --git a/test/global/binstubs/utils.dart b/test/global/binstubs/utils.dart
index 59a8df0..bbd44a7 100644
--- a/test/global/binstubs/utils.dart
+++ b/test/global/binstubs/utils.dart
@@ -15,13 +15,13 @@
 /// The `pub`/`pub.bat` command on the PATH will be the one in tool/test-bin not
 /// the one from the sdk.
 Map<String, String> getEnvironment() {
-  var binDir = p.dirname(Platform.resolvedExecutable);
-  var separator = Platform.isWindows ? ';' : ':';
-  var pubBin = p.absolute('tool', 'test-bin');
-  var path =
+  final binDir = p.dirname(Platform.resolvedExecutable);
+  final separator = Platform.isWindows ? ';' : ':';
+  final pubBin = p.absolute('tool', 'test-bin');
+  final path =
       "$pubBin$separator${Platform.environment["PATH"]}$separator$binDir";
 
-  var environment = getPubTestEnvironment();
+  final environment = getPubTestEnvironment();
   environment['PATH'] = path;
   return environment;
 }
diff --git a/test/global/deactivate/path_package_test.dart b/test/global/deactivate/path_package_test.dart
index 655f2cd..78eebc0 100644
--- a/test/global/deactivate/path_package_test.dart
+++ b/test/global/deactivate/path_package_test.dart
@@ -18,7 +18,7 @@
 
     await runPub(args: ['global', 'activate', '--source', 'path', '../foo']);
 
-    var path = canonicalize(p.join(d.sandbox, 'foo'));
+    final path = canonicalize(p.join(d.sandbox, 'foo'));
     await runPub(
       args: ['global', 'deactivate', 'foo'],
       output: 'Deactivated package foo 1.0.0 at path "$path".',
diff --git a/test/global/list_test.dart b/test/global/list_test.dart
index 6dca489..1f59243 100644
--- a/test/global/list_test.dart
+++ b/test/global/list_test.dart
@@ -44,7 +44,7 @@
 
     await runPub(args: ['global', 'activate', '-spath', '../foo']);
 
-    var path = canonicalize(p.join(d.sandbox, 'foo'));
+    final path = canonicalize(p.join(d.sandbox, 'foo'));
     await runPub(args: ['global', 'list'], output: 'foo 1.0.0 at path "$path"');
   });
 
diff --git a/test/global/run/implicit_executable_name_test.dart b/test/global/run/implicit_executable_name_test.dart
index 3230016..9689776 100644
--- a/test/global/run/implicit_executable_name_test.dart
+++ b/test/global/run/implicit_executable_name_test.dart
@@ -20,7 +20,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emits('foo'));
     await pub.shouldExit();
   });
diff --git a/test/global/run/missing_path_package_test.dart b/test/global/run/missing_path_package_test.dart
index dce42da..68aae22 100644
--- a/test/global/run/missing_path_package_test.dart
+++ b/test/global/run/missing_path_package_test.dart
@@ -20,7 +20,7 @@
 
     deleteEntry(p.join(d.sandbox, 'foo'));
 
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(
       pub.stderr,
       emits('The directory `${d.path('foo')}` does not exist.'),
diff --git a/test/global/run/nonexistent_script_test.dart b/test/global/run/nonexistent_script_test.dart
index 515dbfb..90e3fb0 100644
--- a/test/global/run/nonexistent_script_test.dart
+++ b/test/global/run/nonexistent_script_test.dart
@@ -21,7 +21,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var pub = await pubRun(global: true, args: ['foo:script']);
+    final pub = await pubRun(global: true, args: ['foo:script']);
     expect(
       pub.stderr,
       emits(
diff --git a/test/global/run/package_api_test.dart b/test/global/run/package_api_test.dart
index 2abb7f0..6d849c3 100644
--- a/test/global/run/package_api_test.dart
+++ b/test/global/run/package_api_test.dart
@@ -35,20 +35,20 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var pub = await pubRun(global: true, args: ['foo:script']);
+    final pub = await pubRun(global: true, args: ['foo:script']);
 
-    var packageConfigPath = p.join(
+    final packageConfigPath = p.join(
       d.sandbox,
       cachePath,
       'global_packages/foo/.dart_tool/package_config.json',
     );
     expect(pub.stdout, emits(p.toUri(packageConfigPath).toString()));
 
-    var fooResourcePath =
+    final fooResourcePath =
         p.join(globalServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(fooResourcePath).toString()));
 
-    var barResourcePath =
+    final barResourcePath =
         p.join(globalServer.pathInCache('bar', '1.0.0'), 'lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(barResourcePath).toString()));
     await pub.shouldExit(0);
@@ -81,16 +81,16 @@
 
     await runPub(args: ['global', 'activate', '-s', 'path', '.']);
 
-    var pub = await pubRun(global: true, args: ['myapp:script']);
+    final pub = await pubRun(global: true, args: ['myapp:script']);
 
-    var packageConfigPath =
+    final packageConfigPath =
         p.join(d.sandbox, 'myapp/.dart_tool/package_config.json');
     expect(pub.stdout, emitsThrough(p.toUri(packageConfigPath).toString()));
 
-    var myappResourcePath = p.join(d.sandbox, 'myapp/lib/resource.txt');
+    final myappResourcePath = p.join(d.sandbox, 'myapp/lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(myappResourcePath).toString()));
 
-    var fooResourcePath = p.join(d.sandbox, 'foo/lib/resource.txt');
+    final fooResourcePath = p.join(d.sandbox, 'foo/lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(fooResourcePath).toString()));
     await pub.shouldExit(0);
   });
diff --git a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
index d71fb10..f16d55a 100644
--- a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
+++ b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
@@ -41,7 +41,7 @@
         'script.dart-$versionSuffix.snapshot',
       ),
     );
-    var pub = await pubRun(global: true, args: ['foo:script']);
+    final pub = await pubRun(global: true, args: ['foo:script']);
     // In the real world this would just print "hello!", but since we collect
     // all output we see the precompilation messages as well.
     expect(pub.stdout, emits('Building package executable...'));
diff --git a/test/global/run/reflects_changes_to_local_package_test.dart b/test/global/run/reflects_changes_to_local_package_test.dart
index 0cc276a..476d7d9 100644
--- a/test/global/run/reflects_changes_to_local_package_test.dart
+++ b/test/global/run/reflects_changes_to_local_package_test.dart
@@ -18,7 +18,7 @@
 
     await d.file('foo/bin/foo.dart', "main() => print('changed');").create();
 
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emitsThrough('changed'));
     await pub.shouldExit();
   });
diff --git a/test/global/run/runs_git_script_test.dart b/test/global/run/runs_git_script_test.dart
index 8bdfef7..4ff4269 100644
--- a/test/global/run/runs_git_script_test.dart
+++ b/test/global/run/runs_git_script_test.dart
@@ -18,7 +18,7 @@
 
     await runPub(args: ['global', 'activate', '-sgit', '../foo.git']);
 
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emits('ok'));
     await pub.shouldExit();
   });
diff --git a/test/global/run/runs_path_script_test.dart b/test/global/run/runs_path_script_test.dart
index 29220f3..fdd6fe8 100644
--- a/test/global/run/runs_path_script_test.dart
+++ b/test/global/run/runs_path_script_test.dart
@@ -16,7 +16,7 @@
 
     await runPub(args: ['global', 'activate', '--source', 'path', '../foo']);
 
-    var pub = await pubRun(global: true, args: ['foo']);
+    final pub = await pubRun(global: true, args: ['foo']);
     expect(pub.stdout, emitsThrough('ok'));
     await pub.shouldExit();
   });
diff --git a/test/global/run/runs_script_in_checked_mode_test.dart b/test/global/run/runs_script_in_checked_mode_test.dart
index 10cb454..4d080c2 100644
--- a/test/global/run/runs_script_in_checked_mode_test.dart
+++ b/test/global/run/runs_script_in_checked_mode_test.dart
@@ -20,7 +20,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var pub =
+    final pub =
         await pubRun(global: true, args: ['--enable-asserts', 'foo:script']);
     expect(pub.stderr, emitsThrough(contains('Failed assertion')));
     await pub.shouldExit(255);
diff --git a/test/global/run/runs_script_in_unchecked_mode_test.dart b/test/global/run/runs_script_in_unchecked_mode_test.dart
index 0451a71..d781e3e 100644
--- a/test/global/run/runs_script_in_unchecked_mode_test.dart
+++ b/test/global/run/runs_script_in_unchecked_mode_test.dart
@@ -27,7 +27,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var pub = await pubRun(global: true, args: ['foo:script']);
+    final pub = await pubRun(global: true, args: ['foo:script']);
     expect(pub.stdout, emits('no checks'));
     await pub.shouldExit();
   });
diff --git a/test/global/run/runs_script_test.dart b/test/global/run/runs_script_test.dart
index c8c8e37..abd8d28 100644
--- a/test/global/run/runs_script_test.dart
+++ b/test/global/run/runs_script_test.dart
@@ -20,7 +20,7 @@
 
     await runPub(args: ['global', 'activate', 'foo']);
 
-    var pub = await pubRun(global: true, args: ['foo:script']);
+    final pub = await pubRun(global: true, args: ['foo:script']);
     expect(pub.stdout, emits('ok'));
     await pub.shouldExit();
   });
diff --git a/test/hosted/metadata_test.dart b/test/hosted/metadata_test.dart
index 1bc3cc0..736fe0d 100644
--- a/test/hosted/metadata_test.dart
+++ b/test/hosted/metadata_test.dart
@@ -87,7 +87,7 @@
     });
 
     test("doesn't send metadata headers to a foreign server", () async {
-      var server = await startPackageServer()
+      final server = await startPackageServer()
         ..serve('foo', '1.0.0');
 
       await d.appDir(
diff --git a/test/hosted/version_negotiation_test.dart b/test/hosted/version_negotiation_test.dart
index 858f1a3..fea2ef5 100644
--- a/test/hosted/version_negotiation_test.dart
+++ b/test/hosted/version_negotiation_test.dart
@@ -48,7 +48,7 @@
         },
       ).create();
 
-      var pub = await startPub(args: [command.name]);
+      final pub = await startPub(args: [command.name]);
 
       globalServer.expect(
         'GET',
diff --git a/test/ignore_test.dart b/test/ignore_test.dart
index dd09cd5..7627cd1 100644
--- a/test/ignore_test.dart
+++ b/test/ignore_test.dart
@@ -98,7 +98,7 @@
 
     for (final c in testData) {
       c.paths.forEach((path, expected) {
-        var ignoreCase = c.ignoreCase;
+        final ignoreCase = c.ignoreCase;
         if (ignoreCase == null) {
           testIgnorePath(c, path, expected, false);
           testIgnorePath(c, path, expected, true);
@@ -197,7 +197,7 @@
 
     for (final c in testData) {
       c.paths.forEach((path, expected) {
-        var ignoreCase = c.ignoreCase;
+        final ignoreCase = c.ignoreCase;
         if (ignoreCase == null) {
           testIgnorePath(c, path, expected, false);
           testIgnorePath(c, path, expected, true);
diff --git a/test/io_test.dart b/test/io_test.dart
index e206286..4877ebb 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -91,7 +91,7 @@
     test("doesn't ignore hidden files above the directory being listed", () {
       expect(
         withTempDir((temp) {
-          var dir = p.join(temp, '.foo', 'bar');
+          final dir = p.join(temp, '.foo', 'bar');
           ensureDir(dir);
           writeTextFile(p.join(dir, 'file1.txt'), '');
           writeTextFile(p.join(dir, 'file2.txt'), '');
@@ -115,7 +115,7 @@
     test('resolves a non-link', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var filePath = p.join(temp, 'file');
+          final filePath = p.join(temp, 'file');
           writeTextFile(filePath, '');
           expect(canonicalize(filePath), equals(filePath));
         }),
@@ -189,7 +189,7 @@
     test('resolves a single-level horizontally recursive symlink', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var linkPath = p.join(temp, 'foo');
+          final linkPath = p.join(temp, 'foo');
           createSymlink(linkPath, linkPath);
           expect(canonicalize(linkPath), equals(linkPath));
         }),
@@ -200,9 +200,9 @@
     test('resolves a multi-level horizontally recursive symlink', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var fooPath = p.join(temp, 'foo');
-          var barPath = p.join(temp, 'bar');
-          var bazPath = p.join(temp, 'baz');
+          final fooPath = p.join(temp, 'foo');
+          final barPath = p.join(temp, 'bar');
+          final bazPath = p.join(temp, 'baz');
           createSymlink(barPath, fooPath);
           createSymlink(bazPath, barPath);
           createSymlink(fooPath, bazPath);
@@ -233,10 +233,10 @@
     test('resolves multiple nested symlinks', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var dir1 = p.join(temp, 'dir1');
-          var dir2 = p.join(temp, 'dir2');
-          var subdir1 = p.join(dir1, 'subdir1');
-          var subdir2 = p.join(dir2, 'subdir2');
+          final dir1 = p.join(temp, 'dir1');
+          final dir2 = p.join(temp, 'dir2');
+          final subdir1 = p.join(dir1, 'subdir1');
+          final subdir2 = p.join(dir2, 'subdir2');
           _createDir(dir2);
           _createDir(subdir2);
           createSymlink(dir2, dir1);
@@ -253,9 +253,9 @@
     test('resolves a nested vertical symlink', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var dir1 = p.join(temp, 'dir1');
-          var dir2 = p.join(temp, 'dir2');
-          var subdir = p.join(dir1, 'subdir');
+          final dir1 = p.join(temp, 'dir1');
+          final dir2 = p.join(temp, 'dir2');
+          final subdir = p.join(dir1, 'subdir');
           _createDir(dir1);
           _createDir(dir2);
           createSymlink(dir2, subdir);
@@ -271,8 +271,8 @@
     test('resolves a vertically recursive symlink', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var dir = p.join(temp, 'dir');
-          var subdir = p.join(dir, 'subdir');
+          final dir = p.join(temp, 'dir');
+          final subdir = p.join(dir, 'subdir');
           _createDir(dir);
           createSymlink(dir, subdir);
           expect(
@@ -298,9 +298,9 @@
         () {
       expect(
         _withCanonicalTempDir((temp) {
-          var dir = p.join(temp, 'dir');
-          var linkdir = p.join(temp, 'linkdir');
-          var linkfile = p.join(dir, 'link');
+          final dir = p.join(temp, 'dir');
+          final linkdir = p.join(temp, 'linkdir');
+          final linkfile = p.join(dir, 'link');
           _createDir(dir);
           createSymlink(dir, linkdir);
           createSymlink(p.join(linkdir, 'file'), linkfile);
@@ -313,10 +313,10 @@
     test('resolves a pair of pathologically-recursive symlinks', () {
       expect(
         _withCanonicalTempDir((temp) {
-          var foo = p.join(temp, 'foo');
-          var subfoo = p.join(foo, 'subfoo');
-          var bar = p.join(temp, 'bar');
-          var subbar = p.join(bar, 'subbar');
+          final foo = p.join(temp, 'foo');
+          final subfoo = p.join(foo, 'subfoo');
+          final bar = p.join(temp, 'bar');
+          final subbar = p.join(bar, 'subbar');
           createSymlink(subbar, foo);
           createSymlink(subfoo, bar);
           expect(
@@ -627,7 +627,7 @@
     test('returns $forFile for a file', () {
       expect(
         withTempDir((temp) {
-          var file = p.join(temp, 'test.txt');
+          final file = p.join(temp, 'test.txt');
           writeTextFile(file, 'contents');
           expect(predicate(file), equals(forFile));
         }),
@@ -638,7 +638,7 @@
     test('returns $forDirectory for a directory', () {
       expect(
         withTempDir((temp) {
-          var file = p.join(temp, 'dir');
+          final file = p.join(temp, 'dir');
           _createDir(file);
           expect(predicate(file), equals(forDirectory));
         }),
@@ -649,8 +649,8 @@
     test('returns $forDirectorySymlink for a symlink to a directory', () {
       expect(
         withTempDir((temp) {
-          var targetPath = p.join(temp, 'dir');
-          var symlinkPath = p.join(temp, 'linkdir');
+          final targetPath = p.join(temp, 'dir');
+          final symlinkPath = p.join(temp, 'linkdir');
           _createDir(targetPath);
           createSymlink(targetPath, symlinkPath);
           expect(predicate(symlinkPath), equals(forDirectorySymlink));
@@ -664,9 +664,9 @@
         'a directory', () {
       expect(
         withTempDir((temp) {
-          var targetPath = p.join(temp, 'dir');
-          var symlink1Path = p.join(temp, 'link1dir');
-          var symlink2Path = p.join(temp, 'link2dir');
+          final targetPath = p.join(temp, 'dir');
+          final symlink1Path = p.join(temp, 'link1dir');
+          final symlink2Path = p.join(temp, 'link2dir');
           _createDir(targetPath);
           createSymlink(targetPath, symlink1Path);
           createSymlink(symlink1Path, symlink2Path);
@@ -682,8 +682,8 @@
     test('returns $forBrokenSymlink for a broken symlink', () {
       expect(
         withTempDir((temp) {
-          var targetPath = p.join(temp, 'dir');
-          var symlinkPath = p.join(temp, 'linkdir');
+          final targetPath = p.join(temp, 'dir');
+          final symlinkPath = p.join(temp, 'linkdir');
           _createDir(targetPath);
           createSymlink(targetPath, symlinkPath);
           deleteEntry(targetPath);
@@ -697,9 +697,9 @@
         () {
       expect(
         withTempDir((temp) {
-          var targetPath = p.join(temp, 'dir');
-          var symlink1Path = p.join(temp, 'link1dir');
-          var symlink2Path = p.join(temp, 'link2dir');
+          final targetPath = p.join(temp, 'dir');
+          final symlink1Path = p.join(temp, 'link1dir');
+          final symlink2Path = p.join(temp, 'link2dir');
           _createDir(targetPath);
           createSymlink(targetPath, symlink1Path);
           createSymlink(symlink1Path, symlink2Path);
@@ -715,8 +715,8 @@
       test('returns $forFileSymlink for a symlink to a file', () {
         expect(
           withTempDir((temp) {
-            var targetPath = p.join(temp, 'test.txt');
-            var symlinkPath = p.join(temp, 'link.txt');
+            final targetPath = p.join(temp, 'test.txt');
+            final symlinkPath = p.join(temp, 'link.txt');
             writeTextFile(targetPath, 'contents');
             createSymlink(targetPath, symlinkPath);
             expect(predicate(symlinkPath), equals(forFileSymlink));
@@ -730,9 +730,9 @@
           'file', () {
         expect(
           withTempDir((temp) {
-            var targetPath = p.join(temp, 'test.txt');
-            var symlink1Path = p.join(temp, 'link1.txt');
-            var symlink2Path = p.join(temp, 'link2.txt');
+            final targetPath = p.join(temp, 'test.txt');
+            final symlink1Path = p.join(temp, 'link1.txt');
+            final symlink2Path = p.join(temp, 'link2.txt');
             writeTextFile(targetPath, 'contents');
             createSymlink(targetPath, symlink1Path);
             createSymlink(symlink1Path, symlink2Path);
diff --git a/test/lish/archives_and_uploads_a_package_test.dart b/test/lish/archives_and_uploads_a_package_test.dart
index e06253a..cff6aed 100644
--- a/test/lish/archives_and_uploads_a_package_test.dart
+++ b/test/lish/archives_and_uploads_a_package_test.dart
@@ -19,7 +19,7 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
@@ -34,7 +34,10 @@
     });
 
     expect(pub.stdout, emits(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(exit_codes.SUCCESS);
   });
 
@@ -47,7 +50,7 @@
         {'url': globalServer.url, 'token': 'access-token'},
       ],
     }).create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
@@ -62,7 +65,10 @@
     });
 
     expect(pub.stdout, emits(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(exit_codes.SUCCESS);
   });
 
@@ -75,7 +81,7 @@
         {'url': '${globalServer.url}/sub/folder', 'env': 'TOKEN'},
       ],
     }).create();
-    var pub = await startPublish(
+    final pub = await startPublish(
       globalServer,
       path: '/sub/folder',
       overrideDefaultHostedServer: false,
@@ -95,7 +101,10 @@
     });
 
     expect(pub.stdout, emits(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(exit_codes.SUCCESS);
   });
 
@@ -105,7 +114,7 @@
   test('with an empty Git submodule', () async {
     await d.git('empty').create();
 
-    var repo = d.git(appPath, d.validPackage().contents);
+    final repo = d.git(appPath, d.validPackage().contents);
     await repo.create();
 
     await repo.runGit([
@@ -125,7 +134,7 @@
 
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
@@ -134,13 +143,17 @@
     globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(
         jsonEncode({
-          'success': {'message': 'Package test_pkg 1.0.0 uploaded!'},
+          'success': {'message': 'Package test_pkg 1.0.0\u0000uploaded!'},
+          // The \u0000 should be sanitized to a space.
         }),
       );
     });
 
     expect(pub.stdout, emits(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(exit_codes.SUCCESS);
   });
 
diff --git a/test/lish/cloud_storage_upload_doesnt_redirect_test.dart b/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
index 38f32cf..8aff97c 100644
--- a/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
+++ b/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
@@ -14,7 +14,7 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
diff --git a/test/lish/cloud_storage_upload_provides_an_error_test.dart b/test/lish/cloud_storage_upload_provides_an_error_test.dart
index 431e433..be0ba9f 100644
--- a/test/lish/cloud_storage_upload_provides_an_error_test.dart
+++ b/test/lish/cloud_storage_upload_provides_an_error_test.dart
@@ -14,7 +14,7 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
diff --git a/test/lish/does_not_include_dot_file.dart b/test/lish/does_not_include_dot_file.dart
index 7c9e6a4..8ddbcca 100644
--- a/test/lish/does_not_include_dot_file.dart
+++ b/test/lish/does_not_include_dot_file.dart
@@ -31,7 +31,7 @@
   test('Check if package doesn\'t include dot-files', () async {
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
diff --git a/test/lish/does_not_include_pubspec_overrides_file.dart b/test/lish/does_not_include_pubspec_overrides_file.dart
index 6d5c671..bd98a05 100644
--- a/test/lish/does_not_include_pubspec_overrides_file.dart
+++ b/test/lish/does_not_include_pubspec_overrides_file.dart
@@ -32,7 +32,7 @@
   test('Check if package doesn\'t include pubspec_overrides.yaml', () async {
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
diff --git a/test/lish/dry_run_package_validation_has_a_warning_test.dart b/test/lish/dry_run_package_validation_has_a_warning_test.dart
index 358a986..b8fa476 100644
--- a/test/lish/dry_run_package_validation_has_a_warning_test.dart
+++ b/test/lish/dry_run_package_validation_has_a_warning_test.dart
@@ -14,7 +14,7 @@
     (await servePackages()).serve('foo', '1.0.0');
     await d.validPackage().create();
 
-    var pkg = packageMap(
+    final pkg = packageMap(
       'test_pkg',
       '1.0.0',
       null,
@@ -24,7 +24,7 @@
     pkg['dependencies'] = {'foo': 'any'};
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
-    var pub = await startPublish(globalServer, args: ['--dry-run']);
+    final pub = await startPublish(globalServer, args: ['--dry-run']);
 
     await pub.shouldExit(exit_codes.DATA);
     expect(
diff --git a/test/lish/dry_run_package_validation_has_no_warnings_test.dart b/test/lish/dry_run_package_validation_has_no_warnings_test.dart
index 3045220..08c84e9 100644
--- a/test/lish/dry_run_package_validation_has_no_warnings_test.dart
+++ b/test/lish/dry_run_package_validation_has_no_warnings_test.dart
@@ -15,7 +15,7 @@
     await d.validPackage().create();
 
     await servePackages();
-    var pub = await startPublish(globalServer, args: ['--dry-run']);
+    final pub = await startPublish(globalServer, args: ['--dry-run']);
 
     await pub.shouldExit(exit_codes.SUCCESS);
     expect(pub.stderr, emitsThrough('Package has 0 warnings.'));
diff --git a/test/lish/force_does_not_publish_if_there_are_errors_test.dart b/test/lish/force_does_not_publish_if_there_are_errors_test.dart
index d1cad32..6523874 100644
--- a/test/lish/force_does_not_publish_if_there_are_errors_test.dart
+++ b/test/lish/force_does_not_publish_if_there_are_errors_test.dart
@@ -19,7 +19,7 @@
     File(d.path(p.join(appPath, 'LICENSE'))).deleteSync();
 
     await servePackages();
-    var pub = await startPublish(globalServer, args: ['--force']);
+    final pub = await startPublish(globalServer, args: ['--force']);
 
     await pub.shouldExit(exit_codes.DATA);
     expect(
diff --git a/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart b/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart
index cf05b23..ccc9958 100644
--- a/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart
+++ b/test/lish/force_publishes_if_tests_are_no_warnings_or_errors_test.dart
@@ -17,7 +17,7 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer, args: ['--force']);
+    final pub = await startPublish(globalServer, args: ['--force']);
 
     handleUploadForm(globalServer);
     handleUpload(globalServer);
@@ -31,6 +31,9 @@
     });
 
     await pub.shouldExit(exit_codes.SUCCESS);
-    expect(pub.stdout, emitsThrough('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emitsThrough('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
   });
 }
diff --git a/test/lish/force_publishes_if_there_are_warnings_test.dart b/test/lish/force_publishes_if_there_are_warnings_test.dart
index ca423f9..ceded0f 100644
--- a/test/lish/force_publishes_if_there_are_warnings_test.dart
+++ b/test/lish/force_publishes_if_there_are_warnings_test.dart
@@ -15,7 +15,7 @@
 void main() {
   test('--force publishes if there are warnings', () async {
     await d.validPackage().create();
-    var pkg = packageMap(
+    final pkg = packageMap(
       'test_pkg',
       '1.0.0',
       null,
@@ -28,7 +28,7 @@
     (await servePackages()).serve('foo', '1.0.0');
 
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer, args: ['--force']);
+    final pub = await startPublish(globalServer, args: ['--force']);
 
     handleUploadForm(globalServer);
     handleUpload(globalServer);
@@ -52,6 +52,9 @@
         ),
       ]),
     );
-    expect(pub.stdout, emitsThrough('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emitsThrough('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
   });
 }
diff --git a/test/lish/many_files_test.dart b/test/lish/many_files_test.dart
index 2d99c81..c9b4691 100644
--- a/test/lish/many_files_test.dart
+++ b/test/lish/many_files_test.dart
@@ -41,7 +41,7 @@
     ).create();
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
     pub.stdin.writeln('y');
     handleUploadForm(globalServer);
     handleUpload(globalServer);
@@ -73,7 +73,7 @@
     } else {
       // On POSIX, the maximum argument list length can be retrieved
       // automatically.
-      var result = Process.runSync('getconf', ['ARG_MAX']);
+      final result = Process.runSync('getconf', ['ARG_MAX']);
       if (result.exitCode != 0) {
         fail('getconf failed with exit code ${result.exitCode}:\n'
             '${result.stderr}');
@@ -82,25 +82,25 @@
       argMax = int.parse(result.stdout as String);
     }
 
-    var appRoot = p.join(d.sandbox, appPath);
+    final appRoot = p.join(d.sandbox, appPath);
 
     // We'll make the filenames as long as possible to reduce the number of
     // files we have to create to hit the maximum. However, the tar process
     // uses relative paths, which means we can't count the root as part of the
     // length.
-    var lengthPerFile = _pathMax - appRoot.length;
+    final lengthPerFile = _pathMax - appRoot.length;
 
     // Create enough files to hit [argMax]. This may be a slight overestimate,
     // since other options are passed to the tar command line, but we don't
     // know how long those will be.
-    var filesToCreate = (argMax / lengthPerFile).ceil();
+    final filesToCreate = (argMax / lengthPerFile).ceil();
 
     for (var i = 0; i < filesToCreate; i++) {
-      var iString = i.toString();
+      final iString = i.toString();
 
       // The file name contains "x"s to make the path hit [_pathMax],
       // followed by a number to distinguish different files.
-      var fileName =
+      final fileName =
           'x' * (_pathMax - appRoot.length - iString.length - 1) + iString;
 
       File(p.join(appRoot, fileName)).writeAsStringSync('');
@@ -108,7 +108,7 @@
 
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
@@ -123,7 +123,10 @@
     });
 
     expect(pub.stdout, emits(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(exit_codes.SUCCESS);
   });
 }
diff --git a/test/lish/package_creation_provides_a_malformed_error_test.dart b/test/lish/package_creation_provides_a_malformed_error_test.dart
index 662f896..d2d6ef9 100644
--- a/test/lish/package_creation_provides_a_malformed_error_test.dart
+++ b/test/lish/package_creation_provides_a_malformed_error_test.dart
@@ -16,13 +16,13 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
     handleUpload(globalServer);
 
-    var body = {'error': 'Your package was too boring.'};
+    final body = {'error': 'Your package was too boring.'};
     globalServer.expect('GET', '/create', (request) {
       return shelf.Response.notFound(jsonEncode(body));
     });
diff --git a/test/lish/package_creation_provides_a_malformed_success_test.dart b/test/lish/package_creation_provides_a_malformed_success_test.dart
index dc3791b..048c980 100644
--- a/test/lish/package_creation_provides_a_malformed_success_test.dart
+++ b/test/lish/package_creation_provides_a_malformed_success_test.dart
@@ -16,13 +16,13 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
     handleUpload(globalServer);
 
-    var body = {'success': 'Your package was awesome.'};
+    final body = {'success': 'Your package was awesome.'};
     globalServer.expect('GET', '/create', (request) {
       return shelf.Response.ok(jsonEncode(body));
     });
diff --git a/test/lish/package_creation_provides_an_error_test.dart b/test/lish/package_creation_provides_an_error_test.dart
index 9fd5e48..313cb0d 100644
--- a/test/lish/package_creation_provides_an_error_test.dart
+++ b/test/lish/package_creation_provides_an_error_test.dart
@@ -16,7 +16,7 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
@@ -30,7 +30,10 @@
       );
     });
 
-    expect(pub.stderr, emits('Your package was too boring.'));
+    expect(
+      pub.stderr,
+      emits('Message from server: Your package was too boring.'),
+    );
     await pub.shouldExit(1);
   });
 }
diff --git a/test/lish/package_creation_provides_invalid_json_test.dart b/test/lish/package_creation_provides_invalid_json_test.dart
index 9a7b935..a4b2fe8 100644
--- a/test/lish/package_creation_provides_invalid_json_test.dart
+++ b/test/lish/package_creation_provides_invalid_json_test.dart
@@ -14,7 +14,7 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
diff --git a/test/lish/package_validation_has_a_warning_and_continues_test.dart b/test/lish/package_validation_has_a_warning_and_continues_test.dart
index f1854d4..a0c0bc6 100644
--- a/test/lish/package_validation_has_a_warning_and_continues_test.dart
+++ b/test/lish/package_validation_has_a_warning_and_continues_test.dart
@@ -23,7 +23,7 @@
 
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
     expect(pub.stdout, emitsThrough(startsWith('Package has 1 warning.')));
     pub.stdin.writeln('y');
     handleUploadForm(globalServer);
@@ -38,6 +38,9 @@
     });
 
     await pub.shouldExit(exit_codes.SUCCESS);
-    expect(pub.stdout, emitsThrough('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emitsThrough('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
   });
 }
diff --git a/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart b/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
index f44b3a4..ccd221e 100644
--- a/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
+++ b/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
@@ -11,7 +11,7 @@
 void main() {
   test('package validation has a warning and is canceled', () async {
     await d.validPackage().create();
-    var pkg = packageMap(
+    final pkg = packageMap(
       'test_pkg',
       '1.0.0',
       null,
@@ -24,7 +24,7 @@
     ]).create();
 
     await servePackages();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     pub.stdin.writeln('n');
     await pub.shouldExit(exit_codes.DATA);
diff --git a/test/lish/package_validation_has_an_error_test.dart b/test/lish/package_validation_has_an_error_test.dart
index 1f47c68..46c75d7 100644
--- a/test/lish/package_validation_has_an_error_test.dart
+++ b/test/lish/package_validation_has_an_error_test.dart
@@ -20,7 +20,7 @@
     ]).create();
 
     await servePackages();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await pub.shouldExit(exit_codes.DATA);
     expect(
diff --git a/test/lish/publishing_to_and_from_archive_test.dart b/test/lish/publishing_to_and_from_archive_test.dart
index 8d3ee5c..9e2e6bf 100644
--- a/test/lish/publishing_to_and_from_archive_test.dart
+++ b/test/lish/publishing_to_and_from_archive_test.dart
@@ -49,7 +49,10 @@
     handleUpload(server);
 
     expect(pub.stdout, emitsThrough(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(SUCCESS);
   });
 }
diff --git a/test/lish/server_arg_overrides_publish_to_url_test.dart b/test/lish/server_arg_overrides_publish_to_url_test.dart
index dd05b7c..ea245a7 100644
--- a/test/lish/server_arg_overrides_publish_to_url_test.dart
+++ b/test/lish/server_arg_overrides_publish_to_url_test.dart
@@ -15,7 +15,7 @@
     // try to ping it, and will use multiple retries when doing so.
     final packageServer = await startPackageServer();
 
-    var pkg = packageMap('test_pkg', '1.0.0');
+    final pkg = packageMap('test_pkg', '1.0.0');
     pkg['publish_to'] = 'http://pubspec.com';
     await d.dir(appPath, [d.pubspec(pkg)]).create();
     await runPub(
diff --git a/test/lish/skip_validation_test.dart b/test/lish/skip_validation_test.dart
index e9b3dc0..885b580 100644
--- a/test/lish/skip_validation_test.dart
+++ b/test/lish/skip_validation_test.dart
@@ -33,7 +33,7 @@
     await d.credentialsFile(globalServer, 'access-token').create();
 
     await servePackages();
-    var pub = await startPublish(globalServer, args: ['--skip-validation']);
+    final pub = await startPublish(globalServer, args: ['--skip-validation']);
 
     await confirmPublish(pub);
 
diff --git a/test/lish/unicode_file_names_test.dart b/test/lish/unicode_file_names_test.dart
index 64d9a09..ec3be5b 100644
--- a/test/lish/unicode_file_names_test.dart
+++ b/test/lish/unicode_file_names_test.dart
@@ -19,7 +19,7 @@
 
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
     handleUploadForm(globalServer);
@@ -34,7 +34,10 @@
     });
 
     expect(pub.stdout, emits(startsWith('Uploading...')));
-    expect(pub.stdout, emits('Package test_pkg 1.0.0 uploaded!'));
+    expect(
+      pub.stdout,
+      emits('Message from server: Package test_pkg 1.0.0 uploaded!'),
+    );
     await pub.shouldExit(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 e4c8bd5..44eca65 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
@@ -15,11 +15,11 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    var body = {
+    final body = {
       'url': 'http://example.com/upload',
       'fields': {'field': 12},
     };
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 4701d44..bad76ec 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
@@ -15,11 +15,11 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    var body = {'url': 'http://example.com/upload', 'fields': 12};
+    final body = {'url': 'http://example.com/upload', 'fields': 12};
     handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
diff --git a/test/lish/upload_form_is_missing_fields_test.dart b/test/lish/upload_form_is_missing_fields_test.dart
index 8aee08a..2c0becc 100644
--- a/test/lish/upload_form_is_missing_fields_test.dart
+++ b/test/lish/upload_form_is_missing_fields_test.dart
@@ -15,11 +15,11 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    var body = {'url': 'http://example.com/upload'};
+    final body = {'url': 'http://example.com/upload'};
     handleUploadForm(globalServer, body: body);
     expect(pub.stderr, emits('Invalid server response:'));
     expect(pub.stderr, emits(jsonEncode(body)));
diff --git a/test/lish/upload_form_is_missing_url_test.dart b/test/lish/upload_form_is_missing_url_test.dart
index 30364b4..0b8c873 100644
--- a/test/lish/upload_form_is_missing_url_test.dart
+++ b/test/lish/upload_form_is_missing_url_test.dart
@@ -15,11 +15,11 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    var body = {
+    final body = {
       'fields': {'field1': 'value1', 'field2': 'value2'},
     };
 
diff --git a/test/lish/upload_form_provides_an_error_test.dart b/test/lish/upload_form_provides_an_error_test.dart
index fbbf129..2e62e92 100644
--- a/test/lish/upload_form_provides_an_error_test.dart
+++ b/test/lish/upload_form_provides_an_error_test.dart
@@ -11,23 +11,25 @@
 import '../test_pub.dart';
 
 void main() {
-  test('upload form provides an error', () async {
+  test('upload form provides an error, that is sanitized', () async {
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
     globalServer.expect('GET', '/api/packages/versions/new', (request) async {
       return shelf.Response.notFound(
         jsonEncode({
-          'error': {'message': 'your request sucked'},
+          'error': {
+            'message': 'your request\u0000sucked',
+          }, // The \u0000 should be sanitized to a space.
         }),
       );
     });
 
-    expect(pub.stderr, emits('your request sucked'));
+    expect(pub.stderr, emits('Message from server: your request sucked'));
     await pub.shouldExit(1);
   });
 }
diff --git a/test/lish/upload_form_provides_invalid_json_test.dart b/test/lish/upload_form_provides_invalid_json_test.dart
index 1b64d5f..1c12f0c 100644
--- a/test/lish/upload_form_provides_invalid_json_test.dart
+++ b/test/lish/upload_form_provides_invalid_json_test.dart
@@ -14,7 +14,7 @@
     await d.validPackage().create();
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
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 9fb642c..20f8312 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
@@ -15,11 +15,11 @@
     await servePackages();
     await d.validPackage().create();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
-    var body = {
+    final body = {
       'url': 12,
       'fields': {'field1': 'value1', 'field2': 'value2'},
     };
diff --git a/test/lish/uses_publish_to_url_test.dart b/test/lish/uses_publish_to_url_test.dart
index 57d7de2..c389b14 100644
--- a/test/lish/uses_publish_to_url_test.dart
+++ b/test/lish/uses_publish_to_url_test.dart
@@ -12,7 +12,7 @@
 void main() {
   test('uses the publish_to URL', () async {
     final server = await servePackages();
-    var pkg = packageMap('test_pkg', '1.0.0');
+    final pkg = packageMap('test_pkg', '1.0.0');
     pkg['publish_to'] = server.url;
     await d.dir(appPath, [d.pubspec(pkg)]).create();
     await runPub(
diff --git a/test/lock_file_test.dart b/test/lock_file_test.dart
index 282a7c9..93b2364 100644
--- a/test/lock_file_test.dart
+++ b/test/lock_file_test.dart
@@ -17,17 +17,17 @@
   group('LockFile', () {
     group('parse()', () {
       test('returns an empty lockfile if the contents are empty', () {
-        var lockFile = LockFile.parse('', sources);
+        final lockFile = LockFile.parse('', sources);
         expect(lockFile.packages.length, equals(0));
       });
 
       test('returns an empty lockfile if the contents are whitespace', () {
-        var lockFile = LockFile.parse('  \t\n  ', sources);
+        final lockFile = LockFile.parse('  \t\n  ', sources);
         expect(lockFile.packages.length, equals(0));
       });
 
       test('parses a series of package descriptions', () {
-        var lockFile = LockFile.parse(
+        final lockFile = LockFile.parse(
           '''
 packages:
   bar:
@@ -48,7 +48,7 @@
 
         expect(lockFile.packages.length, equals(2));
 
-        var bar = lockFile.packages['bar']!;
+        final bar = lockFile.packages['bar']!;
         expect(bar.name, equals('bar'));
         expect(bar.version, equals(Version(1, 2, 3)));
         expect(bar.source, equals(cache.hosted));
@@ -57,7 +57,7 @@
           equals('https://bar.com'),
         );
 
-        var foo = lockFile.packages['foo']!;
+        final foo = lockFile.packages['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.version, equals(Version(2, 3, 4)));
         expect(foo.source, equals(cache.hosted));
@@ -68,7 +68,7 @@
       });
 
       test('allows an unknown source', () {
-        var lockFile = LockFile.parse(
+        final lockFile = LockFile.parse(
           '''
 packages:
   foo:
@@ -78,12 +78,12 @@
 ''',
           cache.sources,
         );
-        var foo = lockFile.packages['foo']!;
+        final foo = lockFile.packages['foo']!;
         expect(foo.source, equals(sources('bad')));
       });
 
       test('allows an empty dependency map', () {
-        var lockFile = LockFile.parse(
+        final lockFile = LockFile.parse(
           '''
 packages:
 ''',
@@ -93,7 +93,7 @@
       });
 
       test('allows an old-style SDK constraint', () {
-        var lockFile = LockFile.parse('sdk: ">=1.2.3 <4.0.0"', sources);
+        final lockFile = LockFile.parse('sdk: ">=1.2.3 <4.0.0"', sources);
         expect(
           lockFile.sdkConstraints['dart']!.effectiveConstraint,
           VersionConstraint.parse('>=1.2.3 <4.0.0'),
@@ -103,7 +103,7 @@
       });
 
       test('allows new-style SDK constraints', () {
-        var lockFile = LockFile.parse(
+        final lockFile = LockFile.parse(
           '''
 sdks:
   dart: ">=1.2.3 <4.0.0"
@@ -367,7 +367,7 @@
     });
 
     test('serialize() dumps the lockfile to YAML', () {
-      var lockfile = LockFile(
+      final lockfile = LockFile(
         [
           PackageId(
             'foo',
diff --git a/test/oauth2/utils.dart b/test/oauth2/utils.dart
index 05636e2..dc9da93 100644
--- a/test/oauth2/utils.dart
+++ b/test/oauth2/utils.dart
@@ -24,8 +24,8 @@
         'behalf.'),
   );
 
-  var line = await pub.stdout.next;
-  var match =
+  final line = await pub.stdout.next;
+  final match =
       RegExp(r'[?&]redirect_uri=([0-9a-zA-Z.%+-]+)[$&]').firstMatch(line)!;
   expect(match, isNotNull);
 
@@ -37,14 +37,14 @@
 
   // Call the redirect url as the browser would otherwise do after successful
   // sign-in with Google account.
-  var response =
+  final response =
       await (http.Request('GET', redirectUrl)..followRedirects = false).send();
   expect(response.headers['location'], equals('https://pub.dev/authorized'));
 }
 
 void handleAccessTokenRequest(PackageServer server, String accessToken) {
   server.expect('POST', '/token', (request) async {
-    var body = await request.readAsString();
+    final body = await request.readAsString();
     expect(body, matches(RegExp(r'(^|&)code=access\+code(&|$)')));
 
     return shelf.Response.ok(
@@ -57,14 +57,14 @@
 /// Adds additional query parameters to [url], overwriting the original
 /// parameters if a name conflict occurs.
 Uri _addQueryParameters(Uri url, Map<String, String> parameters) {
-  var queryMap = queryToMap(url.query);
+  final queryMap = queryToMap(url.query);
   queryMap.addAll(parameters);
   return url.resolve('?${_mapToQuery(queryMap)}');
 }
 
 /// Convert a [Map] from parameter names to values to a URL query string.
 String _mapToQuery(Map<String, String?> map) {
-  var pairs = <List<String?>>[];
+  final pairs = <List<String?>>[];
   map.forEach((key, value) {
     key = Uri.encodeQueryComponent(key);
     value = (value == null || value.isEmpty)
diff --git a/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart b/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart
index e2192de..d4e62ac 100644
--- a/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart
+++ b/test/oauth2/with_a_malformed_credentials_authenticates_again_test.dart
@@ -18,7 +18,7 @@
     await servePackages();
     await configDir([d.file('pub-credentials.json', '{bad json')]).create();
 
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
     await confirmPublish(pub);
     await authorizePub(pub, globalServer, 'new access token');
 
diff --git a/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart b/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart
index 860de44..3169558 100644
--- a/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart
+++ b/test/oauth2/with_a_pre_existing_credentials_does_not_authenticate_test.dart
@@ -13,7 +13,7 @@
 
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
diff --git a/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart b/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
index 0669cb6..45d3e8b 100644
--- a/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
+++ b/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
@@ -29,7 +29,7 @@
         )
         .create();
 
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     globalServer.expect('POST', '/token', (request) {
       return request.read().drain<void>().then((_) {
@@ -46,7 +46,7 @@
     await expectLater(pub.stdout, emits(startsWith('Uploading...')));
     await authorizePub(pub, globalServer, 'new access token');
 
-    var done = Completer<void>();
+    final done = Completer<void>();
     globalServer.expect('GET', '/api/packages/versions/new', (request) async {
       expect(
         request.headers,
diff --git a/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart b/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart
index 4f9050c..7686b84 100644
--- a/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart
+++ b/test/oauth2/with_an_expired_credentials_refreshes_and_saves_test.dart
@@ -26,7 +26,7 @@
         )
         .create();
 
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
     await confirmPublish(pub);
 
     globalServer.expect('POST', '/token', (request) {
diff --git a/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart b/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart
index a5e495a..9814e8c 100644
--- a/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart
+++ b/test/oauth2/with_an_expired_credentials_without_a_refresh_token_authenticates_again_test.dart
@@ -24,7 +24,7 @@
         )
         .create();
 
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
     await confirmPublish(pub);
 
     await expectLater(
diff --git a/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart b/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart
index 6ea2883..9562345 100644
--- a/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart
+++ b/test/oauth2/with_no_credentials_authenticates_and_saves_credentials_test.dart
@@ -15,7 +15,7 @@
       'credentials.json', () async {
     await d.validPackage().create();
     await servePackages();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
     await confirmPublish(pub);
     await authorizePub(pub, globalServer);
 
diff --git a/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart b/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart
index 4b85a04..8deb238 100644
--- a/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart
+++ b/test/oauth2/with_server_rejected_credentials_authenticates_again_test.dart
@@ -17,7 +17,7 @@
     await d.validPackage().create();
     await servePackages();
     await d.credentialsFile(globalServer, 'access-token').create();
-    var pub = await startPublish(globalServer);
+    final pub = await startPublish(globalServer);
 
     await confirmPublish(pub);
 
diff --git a/test/outdated/outdated_test.dart b/test/outdated/outdated_test.dart
index 084c8dc..ff9ed28 100644
--- a/test/outdated/outdated_test.dart
+++ b/test/outdated/outdated_test.dart
@@ -778,6 +778,56 @@
     await ctx.runOutdatedTests();
   });
 
+  testWithGolden('reports dependencies from all of workspace', (ctx) async {
+    final server = await servePackages();
+    server.serve('myapp', '1.2.4');
+    server.serve('dep', '0.9.0', deps: {'myapp': '^1.2.3'});
+    server.serve('dep', '0.8.0', deps: {'myapp': '^1.2.3'});
+    server.serve('dep', '1.0.0');
+    server.serve('dep_a', '0.9.0');
+    server.serve('dep_a', '1.0.0');
+    server.serve('dev_dep_a', '0.9.0');
+    server.serve('dev_dep_a', '1.0.0');
+
+    await d.dir(appPath, [
+      d.libPubspec(
+        'myapp',
+        '1.2.3',
+        deps: {'dep': '^0.9.0'},
+        extras: {
+          'workspace': ['pkgs/a'],
+        },
+        sdk: '^3.5.0',
+      ),
+      d.dir('pkgs', [
+        d.dir('a', [
+          d.libPubspec(
+            'a',
+            '1.1.1',
+            deps: {'myapp': '^1.0.0', 'dep_a': '^0.9.0'},
+            devDeps: {'dev_dep_a': '^0.9.0'},
+            extras: {
+              'dependency_overrides': {'dep': '0.8.0'},
+            },
+            resolutionWorkspace: true,
+          ),
+        ]),
+      ]),
+    ]).create();
+
+    await pubGet(
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+
+    server.serve('dep', '0.9.5');
+    server.serve('dep_a', '0.9.5');
+    server.serve('dev_dep_a', '0.9.5');
+
+    await ctx.runOutdatedTests(
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+  });
+
   testWithGolden('Handles SDK dependencies', (ctx) async {
     await servePackages()
       ..serve(
diff --git a/test/package_config_file_test.dart b/test/package_config_file_test.dart
index d5d052b..9353a2f 100644
--- a/test/package_config_file_test.dart
+++ b/test/package_config_file_test.dart
@@ -81,7 +81,7 @@
         d.dir('lib'),
       ]).create();
 
-      var oldFile = d.dir(appPath, [
+      final oldFile = d.dir(appPath, [
         d.packageConfigFile([
           d.packageConfigEntry(
             name: 'notFoo',
diff --git a/test/package_server.dart b/test/package_server.dart
index 1b142b2..d39168f 100644
--- a/test/package_server.dart
+++ b/test/package_server.dart
@@ -300,7 +300,7 @@
     String? sdk,
     Map<String, List<String>>? headers,
   }) {
-    var pubspecFields = <String, dynamic>{
+    final pubspecFields = <String, dynamic>{
       'name': name,
       'version': version,
       'environment': {'sdk': sdk ?? '^3.0.0'},
@@ -311,7 +311,7 @@
     contents ??= [d.libDir(name, '$name $version')];
     contents = [d.file('pubspec.yaml', yaml(pubspecFields)), ...contents];
 
-    var package = _packages.putIfAbsent(name, _ServedPackage.new);
+    final package = _packages.putIfAbsent(name, _ServedPackage.new);
     package.versions[version] = _ServedPackageVersion(
       pubspecFields,
       headers: headers,
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index cbbb0cf..899c1db 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -17,7 +17,7 @@
   group('parse()', () {
     final sources = SystemCache().sources;
 
-    var throwsPubspecException =
+    final throwsPubspecException =
         throwsA(const TypeMatcher<SourceSpanApplicationException>());
 
     void expectPubspecException(
@@ -35,7 +35,7 @@
         );
       }
 
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         contents,
         sources,
         containingDescription: containingDescription ?? RootDescription('.'),
@@ -81,7 +81,7 @@
     });
 
     test('allows a version constraint for dependencies', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependencies:
   foo:
@@ -94,7 +94,7 @@
         containingDescription: RootDescription('.'),
       );
 
-      var foo = pubspec.dependencies['foo']!;
+      final foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.allows(Version(1, 2, 3)), isTrue);
       expect(foo.constraint.allows(Version(1, 2, 5)), isTrue);
@@ -102,7 +102,7 @@
     });
 
     test('allows empty version constraint', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependencies:
   foo:
@@ -115,13 +115,13 @@
         containingDescription: RootDescription('.'),
       );
 
-      var foo = pubspec.dependencies['foo']!;
+      final foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.isEmpty, isTrue);
     });
 
     test('allows an empty dependencies map', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependencies:
 ''',
@@ -133,7 +133,7 @@
     });
 
     test('allows a version constraint for dev dependencies', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dev_dependencies:
   foo:
@@ -146,7 +146,7 @@
         containingDescription: RootDescription('.'),
       );
 
-      var foo = pubspec.devDependencies['foo']!;
+      final foo = pubspec.devDependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.allows(Version(1, 2, 3)), isTrue);
       expect(foo.constraint.allows(Version(1, 2, 5)), isTrue);
@@ -154,7 +154,7 @@
     });
 
     test('allows an empty dev dependencies map', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dev_dependencies:
 ''',
@@ -166,7 +166,7 @@
     });
 
     test('allows a version constraint for dependency overrides', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependency_overrides:
   foo:
@@ -179,7 +179,7 @@
         containingDescription: RootDescription('.'),
       );
 
-      var foo = pubspec.dependencyOverrides['foo']!;
+      final foo = pubspec.dependencyOverrides['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.constraint.allows(Version(1, 2, 3)), isTrue);
       expect(foo.constraint.allows(Version(1, 2, 5)), isTrue);
@@ -187,7 +187,7 @@
     });
 
     test('allows an empty dependency overrides map', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependency_overrides:
 ''',
@@ -199,7 +199,7 @@
     });
 
     test('allows an unknown source', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependencies:
   foo:
@@ -209,13 +209,13 @@
         containingDescription: RootDescription('.'),
       );
 
-      var foo = pubspec.dependencies['foo']!;
+      final foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.source, equals(sources('unknown')));
     });
 
     test('allows a default source', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 dependencies:
   foo:
@@ -225,7 +225,7 @@
         containingDescription: RootDescription('.'),
       );
 
-      var foo = pubspec.dependencies['foo']!;
+      final foo = pubspec.dependencies['foo']!;
       expect(foo.name, equals('foo'));
       expect(foo.source, equals(sources('hosted')));
     });
@@ -445,7 +445,7 @@
     });
 
     test('allows comment-only files', () {
-      var pubspec = Pubspec.parse(
+      final pubspec = Pubspec.parse(
         '''
 # No external dependencies yet
 # Including for completeness
@@ -477,7 +477,7 @@
 
     group('source dependencies', () {
       test('with url and name', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 name: pkg
 dependencies:
@@ -490,7 +490,7 @@
           containingDescription: RootDescription('.'),
         );
 
-        var foo = pubspec.dependencies['foo']!;
+        final foo = pubspec.dependencies['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.source.name, 'hosted');
         expect(
@@ -505,7 +505,7 @@
       });
 
       test('with url only', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 name: pkg
 environment:
@@ -519,7 +519,7 @@
           containingDescription: RootDescription('.'),
         );
 
-        var foo = pubspec.dependencies['foo']!;
+        final foo = pubspec.dependencies['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.source.name, 'hosted');
         expect(
@@ -534,7 +534,7 @@
       });
 
       test('with url as string', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 name: pkg
 environment:
@@ -547,7 +547,7 @@
           containingDescription: RootDescription('.'),
         );
 
-        var foo = pubspec.dependencies['foo']!;
+        final foo = pubspec.dependencies['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.source.name, 'hosted');
         expect(
@@ -562,7 +562,7 @@
       });
 
       test('interprets string description as name for older versions', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 name: pkg
 environment:
@@ -575,7 +575,7 @@
           containingDescription: RootDescription('.'),
         );
 
-        var foo = pubspec.dependencies['foo']!;
+        final foo = pubspec.dependencies['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.source.name, 'hosted');
         expect(
@@ -592,7 +592,7 @@
       test(
         'reports helpful span when using new syntax with invalid environment',
         () {
-          var pubspec = Pubspec.parse(
+          final pubspec = Pubspec.parse(
             '''
 name: pkg
 environment:
@@ -616,7 +616,7 @@
       );
 
       test('without a description', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 name: pkg
 dependencies:
@@ -626,7 +626,7 @@
           containingDescription: RootDescription('.'),
         );
 
-        var foo = pubspec.dependencies['foo']!;
+        final foo = pubspec.dependencies['foo']!;
         expect(foo.name, equals('foo'));
         expect(foo.source.name, 'hosted');
         expect(
@@ -730,7 +730,7 @@
 
     group('environment', () {
       test('allows an omitted environment', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           'name: testing',
           sources,
           containingDescription: RootDescription('.'),
@@ -745,7 +745,7 @@
       });
 
       test('default SDK constraint can be omitted with empty environment', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '',
           sources,
           containingDescription: RootDescription('.'),
@@ -759,7 +759,7 @@
       });
 
       test('defaults the upper constraint for the SDK', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
   name: test
   environment:
@@ -779,7 +779,7 @@
       test(
           'default upper constraint for the SDK applies only if compatible '
           'with the lower bound', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
   environment:
     sdk: ">3.0.0"
@@ -806,7 +806,7 @@
       });
 
       test('allows a version constraint for the SDKs', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 environment:
   sdk: ">=1.2.3 <2.3.4"
@@ -875,7 +875,7 @@
 
     group('publishTo', () {
       test('defaults to null if omitted', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '',
           sources,
           containingDescription: RootDescription('.'),
@@ -891,7 +891,7 @@
       });
 
       test('allows a URL', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 publish_to: http://example.com
 ''',
@@ -902,7 +902,7 @@
       });
 
       test('allows none', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 publish_to: none
 ''',
@@ -929,7 +929,7 @@
 
     group('executables', () {
       test('defaults to an empty map if omitted', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '',
           sources,
           containingDescription: RootDescription('.'),
@@ -938,7 +938,7 @@
       });
 
       test('allows simple names for keys and most characters in values', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 executables:
   abcDEF-123_: "abc DEF-123._"
@@ -992,7 +992,7 @@
       });
 
       test('uses the key if the value is null', () {
-        var pubspec = Pubspec.parse(
+        final pubspec = Pubspec.parse(
           '''
 executables:
   command:
@@ -1035,12 +1035,12 @@
           );
         }
 
-        var pubspec = parsePubspecOverrides(contents);
+        final pubspec = parsePubspecOverrides(contents);
         expect(() => fn(pubspec), throwsA(expectation));
       }
 
       test('allows empty overrides file', () {
-        var pubspec = parsePubspecOverrides('');
+        final pubspec = parsePubspecOverrides('');
         expect(pubspec.dependencyOverrides['foo'], isNull);
         final bar = pubspec.dependencyOverrides['bar']!;
         expect(bar.name, equals('bar'));
diff --git a/test/remove/remove_test.dart b/test/remove/remove_test.dart
index c2787f0..7c5fbc6 100644
--- a/test/remove/remove_test.dart
+++ b/test/remove/remove_test.dart
@@ -213,7 +213,7 @@
     final server = await servePackages();
     server.serve('bar', '2.0.1');
 
-    var custom = await startPackageServer();
+    final custom = await startPackageServer();
     custom.serve('foo', '1.2.3');
 
     await d.appDir(
diff --git a/test/run/allows_dart_extension_test.dart b/test/run/allows_dart_extension_test.dart
index 62475bc..36557ad 100644
--- a/test/run/allows_dart_extension_test.dart
+++ b/test/run/allows_dart_extension_test.dart
@@ -25,7 +25,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['script.dart']);
+    final pub = await pubRun(args: ['script.dart']);
     expect(pub.stdout, emitsThrough('stdout output'));
     expect(pub.stderr, emitsThrough('stderr output'));
     await pub.shouldExit(123);
diff --git a/test/run/app_can_read_from_stdin_test.dart b/test/run/app_can_read_from_stdin_test.dart
index 646e7e5..0084be4 100644
--- a/test/run/app_can_read_from_stdin_test.dart
+++ b/test/run/app_can_read_from_stdin_test.dart
@@ -28,7 +28,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['bin/script']);
+    final pub = await pubRun(args: ['bin/script']);
 
     await expectLater(pub.stdout, emitsThrough('started'));
     pub.stdin.writeln('first');
@@ -55,7 +55,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['bin/script']);
+    final pub = await pubRun(args: ['bin/script']);
 
     await expectLater(pub.stdout, emitsThrough('started'));
     pub.stdin.writeln('first');
diff --git a/test/run/errors_if_only_transitive_dependency_test.dart b/test/run/errors_if_only_transitive_dependency_test.dart
index 1ba780e..4723990 100644
--- a/test/run/errors_if_only_transitive_dependency_test.dart
+++ b/test/run/errors_if_only_transitive_dependency_test.dart
@@ -35,7 +35,7 @@
 
     await pubGet();
 
-    var pub = await pubRun(args: ['foo:script']);
+    final pub = await pubRun(args: ['foo:script']);
     expect(pub.stderr, emits('Package "foo" is not an immediate dependency.'));
     expect(
       pub.stderr,
diff --git a/test/run/forwards_signal_posix_test.dart b/test/run/forwards_signal_posix_test.dart
index cfb18da..ec0b3e3 100644
--- a/test/run/forwards_signal_posix_test.dart
+++ b/test/run/forwards_signal_posix_test.dart
@@ -47,7 +47,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['bin/script']);
+    final pub = await pubRun(args: ['bin/script']);
 
     await expectLater(pub.stdout, emitsThrough('ready'));
     for (var signal in _catchableSignals) {
diff --git a/test/run/includes_parent_directories_of_entrypoint_test.dart b/test/run/includes_parent_directories_of_entrypoint_test.dart
index 7156039..668714d 100644
--- a/test/run/includes_parent_directories_of_entrypoint_test.dart
+++ b/test/run/includes_parent_directories_of_entrypoint_test.dart
@@ -32,7 +32,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: [p.join('tool', 'a', 'b', 'app')]);
+    final pub = await pubRun(args: [p.join('tool', 'a', 'b', 'app')]);
     expect(pub.stdout, emitsThrough('a b'));
     await pub.shouldExit();
   });
diff --git a/test/run/loads_package_imports_in_a_dependency_test.dart b/test/run/loads_package_imports_in_a_dependency_test.dart
index 192cdbf..87e5506 100644
--- a/test/run/loads_package_imports_in_a_dependency_test.dart
+++ b/test/run/loads_package_imports_in_a_dependency_test.dart
@@ -30,7 +30,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['foo:bar']);
+    final pub = await pubRun(args: ['foo:bar']);
     expect(pub.stdout, emitsThrough('foobar'));
     await pub.shouldExit();
   });
diff --git a/test/run/nonexistent_dependency_test.dart b/test/run/nonexistent_dependency_test.dart
index 955cdaa..1f420fa 100644
--- a/test/run/nonexistent_dependency_test.dart
+++ b/test/run/nonexistent_dependency_test.dart
@@ -13,7 +13,7 @@
     await d.dir(appPath, [d.appPubspec()]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['foo:script']);
+    final pub = await pubRun(args: ['foo:script']);
     expect(
       pub.stderr,
       emits('Could not find package "foo". Did you forget to add a '
diff --git a/test/run/nonexistent_script_in_dependency_test.dart b/test/run/nonexistent_script_in_dependency_test.dart
index 3179e0c..15c0e3e 100644
--- a/test/run/nonexistent_script_in_dependency_test.dart
+++ b/test/run/nonexistent_script_in_dependency_test.dart
@@ -23,7 +23,7 @@
 
     await pubGet();
 
-    var pub = await pubRun(args: ['foo:script']);
+    final pub = await pubRun(args: ['foo:script']);
     expect(
       pub.stderr,
       emits(
diff --git a/test/run/nonexistent_script_test.dart b/test/run/nonexistent_script_test.dart
index 4ebb8c0..37b26ae 100644
--- a/test/run/nonexistent_script_test.dart
+++ b/test/run/nonexistent_script_test.dart
@@ -14,7 +14,7 @@
     await d.dir(appPath, [d.appPubspec()]).create();
 
     await pubGet();
-    var pub = await pubRun(args: [p.join('bin', 'script')]);
+    final pub = await pubRun(args: [p.join('bin', 'script')]);
     expect(
       pub.stderr,
       emits("Could not find ${p.join("bin", "script.dart")}."),
diff --git a/test/run/package_api_test.dart b/test/run/package_api_test.dart
index 4be659c..6d95d9d 100644
--- a/test/run/package_api_test.dart
+++ b/test/run/package_api_test.dart
@@ -34,7 +34,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['bin/script']);
+    final pub = await pubRun(args: ['bin/script']);
 
     expect(
       pub.stdout,
@@ -71,7 +71,7 @@
 
     await pubGet();
 
-    var pub = await pubRun(args: ['foo:script']);
+    final pub = await pubRun(args: ['foo:script']);
 
     expect(pub.stdout, emitsThrough('Building package executable...'));
     expect(pub.stdout, emits('Built foo:script.'));
@@ -87,7 +87,7 @@
       pub.stdout,
       emits(p.toUri(p.join(d.sandbox, 'myapp/lib/resource.txt')).toString()),
     );
-    var fooResourcePath =
+    final fooResourcePath =
         p.join(globalServer.pathInCache('foo', '1.0.0'), 'lib/resource.txt');
     expect(pub.stdout, emits(p.toUri(fooResourcePath).toString()));
     await pub.shouldExit(0);
diff --git a/test/run/passes_along_arguments_test.dart b/test/run/passes_along_arguments_test.dart
index 4f372c2..15f383c 100644
--- a/test/run/passes_along_arguments_test.dart
+++ b/test/run/passes_along_arguments_test.dart
@@ -24,7 +24,8 @@
 
     // Use some args that would trip up pub's arg parser to ensure that it
     // isn't trying to look at them.
-    var pub = await pubRun(args: ['bin/args', '--verbose', '-m', '--', 'help']);
+    final pub =
+        await pubRun(args: ['bin/args', '--verbose', '-m', '--', 'help']);
 
     expect(pub.stdout, emitsThrough('--verbose -m -- help'));
     await pub.shouldExit();
diff --git a/test/run/precompile_test.dart b/test/run/precompile_test.dart
index 263edde..856f0bd 100644
--- a/test/run/precompile_test.dart
+++ b/test/run/precompile_test.dart
@@ -38,7 +38,7 @@
 
   test('`pub run` precompiles script', () async {
     await setupForPubRunToPrecompile();
-    var pub = await pubRun(args: ['test']);
+    final pub = await pubRun(args: ['test']);
     await pub.shouldExit(0);
     final lines = await pub.stdout.rest.toList();
     expect(lines, contains('Building package executable...'));
@@ -50,7 +50,7 @@
       () async {
     await setupForPubRunToPrecompile();
 
-    var pub = await pubRun(args: ['test'], verbose: false);
+    final pub = await pubRun(args: ['test'], verbose: false);
     await pub.shouldExit(0);
     final lines = await pub.stdout.rest.toList();
     expect(lines, isNot(contains('Building package executable...')));
@@ -77,7 +77,7 @@
       environment: {'PUB_CACHE': '.pub_cache'},
     );
 
-    var pub = await pubRun(
+    final pub = await pubRun(
       args: ['test'],
       environment: {'PUB_CACHE': '.pub_cache'},
     );
@@ -106,7 +106,7 @@
       output: contains('Building package executables...'),
     );
 
-    var pub = await pubRun(
+    final pub = await pubRun(
       args: ['test'],
     );
     await pub.shouldExit(0);
@@ -136,7 +136,7 @@
       output: contains('Building package executables...'),
     );
 
-    var pub = await pubRun(
+    final pub = await pubRun(
       args: ['test'],
       environment: {'PUB_CACHE': '.pub_cache'},
     );
diff --git a/test/run/runs_app_in_entrypoint_test.dart b/test/run/runs_app_in_entrypoint_test.dart
index 6e47168..d2aba0f 100644
--- a/test/run/runs_app_in_entrypoint_test.dart
+++ b/test/run/runs_app_in_entrypoint_test.dart
@@ -25,7 +25,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['bin/script']);
+    final pub = await pubRun(args: ['bin/script']);
     expect(pub.stdout, emitsThrough('stdout output'));
     expect(pub.stderr, emits('stderr output'));
     await pub.shouldExit(123);
diff --git a/test/run/runs_named_app_in_dependency_test.dart b/test/run/runs_named_app_in_dependency_test.dart
index 25774cf..0f0cc61 100644
--- a/test/run/runs_named_app_in_dependency_test.dart
+++ b/test/run/runs_named_app_in_dependency_test.dart
@@ -23,7 +23,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['foo:bar']);
+    final pub = await pubRun(args: ['foo:bar']);
     expect(pub.stdout, emitsThrough('foobar'));
     await pub.shouldExit();
   });
diff --git a/test/run/runs_named_app_in_dev_dependency_test.dart b/test/run/runs_named_app_in_dev_dependency_test.dart
index b382399..158ddbe 100644
--- a/test/run/runs_named_app_in_dev_dependency_test.dart
+++ b/test/run/runs_named_app_in_dev_dependency_test.dart
@@ -24,7 +24,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['foo:bar']);
+    final pub = await pubRun(args: ['foo:bar']);
     expect(pub.stdout, emitsThrough('foobar'));
     await pub.shouldExit();
   });
diff --git a/test/run/runs_shorthand_app_in_dependency_test.dart b/test/run/runs_shorthand_app_in_dependency_test.dart
index 86002dc..e4d1aa3 100644
--- a/test/run/runs_shorthand_app_in_dependency_test.dart
+++ b/test/run/runs_shorthand_app_in_dependency_test.dart
@@ -24,7 +24,7 @@
     ]).create();
 
     await pubGet();
-    var pub = await pubRun(args: ['foo']);
+    final pub = await pubRun(args: ['foo']);
     expect(pub.stdout, emitsThrough('foo'));
     await pub.shouldExit();
   });
diff --git a/test/snapshot_test.dart b/test/snapshot_test.dart
index 1d515ef..c2dea39 100644
--- a/test/snapshot_test.dart
+++ b/test/snapshot_test.dart
@@ -146,7 +146,7 @@
           d.file('hello.dart-$versionSuffix.snapshot', contains('hello 2!')),
         ]).validate();
 
-        var process = await pubRun(args: ['foo:hello']);
+        final process = await pubRun(args: ['foo:hello']);
         expect(process.stdout, emits('hello 2!'));
         await process.shouldExit();
       });
@@ -206,7 +206,7 @@
           d.file('hello.dart-$versionSuffix.snapshot', contains('hello 2!')),
         ]).validate();
 
-        var process = await pubRun(args: ['foo:hello']);
+        final process = await pubRun(args: ['foo:hello']);
         expect(process.stdout, emits('hello 2!'));
         await process.shouldExit();
       });
@@ -253,7 +253,7 @@
           d.file('hello.dart-$versionSuffix.snapshot', contains('Goodbye!')),
         ]).validate();
 
-        var process = await pubRun(args: ['foo:hello']);
+        final process = await pubRun(args: ['foo:hello']);
         expect(process.stdout, emits('Goodbye!'));
         await process.shouldExit();
       });
@@ -282,7 +282,7 @@
           ),
         ]).create();
 
-        var process = await pubRun(args: ['foo:hello']);
+        final process = await pubRun(args: ['foo:hello']);
 
         // In the real world this would just print "hello!", but since we collect
         // all output we see the precompilation messages as well.
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 380c666..ad80b42 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -159,7 +159,7 @@
     throw ArgumentError("Cannot pass both 'error' and 'warning'.");
   }
 
-  var allArgs = [command.name];
+  final allArgs = [command.name];
   if (args != null) allArgs.addAll(args);
 
   output ??= command.success;
@@ -301,9 +301,9 @@
   Map<String, String>? environment,
   bool verbose = true,
 }) async {
-  var pubArgs = global ? ['global', 'run'] : ['run'];
+  final pubArgs = global ? ['global', 'run'] : ['run'];
   pubArgs.addAll(args);
-  var pub = await startPub(
+  final pub = await startPub(
     args: pubArgs,
     environment: environment,
     verbose: verbose,
@@ -358,7 +358,7 @@
   // Cannot pass both output and outputJson.
   assert(output == null || outputJson == null);
 
-  var pub = await startPub(
+  final pub = await startPub(
     args: args,
     workingDirectory: workingDirectory,
     environment: environment,
@@ -372,11 +372,11 @@
 
   await pub.shouldExit(exitCode);
 
-  var actualOutput = (await pub.stdoutStream().toList()).join('\n');
-  var actualError = (await pub.stderrStream().toList()).join('\n');
-  var actualSilent = (await pub.silentStream().toList()).join('\n');
+  final actualOutput = (await pub.stdoutStream().toList()).join('\n');
+  final actualError = (await pub.stderrStream().toList()).join('\n');
+  final actualSilent = (await pub.silentStream().toList()).join('\n');
 
-  var failures = <String>[];
+  final failures = <String>[];
   if (outputJson == null) {
     _validateOutput(failures, 'stdout', output, actualOutput);
   } else {
@@ -404,7 +404,7 @@
   String path = '',
   String? workingDirectory,
 }) async {
-  var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
+  final tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
   args = ['lish', ...?args];
   return await startPub(
     args: args,
@@ -510,7 +510,7 @@
 
   final dotPackagesPath = (await Isolate.packageConfig).toString();
 
-  var dartArgs = ['--packages=$dotPackagesPath', '--enable-asserts'];
+  final dartArgs = ['--packages=$dotPackagesPath', '--enable-asserts'];
   dartArgs
     ..addAll([pubPath, if (!verbose) '--verbosity=normal'])
     ..addAll(args);
@@ -531,7 +531,7 @@
     ...getPubTestEnvironment(tokenEndpoint),
   };
   for (final e in (environment ?? {}).entries) {
-    var value = e.value;
+    final value = e.value;
     if (value == null) {
       mergedEnvironment.remove(e.key);
     } else {
@@ -576,7 +576,7 @@
     Encoding encoding = utf8,
     bool forwardStdio = false,
   }) async {
-    var process = await Process.start(
+    final process = await Process.start(
       executable,
       arguments.toList(),
       workingDirectory: workingDirectory,
@@ -586,7 +586,7 @@
     );
 
     if (description == null) {
-      var humanExecutable = p.isWithin(p.current, executable)
+      final humanExecutable = p.isWithin(p.current, executable)
           ? p.relative(executable)
           : executable;
       description = '$humanExecutable ${arguments.join(' ')}';
@@ -627,10 +627,10 @@
   ) {
     late log.Level lastLevel;
     return stream.map((line) {
-      var match = _logLineRegExp.firstMatch(line);
+      final match = _logLineRegExp.firstMatch(line);
       if (match == null) return (defaultLevel, line);
 
-      var level = _logLevels[match[1]] ?? lastLevel;
+      final level = _logLevels[match[1]] ?? lastLevel;
       lastLevel = level;
       return (level, match[2]!);
     });
@@ -689,9 +689,9 @@
   Iterable<String>? dependenciesInSandBox,
   Map<String, String>? hosted,
 }) async {
-  var cache = SystemCache(rootDir: _pathInSandbox(cachePath));
+  final cache = SystemCache(rootDir: _pathInSandbox(cachePath));
 
-  var lockFile =
+  final lockFile =
       _createLockFile(cache, sandbox: dependenciesInSandBox, hosted: hosted);
 
   await d.dir(package, [
@@ -714,7 +714,7 @@
   Iterable<String>? sandbox,
   Map<String, String>? hosted,
 }) {
-  var dependencies = <String, dynamic>{};
+  final dependencies = <String, dynamic>{};
 
   if (sandbox != null) {
     for (var package in sandbox) {
@@ -755,7 +755,7 @@
 /// Note that this will only affect HTTP requests made via http.dart in the
 /// parent process.
 void useMockClient(MockClient client) {
-  var oldInnerClient = innerHttpClient;
+  final oldInnerClient = innerHttpClient;
   innerHttpClient = client;
   addTearDown(() {
     innerHttpClient = oldInnerClient;
@@ -771,7 +771,7 @@
   Map? devDependencies,
   Map? environment,
 ]) {
-  var package = <String, Object>{
+  final package = <String, Object>{
     'name': name,
     'version': version,
     'homepage': 'https://pub.dev',
@@ -817,8 +817,8 @@
   String expected,
   String actual,
 ) {
-  var actualLines = actual.split('\n');
-  var expectedLines = expected.split('\n');
+  final actualLines = actual.split('\n');
+  final expectedLines = expected.split('\n');
 
   // Strip off the last line. This lets us have expected multiline strings
   // where the closing ''' is on its own line. It also fixes '' expected output
@@ -827,11 +827,11 @@
     expectedLines.removeLast();
   }
 
-  var results = <String>[];
+  final results = <String>[];
   var failed = false;
 
   // Compare them line by line to see which ones match.
-  var length = max(expectedLines.length, actualLines.length);
+  final length = max(expectedLines.length, actualLines.length);
   for (var i = 0; i < length; i++) {
     if (i >= actualLines.length) {
       // Missing output.
@@ -842,8 +842,8 @@
       failed = true;
       results.add('X ${actualLines[i]}');
     } else {
-      var expectedLine = expectedLines[i].trim();
-      var actualLine = actualLines[i].trim();
+      final expectedLine = expectedLines[i].trim();
+      final actualLine = actualLines[i].trim();
 
       if (expectedLine != actualLine) {
         // Mismatched lines.
@@ -902,9 +902,9 @@
 ///
 /// Returns a scheduled Future that contains the validator after validation.
 Future<Validator> validatePackage(ValidatorCreator fn, int? size) async {
-  var cache = SystemCache(rootDir: _pathInSandbox(cachePath));
+  final cache = SystemCache(rootDir: _pathInSandbox(cachePath));
   final entrypoint = Entrypoint(_pathInSandbox(appPath), cache);
-  var validator = fn();
+  final validator = fn();
   validator.context = ValidationContext(
     entrypoint,
     await Future.value(size ?? 100),
@@ -920,7 +920,7 @@
 /// Returns a matcher that asserts that a string contains [times] distinct
 /// occurrences of [pattern], which must be a regular expression pattern.
 Matcher matchesMultiple(String pattern, int times) {
-  var buffer = StringBuffer(pattern);
+  final buffer = StringBuffer(pattern);
   for (var i = 1; i < times; i++) {
     buffer.write(r'(.|\n)*');
     buffer.write(pattern);
@@ -942,7 +942,7 @@
         RegExp(r'sha256: "?[0-9a-f]{64}"?', multiLine: true),
         r'sha256: $SHA256',
       );
-  var port = _globalServer?.port;
+  final port = _globalServer?.port;
   if (port != null) {
     input = input.replaceAll(port.toString(), '\$PORT');
   }
diff --git a/test/testdata/goldens/help_test/pub downgrade --help.txt b/test/testdata/goldens/help_test/pub downgrade --help.txt
index 483e764..a971395 100644
--- a/test/testdata/goldens/help_test/pub downgrade --help.txt
+++ b/test/testdata/goldens/help_test/pub downgrade --help.txt
@@ -11,6 +11,7 @@
     --[no-]offline       Use cached packages instead of accessing the network.
 -n, --dry-run            Report what dependencies would change but don't change any.
 -C, --directory=<dir>    Run this in the directory <dir>.
+    --tighten            Updates lower bounds in pubspec.yaml to match the resolved version.
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-downgrade for detailed documentation.
diff --git a/test/testdata/goldens/help_test/pub unpack --help.txt b/test/testdata/goldens/help_test/pub unpack --help.txt
index ba1a59d..f96e8fa 100644
--- a/test/testdata/goldens/help_test/pub unpack --help.txt
+++ b/test/testdata/goldens/help_test/pub unpack --help.txt
@@ -8,29 +8,29 @@
 
   dart pub unpack foo
 
-Downloads and extracts the latest stable package:foo from pub.dev in a
-directory `foo-<version>`.
+Downloads and extracts the latest stable version of package:foo from pub.dev
+in a directory `foo-<version>`.
 
   dart pub unpack foo:1.2.3-pre --no-resolve
 
 Downloads and extracts package:foo version 1.2.3-pre in a directory
-`foo-1.2.3-pre` without running running implicit `pub get`.
+`foo-1.2.3-pre` without running implicit `pub get`.
 
   dart pub unpack foo --output=archives
 
-Downloads and extracts latest stable version of package:foo in a directory
+Downloads and extracts the latest stable version of package:foo in a directory
 `archives/foo-<version>`.
 
   dart pub unpack 'foo:{hosted:"https://my_repo.org"}'
 
-Downloads and extracts latest stable version of package:foo from my_repo.org
+Downloads and extracts the latest stable version of package:foo from my_repo.org
 in a directory `foo-<version>`.
 
 
-Usage: pub unpack package-name[:constraint]
+Usage: pub unpack package-name[:descriptor]
 -h, --help          Print this usage information.
--f, --[no-]force    overwrites an existing folder if it exists
--o, --output        Download and extract the package in this dir
+-f, --[no-]force    Overwrite the target directory if it already exists.
+-o, --output        Download and extract the package in the specified directory.
                     (defaults to ".")
 
 Run "pub help" to see global options.
diff --git a/test/testdata/goldens/lish/many_files_test/displays all files.txt b/test/testdata/goldens/lish/many_files_test/displays all files.txt
index c9c364c..b293ff6 100644
--- a/test/testdata/goldens/lish/many_files_test/displays all files.txt
+++ b/test/testdata/goldens/lish/many_files_test/displays all files.txt
@@ -39,4 +39,4 @@
 
 Do you want to publish test_pkg 1.0.0 to http://localhost:$PORT (y/N)?
 Uploading...
-Package test_pkg 1.0.0 uploaded!
\ No newline at end of file
+Message from server: Package test_pkg 1.0.0 uploaded!
\ No newline at end of file
diff --git a/test/testdata/goldens/outdated/outdated_test/reports dependencies from all of workspace.txt b/test/testdata/goldens/outdated/outdated_test/reports dependencies from all of workspace.txt
new file mode 100644
index 0000000..c4e53af
--- /dev/null
+++ b/test/testdata/goldens/outdated/outdated_test/reports dependencies from all of workspace.txt
@@ -0,0 +1,246 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
+$ pub outdated --json
+{
+  "packages": [
+    {
+      "package": "dep",
+      "kind": "direct",
+      "isDiscontinued": false,
+      "isCurrentRetracted": false,
+      "isCurrentAffectedByAdvisory": false,
+      "current": {
+        "version": "0.8.0"
+      },
+      "upgradable": {
+        "version": "0.8.0",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "0.8.0",
+        "overridden": true
+      },
+      "latest": {
+        "version": "1.0.0"
+      }
+    },
+    {
+      "package": "dep_a",
+      "kind": "direct",
+      "isDiscontinued": false,
+      "isCurrentRetracted": false,
+      "isCurrentAffectedByAdvisory": false,
+      "current": {
+        "version": "0.9.0"
+      },
+      "upgradable": {
+        "version": "0.9.5"
+      },
+      "resolvable": {
+        "version": "1.0.0"
+      },
+      "latest": {
+        "version": "1.0.0"
+      }
+    },
+    {
+      "package": "dev_dep_a",
+      "kind": "dev",
+      "isDiscontinued": false,
+      "isCurrentRetracted": false,
+      "isCurrentAffectedByAdvisory": false,
+      "current": {
+        "version": "0.9.0"
+      },
+      "upgradable": {
+        "version": "0.9.5"
+      },
+      "resolvable": {
+        "version": "1.0.0"
+      },
+      "latest": {
+        "version": "1.0.0"
+      }
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub outdated --no-color
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable           Resolvable           Latest  
+
+direct dependencies:
+dep           *0.8.0   *0.8.0 (overridden)  *0.8.0 (overridden)  1.0.0   
+dep_a         *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+dev_dependencies:
+dev_dep_a     *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+2 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `dart pub upgrade`.
+
+2  dependencies are constrained to versions that are older than a resolvable version.
+To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
+$ pub outdated --no-color --no-transitive
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable           Resolvable           Latest  
+
+direct dependencies:
+dep           *0.8.0   *0.8.0 (overridden)  *0.8.0 (overridden)  1.0.0   
+dep_a         *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+dev_dependencies:
+dev_dep_a     *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+2 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `dart pub upgrade`.
+
+2  dependencies are constrained to versions that are older than a resolvable version.
+To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
+$ pub outdated --no-color --up-to-date
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable           Resolvable           Latest  
+
+direct dependencies:
+dep           *0.8.0   *0.8.0 (overridden)  *0.8.0 (overridden)  1.0.0   
+dep_a         *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+dev_dependencies:
+dev_dep_a     *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+2 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `dart pub upgrade`.
+
+2  dependencies are constrained to versions that are older than a resolvable version.
+To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
+$ pub outdated --no-color --prereleases
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable           Resolvable           Latest  
+
+direct dependencies:
+dep           *0.8.0   *0.8.0 (overridden)  *0.8.0 (overridden)  1.0.0   
+dep_a         *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+dev_dependencies:
+dev_dep_a     *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+2 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `dart pub upgrade`.
+
+2  dependencies are constrained to versions that are older than a resolvable version.
+To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
+$ pub outdated --no-color --no-dev-dependencies
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable           Resolvable           Latest  
+
+direct dependencies:
+dep           *0.8.0   *0.8.0 (overridden)  *0.8.0 (overridden)  1.0.0   
+dep_a         *0.9.0   *0.9.5               1.0.0                1.0.0   
+
+1 upgradable dependency is locked (in pubspec.lock) to an older version.
+To update it, use `dart pub upgrade`.
+
+1 dependency is constrained to a version that is older than a resolvable version.
+To update it, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
+$ pub outdated --no-color --no-dependency-overrides
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable  Resolvable  Latest  
+
+direct dependencies:
+dep           *0.8.0   *0.9.5      1.0.0       1.0.0   
+dep_a         *0.9.0   *0.9.5      1.0.0       1.0.0   
+
+dev_dependencies:
+dev_dep_a     *0.9.0   *0.9.5      1.0.0       1.0.0   
+
+3 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `dart pub upgrade`.
+
+3  dependencies are constrained to versions that are older than a resolvable version.
+To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
+$ pub outdated --json --no-dev-dependencies
+{
+  "packages": [
+    {
+      "package": "dep",
+      "kind": "direct",
+      "isDiscontinued": false,
+      "isCurrentRetracted": false,
+      "isCurrentAffectedByAdvisory": false,
+      "current": {
+        "version": "0.8.0"
+      },
+      "upgradable": {
+        "version": "0.8.0",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "0.8.0",
+        "overridden": true
+      },
+      "latest": {
+        "version": "1.0.0"
+      }
+    },
+    {
+      "package": "dep_a",
+      "kind": "direct",
+      "isDiscontinued": false,
+      "isCurrentRetracted": false,
+      "isCurrentAffectedByAdvisory": false,
+      "current": {
+        "version": "0.9.0"
+      },
+      "upgradable": {
+        "version": "0.9.5"
+      },
+      "resolvable": {
+        "version": "1.0.0"
+      },
+      "latest": {
+        "version": "1.0.0"
+      }
+    }
+  ]
+}
+
diff --git a/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt b/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt
index b5ea61a..0fb9eb7 100644
--- a/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt
+++ b/test/testdata/goldens/upgrade/example_warns_about_major_versions_test/pub upgrade --major-versions does not update major versions in example~.txt
@@ -2,13 +2,13 @@
 
 ## Section 0
 $ pub upgrade --major-versions --example
+
+Changed 1 constraint in pubspec.yaml:
+  bar: ^1.0.0 -> ^2.0.0
 Resolving dependencies...
 Downloading packages...
 + bar 2.0.0
 Changed 1 dependency!
-
-Changed 1 constraint in pubspec.yaml:
-  bar: ^1.0.0 -> ^2.0.0
 Resolving dependencies in `./example`...
 Downloading packages...
 Got dependencies in `./example`.
@@ -18,11 +18,11 @@
 
 ## Section 1
 $ pub upgrade --major-versions --directory example
+
+Changed 1 constraint in pubspec.yaml:
+  foo: ^1.0.0 -> ^2.0.0
 Resolving dependencies in `example`...
 Downloading packages...
 > foo 2.0.0 (was 1.0.0)
 Changed 1 dependency in `example`!
 
-Changed 1 constraint in pubspec.yaml:
-  foo: ^1.0.0 -> ^2.0.0
-
diff --git a/test/token/error_message_test.dart b/test/token/error_message_test.dart
index 26daa4d..3a98357 100644
--- a/test/token/error_message_test.dart
+++ b/test/token/error_message_test.dart
@@ -52,7 +52,7 @@
   });
 
   test('trims and prints long www-authenticate message', () async {
-    var message = List.generate(2048, (_) => 'a').join();
+    final message = List.generate(2048, (_) => 'a').join();
 
     respondWithWwwAuthenticate('bearer realm="pub", message="$message"');
     await expectPubErrorMessage(
diff --git a/test/token/token_authentication_test.dart b/test/token/token_authentication_test.dart
index dd176b6..64316c2 100644
--- a/test/token/token_authentication_test.dart
+++ b/test/token/token_authentication_test.dart
@@ -19,7 +19,7 @@
         {'url': globalServer.url, 'env': 'TOKEN'},
       ],
     }).create();
-    var pub = await startPublish(
+    final pub = await startPublish(
       globalServer,
       overrideDefaultHostedServer: false,
       environment: {'TOKEN': 'access-token'},
@@ -87,7 +87,7 @@
         {'url': globalServer.url, 'token': 'access-token'},
       ],
     }).create();
-    var pub = await startPublish(
+    final pub = await startPublish(
       globalServer,
       overrideDefaultHostedServer: false,
     );
diff --git a/test/token/when_receives_401_removes_token_test.dart b/test/token/when_receives_401_removes_token_test.dart
index ad90b33..90ec986 100644
--- a/test/token/when_receives_401_removes_token_test.dart
+++ b/test/token/when_receives_401_removes_token_test.dart
@@ -18,7 +18,7 @@
         {'url': server.url, 'token': 'access-token'},
       ],
     }).create();
-    var pub = await startPublish(server, overrideDefaultHostedServer: false);
+    final pub = await startPublish(server, overrideDefaultHostedServer: false);
     await confirmPublish(pub);
 
     server.expect('GET', '/api/packages/versions/new', (request) {
diff --git a/test/token/when_receives_403_persists_saved_token_test.dart b/test/token/when_receives_403_persists_saved_token_test.dart
index 6030855..fc800d4 100644
--- a/test/token/when_receives_403_persists_saved_token_test.dart
+++ b/test/token/when_receives_403_persists_saved_token_test.dart
@@ -18,7 +18,7 @@
         {'url': server.url, 'token': 'access-token'},
       ],
     }).create();
-    var pub = await startPublish(server, overrideDefaultHostedServer: false);
+    final pub = await startPublish(server, overrideDefaultHostedServer: false);
     await confirmPublish(pub);
 
     server.expect('GET', '/api/packages/versions/new', (request) {
diff --git a/test/transcript_test.dart b/test/transcript_test.dart
index 6c225d8..556b4ae 100644
--- a/test/transcript_test.dart
+++ b/test/transcript_test.dart
@@ -7,7 +7,7 @@
 
 void main() {
   test('discards from the middle once it reaches the maximum', () {
-    var transcript = Transcript<String>(4);
+    final transcript = Transcript<String>(4);
     String forEachToString() {
       var result = '';
       transcript.forEach((entry) => result += entry, (n) => result += '[$n]');
@@ -30,7 +30,7 @@
   });
 
   test("does not discard if it doesn't reach the maximum", () {
-    var transcript = Transcript<String>(40);
+    final transcript = Transcript<String>(40);
     String forEachToString() {
       var result = '';
       transcript.forEach((entry) => result += entry, (n) => result += '[$n]');
diff --git a/test/unpack_test.dart b/test/unpack_test.dart
index ebf83ce..21752f9 100644
--- a/test/unpack_test.dart
+++ b/test/unpack_test.dart
@@ -39,7 +39,7 @@
     await runPub(
       args: ['unpack', 'foo'],
       error:
-          'Target directory `.${s}foo-1.2.3` already exists. Use --force to overwrite',
+          'Target directory `.${s}foo-1.2.3` already exists. Use --force to overwrite.',
       exitCode: 1,
     );
     await runPub(args: ['unpack', 'foo', '--force']);
diff --git a/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart b/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
index ad5f20f..37df96f 100644
--- a/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
+++ b/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
@@ -53,7 +53,7 @@
       ]),
     ]).validate();
 
-    var originalFooDepSpec = packageSpec('foo_dep');
+    final originalFooDepSpec = packageSpec('foo_dep');
 
     await d.git('foo.git', [
       d.libDir('foo', 'foo 2'),
diff --git a/test/upgrade/git/upgrade_locked_test.dart b/test/upgrade/git/upgrade_locked_test.dart
index a12a5a6..f528a2e 100644
--- a/test/upgrade/git/upgrade_locked_test.dart
+++ b/test/upgrade/git/upgrade_locked_test.dart
@@ -41,8 +41,8 @@
       ]),
     ]).validate();
 
-    var originalFooSpec = packageSpec('foo');
-    var originalBarSpec = packageSpec('bar');
+    final originalFooSpec = packageSpec('foo');
+    final originalBarSpec = packageSpec('bar');
 
     await d.git(
       'foo.git',
diff --git a/test/upgrade/git/upgrade_one_locked_test.dart b/test/upgrade/git/upgrade_one_locked_test.dart
index eef7db2..089328c 100644
--- a/test/upgrade/git/upgrade_one_locked_test.dart
+++ b/test/upgrade/git/upgrade_one_locked_test.dart
@@ -41,7 +41,7 @@
       ]),
     ]).validate();
 
-    var originalBarSpec = packageSpec('bar');
+    final originalBarSpec = packageSpec('bar');
 
     await d.git(
       'foo.git',
diff --git a/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart b/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart
index b45a56f..10a6605 100644
--- a/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart
+++ b/test/upgrade/git/upgrade_to_incompatible_pubspec_test.dart
@@ -34,7 +34,7 @@
       ]),
     ]).validate();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     await d.git(
       'foo.git',
diff --git a/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart b/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart
index 9d95e0b..82f7ebf 100644
--- a/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart
+++ b/test/upgrade/git/upgrade_to_nonexistent_pubspec_test.dart
@@ -11,7 +11,7 @@
   test('upgrades Git packages to a nonexistent pubspec', () async {
     ensureGit();
 
-    var repo =
+    final repo =
         d.git('foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]);
     await repo.create();
 
@@ -23,7 +23,7 @@
 
     await pubGet();
 
-    var originalFooSpec = packageSpec('foo');
+    final originalFooSpec = packageSpec('foo');
 
     await repo.runGit(['rm', 'pubspec.yaml']);
     await repo.runGit(['commit', '-m', 'delete']);
diff --git a/test/utils_test.dart b/test/utils_test.dart
index 1ce8869..3d9c523 100644
--- a/test/utils_test.dart
+++ b/test/utils_test.dart
@@ -75,7 +75,7 @@
     });
 
     test('handles non-string map keys', () {
-      var map = <Object?, Object?>{};
+      final map = <Object?, Object?>{};
       map[null] = 'null';
       map[123] = 'num';
       map[true] = 'bool';
@@ -118,22 +118,22 @@
   });
 
   group('uuid', () {
-    var uuidRegexp = RegExp('^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-'
+    final uuidRegexp = RegExp('^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-'
         r'[8-9A-B][0-9A-F]{3}-[0-9A-F]{12}$');
 
     test('min value is valid', () {
-      var uuid = createUuid(List<int>.filled(16, 0));
+      final uuid = createUuid(List<int>.filled(16, 0));
       expect(uuid, matches(uuidRegexp));
       expect(uuid, '00000000-0000-4000-8000-000000000000');
     });
     test('max value is valid', () {
-      var uuid = createUuid(List<int>.filled(16, 255));
+      final uuid = createUuid(List<int>.filled(16, 255));
       expect(uuid, matches(uuidRegexp));
       expect(uuid, 'FFFFFFFF-FFFF-4FFF-BFFF-FFFFFFFFFFFF');
     });
     test('random values are valid', () {
       for (var i = 0; i < 100; i++) {
-        var uuid = createUuid();
+        final uuid = createUuid();
         expect(uuid, matches(uuidRegexp));
       }
     });
diff --git a/test/validator/directory_test.dart b/test/validator/directory_test.dart
index 47fe822..c58002b 100644
--- a/test/validator/directory_test.dart
+++ b/test/validator/directory_test.dart
@@ -43,7 +43,7 @@
       'named', () {
     setUp(d.validPackage().create);
 
-    var names = [
+    final names = [
       'benchmarks',
       'docs',
       'examples',
diff --git a/test/validator/flutter_plugin_format_test.dart b/test/validator/flutter_plugin_format_test.dart
index 0a5bd4c..5b6fcb4 100644
--- a/test/validator/flutter_plugin_format_test.dart
+++ b/test/validator/flutter_plugin_format_test.dart
@@ -20,7 +20,7 @@
     });
 
     test('is a Flutter 1.9.0 package', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -31,7 +31,7 @@
     });
 
     test('is a Flutter 1.10.0 package', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -42,7 +42,7 @@
     });
 
     test('is a Flutter 1.10.0-0 package', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -53,7 +53,7 @@
     });
 
     test('is a flutter 1.10.0 plugin with the new format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -76,7 +76,7 @@
 
   group('should consider a package invalid if it', () {
     test('is a flutter plugin with old and new format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -107,7 +107,7 @@
     });
 
     test('is a flutter 1.9.0 plugin with old format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -130,7 +130,7 @@
     });
 
     test('is a flutter 1.9.0 plugin with new format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -160,7 +160,7 @@
     test(
         'is a flutter plugin with only implicit flutter sdk version constraint and the new format',
         () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -187,7 +187,7 @@
     });
 
     test('is a non-flutter package with using the new format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {}, {}, {
+      final pkg = packageMap('test_pkg', '1.0.0', {}, {}, {
         'sdk': '>=2.0.0 <3.0.0',
       });
       pkg['flutter'] = {
@@ -212,7 +212,7 @@
     });
 
     test('is a flutter 1.8.0 plugin with new format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
@@ -240,7 +240,7 @@
     });
 
     test('is a flutter 1.9.999 plugin with new format', () async {
-      var pkg = packageMap('test_pkg', '1.0.0', {
+      final pkg = packageMap('test_pkg', '1.0.0', {
         'flutter': {'sdk': 'flutter'},
       }, {}, {
         'sdk': '>=2.0.0 <3.0.0',
diff --git a/test/validator/license_test.dart b/test/validator/license_test.dart
index 70b6b7b..474984e 100644
--- a/test/validator/license_test.dart
+++ b/test/validator/license_test.dart
@@ -73,7 +73,7 @@
     });
 
     test('has a .gitignored LICENSE file', () async {
-      var repo = d.git(appPath, [d.file('.gitignore', 'LICENSE')]);
+      final repo = d.git(appPath, [d.file('.gitignore', 'LICENSE')]);
       await d.validPackage().create();
       await repo.create();
       await expectValidationDeprecated(license, errors: isNotEmpty);
diff --git a/test/validator/pubspec_field_test.dart b/test/validator/pubspec_field_test.dart
index 694cb47..908167f 100644
--- a/test/validator/pubspec_field_test.dart
+++ b/test/validator/pubspec_field_test.dart
@@ -19,7 +19,7 @@
     test('looks normal', () => expectValidationDeprecated(pubspecField));
 
     test('has an HTTPS homepage URL', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['homepage'] = 'https://pub.dev';
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -27,7 +27,7 @@
     });
 
     test('has an HTTPS repository URL instead of homepage', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg.remove('homepage');
       pkg['repository'] = 'https://pub.dev';
       await d.dir(appPath, [d.pubspec(pkg)]).create();
@@ -36,7 +36,7 @@
     });
 
     test('has an HTTPS documentation URL', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['documentation'] = 'https://pub.dev';
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -44,7 +44,7 @@
     });
 
     test('has empty executables', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['executables'] = <String, String>{};
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -52,7 +52,7 @@
     });
 
     test('has executables', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['executables'] = <String, String?>{
         'test_pkg': null,
         'test_pkg_helper': 'helper',
@@ -66,7 +66,7 @@
   group('should warn if a package', () {
     test('is missing both the "homepage" and the "description" field',
         () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg.remove('homepage');
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -78,7 +78,7 @@
     setUp(d.validPackage().create);
 
     test('is missing the "description" field', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg.remove('description');
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -86,7 +86,7 @@
     });
 
     test('has a non-string "homepage" field', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['homepage'] = 12;
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -94,7 +94,7 @@
     });
 
     test('has a non-string "repository" field', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['repository'] = 12;
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -102,7 +102,7 @@
     });
 
     test('has a non-string "description" field', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['description'] = 12;
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -110,7 +110,7 @@
     });
 
     test('has a non-HTTP homepage URL', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['homepage'] = 'file:///foo/bar';
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -118,7 +118,7 @@
     });
 
     test('has a non-HTTP documentation URL', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['documentation'] = 'file:///foo/bar';
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -126,7 +126,7 @@
     });
 
     test('has a non-HTTP repository URL', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['repository'] = 'file:///foo/bar';
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -134,7 +134,7 @@
     });
 
     test('has invalid executables', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['executables'] = <String>['wrong-thing'];
       await d.dir(appPath, [d.pubspec(pkg)]).create();
 
@@ -142,7 +142,7 @@
     });
 
     test('has invalid executables mapping to a number', () async {
-      var pkg = packageMap('test_pkg', '1.0.0');
+      final pkg = packageMap('test_pkg', '1.0.0');
       pkg['executables'] = <String, dynamic>{
         'test_pkg': 33,
       };
diff --git a/test/validator/pubspec_test.dart b/test/validator/pubspec_test.dart
index 995f376..480033e 100644
--- a/test/validator/pubspec_test.dart
+++ b/test/validator/pubspec_test.dart
@@ -18,7 +18,7 @@
 
   test('should consider a package invalid if it has a .gitignored pubspec',
       () async {
-    var repo = d.git(appPath, [d.file('.gitignore', 'pubspec.yaml')]);
+    final repo = d.git(appPath, [d.file('.gitignore', 'pubspec.yaml')]);
     await d.validPackage().create();
     await repo.create();
 
diff --git a/test/validator/readme_test.dart b/test/validator/readme_test.dart
index 90df3a1..499c7ee 100644
--- a/test/validator/readme_test.dart
+++ b/test/validator/readme_test.dart
@@ -31,7 +31,7 @@
 
     test('has a gitignored README with invalid utf-8', () async {
       await d.validPackage().create();
-      var repo = d.git(appPath, [
+      final repo = d.git(appPath, [
         d.file('README', [192]),
         d.file('.gitignore', 'README'),
       ]);
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index bf3a4e0..8aa3e7a 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -480,7 +480,7 @@
   });
 
   test('mismatched descriptions', () async {
-    var otherServer = await startPackageServer();
+    final otherServer = await startPackageServer();
     otherServer.serve('shared', '1.0.0');
 
     await servePackages()
@@ -903,7 +903,7 @@
 
   // Like the above test, but for a conflicting description.
   test('successful backjump to conflicting description', () async {
-    var otherServer = await startPackageServer();
+    final otherServer = await startPackageServer();
     otherServer.serve('a', '1.0.0');
 
     await servePackages()
@@ -959,7 +959,7 @@
   });
 
   test('failing backjump to conflicting description', () async {
-    var otherServer = await startPackageServer();
+    final otherServer = await startPackageServer();
     otherServer.serve('a', '1.0.0');
 
     await servePackages()
@@ -1978,20 +1978,20 @@
 
   if (result == null) return;
 
-  var cache = SystemCache();
-  var registry = cache.sources;
-  var lockFile =
+  final cache = SystemCache();
+  final registry = cache.sources;
+  final lockFile =
       LockFile.load(p.join(d.sandbox, appPath, 'pubspec.lock'), registry);
-  var resultPubspec = Pubspec.fromMap(
+  final resultPubspec = Pubspec.fromMap(
     {'dependencies': result},
     registry,
     containingDescription: RootDescription('.'),
   );
 
-  var ids = {...lockFile.packages};
+  final ids = {...lockFile.packages};
   for (var dep in resultPubspec.dependencies.values) {
     expect(ids, contains(dep.name));
-    var id = ids.remove(dep.name)!;
+    final id = ids.remove(dep.name)!;
     final description = dep.description;
     if (description is HostedDescription &&
         (description.url == SystemCache().hosted.defaultUrl)) {
diff --git a/test/workspace_test.dart b/test/workspace_test.dart
index e381fb5..df00a0f 100644
--- a/test/workspace_test.dart
+++ b/test/workspace_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 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -51,6 +52,25 @@
       ],
       generatorVersion: '3.5.0',
     ).validate();
+    final workspaceRefA = jsonDecode(
+      File(
+        p.join(
+          sandbox,
+          appPath,
+          'pkgs',
+          'a',
+          '.dart_tool',
+          'pub',
+          'workspace_ref.json',
+        ),
+      ).readAsStringSync(),
+    );
+    expect(workspaceRefA, {'workspaceRoot': p.join('..', '..', '..', '..')});
+    final workspaceRefMyApp = jsonDecode(
+      File(p.join(sandbox, appPath, '.dart_tool', 'pub', 'workspace_ref.json'))
+          .readAsStringSync(),
+    );
+    expect(workspaceRefMyApp, {'workspaceRoot': p.join('..', '..')});
   });
 
   test(
@@ -327,13 +347,16 @@
     await pubGet(
       environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
       workingDirectory: p.join(sandbox, appPath, 'pkgs'),
-      output: contains('Resolving dependencies in `..`...'),
+      output: contains(
+        'Resolving dependencies in `${p.join(sandbox, appPath)}`...',
+      ),
     );
-    final s = p.separator;
     await pubGet(
       environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
       workingDirectory: p.join(sandbox, appPath, 'pkgs', 'a'),
-      output: contains('Resolving dependencies in `..$s..`...'),
+      output: contains(
+        'Resolving dependencies in `${p.join(sandbox, appPath)}`...',
+      ),
     );
 
     await pubGet(
@@ -351,7 +374,9 @@
         appPath,
         'pkgs',
       ),
-      output: contains('Resolving dependencies in `..`...'),
+      output: contains(
+        'Resolving dependencies in `${p.join(sandbox, appPath)}`...',
+      ),
     );
   });
 
@@ -1163,6 +1188,112 @@
           'Because myapp depends on both a 2.0.0 and a, version solving failed.',
     );
   });
+
+  test('Reports error if two members of workspace has same name', () async {
+    final server = await servePackages();
+    server.serve('dev_dep', '1.0.0');
+    await dir(appPath, [
+      libPubspec(
+        'myapp',
+        '1.2.3',
+        extras: {
+          'workspace': ['a', 'b'],
+        },
+        sdk: '^3.5.0',
+      ),
+      dir('a', [
+        libPubspec(
+          'a',
+          '1.0.0',
+          resolutionWorkspace: true,
+        ),
+      ]),
+      dir('b', [
+        libPubspec(
+          'a', // Has same name as sibling.
+          '1.0.0',
+          resolutionWorkspace: true,
+        ),
+      ]),
+    ]).create();
+    await pubGet(
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+      error: '''
+Workspace members must have unique names.
+`a${s}pubspec.yaml` and `b${s}pubspec.yaml` are both called "a".''',
+    );
+  });
+
+  test('Reports error if two members of workspace override the same package',
+      () async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    await dir(appPath, [
+      libPubspec(
+        'myapp',
+        '1.2.3',
+        deps: {'foo': 'any'},
+        extras: {
+          'dependency_overrides': {
+            'foo': {'path': '../foo'},
+          },
+          'workspace': ['a'],
+        },
+        sdk: '^3.5.0',
+      ),
+      dir('a', [
+        libPubspec(
+          'a',
+          '1.0.0',
+          resolutionWorkspace: true,
+        ),
+        pubspecOverrides({
+          'dependency_overrides': {'foo': '2.0.0'},
+        }),
+      ]),
+    ]).create();
+    await pubGet(
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+      error: '''
+The package `foo` is overridden in both:
+package `myapp` at `.` and 'a' at `.${s}a`.
+
+Consider removing one of the overrides.''',
+    );
+  });
+
+  test('overrides are applied', () async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    await dir('foo', [libPubspec('foo', '1.0.1')]).create();
+    await dir(appPath, [
+      libPubspec(
+        'myapp',
+        '1.2.3',
+        deps: {'foo': '1.0.0'},
+        extras: {
+          'workspace': ['a'],
+        },
+        sdk: '^3.5.0',
+      ),
+      dir('a', [
+        libPubspec(
+          'a',
+          '1.0.0',
+          extras: {
+            'dependency_overrides': {
+              'foo': {'path': '../../foo'},
+            },
+          },
+          resolutionWorkspace: true,
+        ),
+      ]),
+    ]).create();
+    await pubGet(
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+      output: contains('! foo 1.0.1 from path ..${s}foo (overridden)'),
+    );
+  });
 }
 
 final s = p.separator;
diff --git a/tool/extract_all_pub_dev.dart b/tool/extract_all_pub_dev.dart
index 9525a19..a61fcaa 100644
--- a/tool/extract_all_pub_dev.dart
+++ b/tool/extract_all_pub_dev.dart
@@ -20,7 +20,7 @@
 const statusFilename = 'extract_all_pub_status.json';
 
 Future<List<String>> allPackageNames() async {
-  var nextUrl = Uri.https('pub.dev', 'api/packages', {'compact': '1'});
+  final nextUrl = Uri.https('pub.dev', 'api/packages', {'compact': '1'});
   final request = http.Request('GET', nextUrl);
   request.attachMetadataHeaders();
   final response = await globalHttpClient.fetch(request);
@@ -40,8 +40,8 @@
 }
 
 Future<void> main() async {
-  var alreadyDonePackages = <String>{};
-  var failures = <Map<String, dynamic>?>[];
+  final alreadyDonePackages = <String>{};
+  final failures = <Map<String, dynamic>?>[];
   if (fileExists(statusFilename)) {
     final json = jsonDecode(readTextFile(statusFilename));
     for (final packageName in json['packages'] as Iterable? ?? []) {