Merge branch 'cherry-pick-for-2.17.2'
diff --git a/doc/repository-spec-v2.md b/doc/repository-spec-v2.md
index 55ccbb0..224aa2d 100644
--- a/doc/repository-spec-v2.md
+++ b/doc/repository-spec-v2.md
@@ -78,7 +78,7 @@
 coming from. Including a URL allowing operators to reach owners/authors of the
 client is good practice.
 
- * `User-Agent: my-pub-bot/1.2.3 (+https://github.com/organization/<repository)`
+ * `User-Agent: my-pub-bot/1.2.3 (+https://github.com/organization/<repository>)`
 
 The `User-Agent` header also allows package repository to determine how many
 different clients would be affected by an API change.
diff --git a/lib/src/command.dart b/lib/src/command.dart
index ee62392..240a15e 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -73,7 +73,12 @@
   ///
   /// This will load the pubspec and fail with an error if the current directory
   /// is not a package.
-  late final Entrypoint entrypoint = Entrypoint(directory, cache);
+  late final Entrypoint entrypoint =
+      Entrypoint(directory, cache, withPubspecOverrides: withPubspecOverrides);
+
+  /// Whether `pubspec_overrides.yaml` is taken into account, when creating
+  /// [entrypoint].
+  bool get withPubspecOverrides => true;
 
   /// The URL for web documentation for this command.
   String? get docUrl => null;
diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index 0980b81..196a7d6 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -94,6 +94,8 @@
         help: 'Build executables in immediate dependencies.');
     argParser.addOption('directory',
         abbr: 'C', help: 'Run this in the directory <dir>.', valueHelp: 'dir');
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
   }
 
   @override
@@ -165,7 +167,8 @@
           .acquireDependencies(SolveType.get,
               dryRun: true,
               precompile: argResults['precompile'],
-              analytics: analytics);
+              analytics: analytics,
+              generateDotPackages: false);
     } else {
       /// Update the `pubspec.yaml` before calling [acquireDependencies] to
       /// ensure that the modification timestamp on `pubspec.lock` and
@@ -180,6 +183,7 @@
         SolveType.get,
         precompile: argResults['precompile'],
         analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
 
       if (argResults['example'] && entrypoint.example != null) {
@@ -188,6 +192,7 @@
           precompile: argResults['precompile'],
           onlyReportSuccessOrFailure: true,
           analytics: analytics,
+          generateDotPackages: argResults['legacy-packages-file'],
         );
       }
     }
@@ -400,17 +405,23 @@
             'version': versionConstraintString
         };
       }
-      final packagePath = [dependencyKey, name];
 
       if (yamlEditor.parseAt(
             [dependencyKey],
             orElse: () => YamlScalar.wrap(null),
           ).value ==
           null) {
-        // Insert dependencyKey: {} if it did not exist.
-        yamlEditor.update([dependencyKey], {});
+        // Handle the case where [dependencyKey] does not already exist.
+        // We ensure it is in Block-style by default.
+        yamlEditor.update(
+            [dependencyKey],
+            wrapAsYamlNode({name: pubspecInformation},
+                collectionStyle: CollectionStyle.BLOCK));
+      } else {
+        final packagePath = [dependencyKey, name];
+
+        yamlEditor.update(packagePath, pubspecInformation);
       }
-      yamlEditor.update(packagePath, pubspecInformation);
 
       /// Remove the package from dev_dependencies if we are adding it to
       /// dependencies. Refer to [_addPackageToPubspec] for additional discussion.
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index 45833d4..5b252ae 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -8,6 +8,7 @@
 import 'dart:io';
 
 import 'package:collection/collection.dart';
+import 'package:path/path.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 import 'package:yaml_edit/yaml_edit.dart';
@@ -22,6 +23,7 @@
 import '../pubspec.dart';
 import '../pubspec_utils.dart';
 import '../solver.dart';
+import '../source/git.dart';
 import '../system_cache.dart';
 import '../utils.dart';
 
@@ -73,11 +75,11 @@
     Future<List<Object>> _computeUpgradeSet(
       Pubspec rootPubspec,
       PackageId? package, {
-      required UpgradeType upgradeType,
+      required _UpgradeType upgradeType,
     }) async {
       if (package == null) return [];
       final lockFile = entrypoint.lockFile;
-      final pubspec = upgradeType == UpgradeType.multiBreaking
+      final pubspec = upgradeType == _UpgradeType.multiBreaking
           ? stripVersionUpperBounds(rootPubspec)
           : Pubspec(
               rootPubspec.name,
@@ -86,7 +88,7 @@
               sdkConstraints: rootPubspec.sdkConstraints,
             );
 
-      final dependencySet = dependencySetOfPackage(pubspec, package);
+      final dependencySet = _dependencySetOfPackage(pubspec, package);
       if (dependencySet != null) {
         // Force the version to be the new version.
         dependencySet[package.name] =
@@ -109,38 +111,43 @@
         ...resolution.packages.where((r) {
           if (r.name == rootPubspec.name) return false;
           final originalVersion = currentPackages[r.name];
-          return originalVersion == null ||
-              r.version != originalVersion.version;
+          return originalVersion == null || r != originalVersion;
         }).map((p) {
-          final depset = dependencySetOfPackage(rootPubspec, p);
+          final depset = _dependencySetOfPackage(rootPubspec, p);
           final originalConstraint = depset?[p.name]?.constraint;
+          final currentPackage = currentPackages[p.name];
           return {
             'name': p.name,
-            'version': p.version.toString(),
+            'version': p.versionOrHash(),
             'kind': _kindString(pubspec, p.name),
+            'source': _source(p, containingDir: directory),
             'constraintBumped': originalConstraint == null
                 ? null
-                : upgradeType == UpgradeType.compatible
+                : upgradeType == _UpgradeType.compatible
                     ? originalConstraint.toString()
                     : VersionConstraint.compatibleWith(p.version).toString(),
             'constraintWidened': originalConstraint == null
                 ? null
-                : upgradeType == UpgradeType.compatible
+                : upgradeType == _UpgradeType.compatible
                     ? originalConstraint.toString()
                     : _widenConstraint(originalConstraint, p.version)
                         .toString(),
             'constraintBumpedIfNeeded': originalConstraint == null
                 ? null
-                : upgradeType == UpgradeType.compatible
+                : upgradeType == _UpgradeType.compatible
                     ? originalConstraint.toString()
                     : originalConstraint.allows(p.version)
                         ? originalConstraint.toString()
                         : VersionConstraint.compatibleWith(p.version)
                             .toString(),
-            'previousVersion': currentPackages[p.name]?.version.toString(),
+            'previousVersion': currentPackage?.versionOrHash(),
             'previousConstraint': originalConstraint?.toString(),
+            'previousSource': currentPackage == null
+                ? null
+                : _source(currentPackage, containingDir: directory),
           };
         }),
+        // Find packages that were removed by the resolution
         for (final oldPackageName in lockFile.packages.keys)
           if (!resolution.packages
               .any((newPackage) => newPackage.name == oldPackageName))
@@ -153,8 +160,10 @@
               'constraintWidened': null,
               'constraintBumpedIfNeeded': null,
               'previousVersion':
-                  currentPackages[oldPackageName]?.version.toString(),
+                  currentPackages[oldPackageName]?.versionOrHash(),
               'previousConstraint': null,
+              'previous': _source(currentPackages[oldPackageName]!,
+                  containingDir: directory)
             },
       ];
     }
@@ -172,7 +181,7 @@
         devDependencies: compatiblePubspec.devDependencies.values,
       );
       final dependencySet =
-          dependencySetOfPackage(singleBreakingPubspec, package);
+          _dependencySetOfPackage(singleBreakingPubspec, package);
       final kind = _kindString(compatiblePubspec, package.name);
       PackageId? singleBreakingVersion;
       if (dependencySet != null) {
@@ -186,25 +195,24 @@
       }
       dependencies.add({
         'name': package.name,
-        'version': package.version.toString(),
+        'version': package.versionOrHash(),
         'kind': kind,
+        'source': _source(package, containingDir: directory),
         'latest':
             (await cache.getLatest(package.toRef(), version: package.version))
-                ?.version
-                .toString(),
+                ?.versionOrHash(),
         'constraint':
             _constraintOf(compatiblePubspec, package.name)?.toString(),
-        if (compatibleVersion != null)
-          'compatible': await _computeUpgradeSet(
-              compatiblePubspec, compatibleVersion,
-              upgradeType: UpgradeType.compatible),
+        'compatible': await _computeUpgradeSet(
+            compatiblePubspec, compatibleVersion,
+            upgradeType: _UpgradeType.compatible),
         'singleBreaking': kind != 'transitive' && singleBreakingVersion == null
             ? []
             : await _computeUpgradeSet(compatiblePubspec, singleBreakingVersion,
-                upgradeType: UpgradeType.singleBreaking),
+                upgradeType: _UpgradeType.singleBreaking),
         'multiBreaking': kind != 'transitive' && multiBreakingVersion != null
             ? await _computeUpgradeSet(compatiblePubspec, multiBreakingVersion,
-                upgradeType: UpgradeType.multiBreaking)
+                upgradeType: _UpgradeType.multiBreaking)
             : [],
       });
     }
@@ -226,6 +234,14 @@
           : 'transitive';
 }
 
+Map<String, Object?> _source(PackageId id, {required String containingDir}) {
+  return {
+    'type': id.source.name,
+    'description':
+        id.description.serializeForLockfile(containingDir: containingDir),
+  };
+}
+
 /// Try to solve [pubspec] return [PackageId]s in the resolution or `null` if no
 /// resolution was found.
 Future<List<PackageId>?> _tryResolve(Pubspec pubspec, SystemCache cache) async {
@@ -265,19 +281,31 @@
     final dependencies = <Object>[];
     final result = <String, Object>{'dependencies': dependencies};
 
-    for (final package in currentPackages) {
+    for (final package in currentPackages.where((p) => !p.isRoot)) {
       dependencies.add({
         'name': package.name,
-        'version': package.version.toString(),
+        'version': package.versionOrHash(),
         'kind': _kindString(pubspec, package.name),
         'constraint': _constraintOf(pubspec, package.name).toString(),
+        'source': _source(package, containingDir: directory),
       });
     }
     log.message(JsonEncoder.withIndent('  ').convert(result));
   }
 }
 
-enum UpgradeType {
+extension on PackageId {
+  String versionOrHash() {
+    final description = this.description;
+    if (description is GitResolvedDescription) {
+      return description.resolvedRef;
+    } else {
+      return version.toString();
+    }
+  }
+}
+
+enum _UpgradeType {
   /// Only upgrade pubspec.lock.
   compatible,
 
@@ -314,7 +342,7 @@
       toApply.add(
         _PackageVersion(
           change['name'],
-          change['version'] != null ? Version.parse(change['version']) : null,
+          change['version'],
           change['constraint'] != null
               ? VersionConstraint.parse(change['constraint'])
               : null,
@@ -333,13 +361,24 @@
       final targetPackage = p.name;
       final targetVersion = p.version;
       final targetConstraint = p.constraint;
+      final targetRevision = p.gitRevision;
 
       if (targetConstraint != null) {
         final section = pubspec.dependencies[targetPackage] != null
             ? 'dependencies'
             : 'dev_dependencies';
-        pubspecEditor
-            .update([section, targetPackage], targetConstraint.toString());
+        final packageConfig =
+            pubspecEditor.parseAt([section, targetPackage]).value;
+        if (packageConfig == null || packageConfig is String) {
+          pubspecEditor
+              .update([section, targetPackage], targetConstraint.toString());
+        } else if (packageConfig is Map) {
+          pubspecEditor.update(
+              [section, targetPackage, 'version'], targetConstraint.toString());
+        } else {
+          fail(
+              'The dependency $targetPackage does not have a map or string as a description');
+        }
       } else if (targetVersion != null) {
         final constraint = _constraintOf(pubspec, targetPackage);
         if (constraint != null && !constraint.allows(targetVersion)) {
@@ -350,24 +389,53 @@
               VersionConstraint.compatibleWith(targetVersion).toString());
         }
       }
-      if (targetVersion != null &&
-          lockFileEditor != null &&
-          lockFileYaml['packages'].containsKey(targetPackage)) {
-        lockFileEditor.update(
-            ['packages', targetPackage, 'version'], targetVersion.toString());
-      }
-      if (targetVersion == null &&
-          lockFileEditor != null &&
-          !lockFileYaml['packages'].containsKey(targetPackage)) {
-        dataError(
-          'Trying to remove non-existing transitive dependency $targetPackage.',
-        );
+      if (lockFileEditor != null) {
+        if (targetVersion != null &&
+            lockFileYaml['packages'].containsKey(targetPackage)) {
+          lockFileEditor.update(
+              ['packages', targetPackage, 'version'], targetVersion.toString());
+        } else if (targetRevision != null &&
+            lockFileYaml['packages'].containsKey(targetPackage)) {
+          final ref = entrypoint.lockFile.packages[targetPackage]!.toRef();
+          final currentDescription = ref.description as GitDescription;
+          final updatedRef = PackageRef(
+              targetPackage,
+              GitDescription(
+                  url: currentDescription.url,
+                  path: currentDescription.path,
+                  ref: targetRevision,
+                  containingDir: directory));
+          final versions = await cache.getVersions(updatedRef);
+          if (versions.isEmpty) {
+            dataError(
+                'Found no versions of $targetPackage with git revision `$targetRevision`.');
+          }
+          // GitSource can only return a single version.
+          assert(versions.length == 1);
+
+          lockFileEditor.update(['packages', targetPackage, 'version'],
+              versions.single.version.toString());
+          lockFileEditor.update(
+            ['packages', targetPackage, 'description', 'resolved-ref'],
+            targetRevision,
+          );
+        } else if (targetVersion == null &&
+            targetRevision == null &&
+            !lockFileYaml['packages'].containsKey(targetPackage)) {
+          dataError(
+            'Trying to remove non-existing transitive dependency $targetPackage.',
+          );
+        }
       }
     }
 
     final updatedLockfile = lockFileEditor == null
         ? null
-        : LockFile.parse(lockFileEditor.toString(), cache.sources);
+        : LockFile.parse(
+            lockFileEditor.toString(),
+            cache.sources,
+            filePath: entrypoint.lockFilePath,
+          );
     await log.warningsOnlyUnlessTerminal(
       () async {
         final updatedPubspec = pubspecEditor.toString();
@@ -381,7 +449,8 @@
         final solveResult = await resolveVersions(
           SolveType.get,
           cache,
-          Package.inMemory(Pubspec.parse(updatedPubspec, cache.sources)),
+          Package.inMemory(Pubspec.parse(updatedPubspec, cache.sources,
+              location: toUri(entrypoint.pubspecPath))),
           lockFile: updatedLockfile,
         );
         if (pubspecEditor.edits.isNotEmpty) {
@@ -401,11 +470,31 @@
 class _PackageVersion {
   String name;
   Version? version;
+  String? gitRevision;
   VersionConstraint? constraint;
-  _PackageVersion(this.name, this.version, this.constraint);
+  _PackageVersion(this.name, String? versionOrHash, this.constraint)
+      : version =
+            versionOrHash == null ? null : _tryParseVersion(versionOrHash),
+        gitRevision =
+            versionOrHash == null ? null : _tryParseHash(versionOrHash);
 }
 
-Map<String, PackageRange>? dependencySetOfPackage(
+Version? _tryParseVersion(String v) {
+  try {
+    return Version.parse(v);
+  } on FormatException {
+    return null;
+  }
+}
+
+String? _tryParseHash(String v) {
+  if (RegExp(r'^[a-fA-F0-9]+$').hasMatch(v)) {
+    return v;
+  }
+  return null;
+}
+
+Map<String, PackageRange>? _dependencySetOfPackage(
     Pubspec pubspec, PackageId package) {
   return pubspec.dependencies.containsKey(package.name)
       ? pubspec.dependencies
@@ -421,7 +510,7 @@
     final min = original.min;
     final max = original.max;
     if (max != null && newVersion >= max) {
-      return compatibleWithIfPossible(
+      return _compatibleWithIfPossible(
         VersionRange(
           min: min,
           includeMin: original.includeMin,
@@ -430,7 +519,7 @@
       );
     }
     if (min != null && newVersion <= min) {
-      return compatibleWithIfPossible(
+      return _compatibleWithIfPossible(
         VersionRange(
             min: newVersion,
             includeMin: true,
@@ -445,7 +534,7 @@
       original, 'original', 'Must be a Version range or empty');
 }
 
-VersionConstraint compatibleWithIfPossible(VersionRange versionRange) {
+VersionConstraint _compatibleWithIfPossible(VersionRange versionRange) {
   final min = versionRange.min;
   if (min != null && min.nextBreaking.firstPreRelease == versionRange.max) {
     return VersionConstraint.compatibleWith(min);
diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart
index 274ab48..fce5ef3 100644
--- a/lib/src/command/downgrade.dart
+++ b/lib/src/command/downgrade.dart
@@ -42,6 +42,8 @@
 
     argParser.addOption('directory',
         abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
   }
 
   @override
@@ -57,6 +59,7 @@
       unlock: argResults.rest,
       dryRun: dryRun,
       analytics: analytics,
+      generateDotPackages: argResults['legacy-packages-file'],
     );
     var example = entrypoint.example;
     if (argResults['example'] && example != null) {
@@ -66,6 +69,7 @@
         dryRun: dryRun,
         onlyReportSuccessOrFailure: true,
         analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
     }
 
diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart
index 302bb93..7b8906c 100644
--- a/lib/src/command/get.dart
+++ b/lib/src/command/get.dart
@@ -33,6 +33,9 @@
 
     argParser.addFlag('packages-dir', hide: true);
 
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
+
     argParser.addFlag(
       'example',
       help: 'Also run in `example/` (if it exists).',
@@ -53,16 +56,20 @@
       SolveType.get,
       dryRun: argResults['dry-run'],
       precompile: argResults['precompile'],
+      generateDotPackages: argResults['legacy-packages-file'],
       analytics: analytics,
     );
 
     var example = entrypoint.example;
     if (argResults['example'] && example != null) {
-      await example.acquireDependencies(SolveType.get,
-          dryRun: argResults['dry-run'],
-          precompile: argResults['precompile'],
-          onlyReportSuccessOrFailure: true,
-          analytics: analytics);
+      await example.acquireDependencies(
+        SolveType.get,
+        dryRun: argResults['dry-run'],
+        precompile: argResults['precompile'],
+        generateDotPackages: argResults['legacy-packages-file'],
+        analytics: analytics,
+        onlyReportSuccessOrFailure: true,
+      );
     }
   }
 }
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index a20fbdf..947c886 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -32,6 +32,8 @@
   String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-lish';
   @override
   bool get takesArguments => false;
+  @override
+  bool get withPubspecOverrides => false;
 
   /// The URL of the server to which to upload the package.
   late final Uri host = () {
@@ -169,8 +171,8 @@
       };
 
       // Using OAuth2 authentication client for the official pub servers
-      final isOfficalServer = officialPubServers.contains(host.toString());
-      if (isOfficalServer && !cache.tokenStore.hasCredential(host)) {
+      final isOfficialServer = officialPubServers.contains(host.toString());
+      if (isOfficialServer && !cache.tokenStore.hasCredential(host)) {
         // Using OAuth2 authentication client for the official pub servers, when
         // we don't have an explicit token from [TokenStore] to use instead.
         //
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index a291198..5c275e4 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -211,6 +211,11 @@
         latestIsOverridden = true;
       }
 
+      final packageStatus = await current?.source.status(current, cache);
+      final discontinued =
+          packageStatus == null ? false : packageStatus.isDiscontinued;
+      final discontinuedReplacedBy = packageStatus?.discontinuedReplacedBy;
+
       return _PackageDetails(
         name,
         await _describeVersion(
@@ -230,6 +235,8 @@
           latestIsOverridden,
         ),
         _kind(name, entrypoint, nonDevDependencies),
+        discontinued,
+        discontinuedReplacedBy,
       );
     }
 
@@ -401,6 +408,7 @@
           ...(rows..sort((a, b) => a.name.compareTo(b.name)))
               .map((packageDetails) => {
                     'package': packageDetails.name,
+                    'isDiscontinued': packageDetails.isDiscontinued,
                     'current': markedRows[packageDetails]![0].toJson(),
                     'upgradable': markedRows[packageDetails]![1].toJson(),
                     'resolvable': markedRows[packageDetails]![2].toJson(),
@@ -575,6 +583,17 @@
           'To update these dependencies, ${mode.upgradeConstrained}.');
     }
   }
+  if (rows.any((package) => package.isDiscontinued)) {
+    log.message('\n');
+    for (var package in rows.where((package) => package.isDiscontinued)) {
+      log.message(log.bold(package.name));
+      final replacedByText = package.discontinuedReplacedBy != null
+          ? ', replaced by ${package.discontinuedReplacedBy}.'
+          : '.';
+      log.message(
+          '    Package ${package.name} has been discontinued$replacedByText');
+    }
+  }
 }
 
 abstract class Mode {
@@ -633,12 +652,17 @@
       ]) {
         String Function(String)? color;
         String? prefix;
+        String? suffix;
         var asDesired = false;
         if (versionDetails != null) {
           final isLatest = versionDetails == packageDetails.latest;
           if (isLatest) {
             color = versionDetails == previous ? color = log.gray : null;
             asDesired = true;
+            if (packageDetails.isDiscontinued &&
+                identical(versionDetails, packageDetails.latest)) {
+              suffix = ' (discontinued)';
+            }
           } else {
             color = log.red;
           }
@@ -650,6 +674,7 @@
             asDesired: asDesired,
             format: color,
             prefix: prefix,
+            suffix: suffix,
           ),
         );
         previous = versionDetails;
@@ -736,9 +761,14 @@
           (versionDetails) {
             String Function(String)? color;
             String? prefix;
+            String? suffix;
             MapEntry<String, Object>? jsonExplanation;
             var asDesired = false;
             if (versionDetails != null) {
+              if (packageDetails.isDiscontinued &&
+                  identical(versionDetails, packageDetails.latest)) {
+                suffix = ' (discontinued)';
+              }
               if (nullSafetyMap[versionDetails._id]!) {
                 color = log.green;
                 prefix = _compliantEmoji;
@@ -755,6 +785,7 @@
               asDesired: asDesired,
               format: color,
               prefix: prefix,
+              suffix: suffix,
               jsonExplanation: jsonExplanation,
             );
           },
@@ -818,9 +849,11 @@
   final _VersionDetails? resolvable;
   final _VersionDetails? latest;
   final _DependencyKind kind;
+  final bool isDiscontinued;
+  final String? discontinuedReplacedBy;
 
   _PackageDetails(this.name, this.current, this.upgradable, this.resolvable,
-      this.latest, this.kind);
+      this.latest, this.kind, this.isDiscontinued, this.discontinuedReplacedBy);
 
   @override
   int compareTo(_PackageDetails other) {
@@ -837,6 +870,8 @@
       'upgradable': upgradable?.toJson(),
       'resolvable': resolvable?.toJson(),
       'latest': latest?.toJson(),
+      'isDiscontinued': isDiscontinued,
+      'discontinuedReplacedBy': discontinuedReplacedBy,
     };
   }
 }
@@ -880,6 +915,7 @@
   final _VersionDetails? _versionDetails;
   final String Function(String)? _format;
   final String? _prefix;
+  final String? _suffix;
 
   /// This should be true if the mode creating this consideres the version as
   /// "good".
@@ -893,15 +929,18 @@
     required this.asDesired,
     format,
     prefix = '',
+    suffix = '',
     jsonExplanation,
   })  : _format = format,
         _prefix = prefix,
+        _suffix = suffix,
         _jsonExplanation = jsonExplanation;
 
   _FormattedString toHuman() => _FormattedString(
         _versionDetails?.describe ?? '-',
         format: _format,
         prefix: _prefix,
+        suffix: _suffix,
       );
 
   Object? toJson() {
@@ -923,16 +962,22 @@
   /// A prefix for marking this string if colors are not used.
   final String _prefix;
 
-  _FormattedString(this.value, {String Function(String)? format, prefix})
+  final String _suffix;
+
+  _FormattedString(this.value,
+      {String Function(String)? format, prefix, suffix})
       : _format = format ?? _noFormat,
-        _prefix = prefix ?? '';
+        _prefix = prefix ?? '',
+        _suffix = suffix ?? '';
 
   String formatted({required bool useColors}) {
-    return useColors ? _format(_prefix + value) : _prefix + value;
+    return useColors
+        ? _format(_prefix + value + _suffix)
+        : _prefix + value + _suffix;
   }
 
   int computeLength({required bool? useColors}) {
-    return _prefix.length + value.length;
+    return _prefix.length + value.length + _suffix.length;
   }
 
   static String _noFormat(String x) => x;
diff --git a/lib/src/command/remove.dart b/lib/src/command/remove.dart
index f3b27bd..82a9547 100644
--- a/lib/src/command/remove.dart
+++ b/lib/src/command/remove.dart
@@ -50,6 +50,9 @@
 
     argParser.addOption('directory',
         abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
+
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
   }
 
   @override
@@ -69,7 +72,8 @@
           .acquireDependencies(SolveType.get,
               precompile: argResults['precompile'],
               dryRun: true,
-              analytics: null);
+              analytics: null,
+              generateDotPackages: false);
     } else {
       /// Update the pubspec.
       _writeRemovalToPubspec(packages);
@@ -81,6 +85,7 @@
         SolveType.get,
         precompile: argResults['precompile'],
         analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
 
       var example = entrypoint.example;
@@ -90,6 +95,7 @@
           precompile: argResults['precompile'],
           onlyReportSuccessOrFailure: true,
           analytics: analytics,
+          generateDotPackages: argResults['legacy-packages-file'],
         );
       }
     }
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 927d849..af0c6e1 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -56,6 +56,9 @@
 
     argParser.addFlag('packages-dir', hide: true);
 
+    argParser.addFlag('legacy-packages-file',
+        help: 'Generate the legacy ".packages" file', negatable: false);
+
     argParser.addFlag(
       'major-versions',
       help: 'Upgrades packages to their latest resolvable versions, '
@@ -80,6 +83,8 @@
 
   bool get _precompile => argResults['precompile'];
 
+  bool get _packagesFile => argResults['legacy-packages-file'];
+
   bool get _upgradeNullSafety =>
       argResults['nullsafety'] || argResults['null-safety'];
 
@@ -126,6 +131,7 @@
       dryRun: _dryRun,
       precompile: _precompile,
       onlyReportSuccessOrFailure: onlySummary,
+      generateDotPackages: _packagesFile,
       analytics: analytics,
     );
     _showOfflineWarning();
@@ -237,6 +243,7 @@
         dryRun: true,
         precompile: _precompile,
         analytics: null, // No analytics for dry-run
+        generateDotPackages: false,
       );
     } else {
       if (changes.isNotEmpty) {
@@ -249,6 +256,7 @@
         SolveType.get,
         precompile: _precompile,
         analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
     }
 
@@ -333,6 +341,7 @@
         dryRun: true,
         precompile: _precompile,
         analytics: null,
+        generateDotPackages: false,
       );
     } else {
       if (changes.isNotEmpty) {
@@ -345,6 +354,7 @@
         SolveType.upgrade,
         precompile: _precompile,
         analytics: analytics,
+        generateDotPackages: argResults['legacy-packages-file'],
       );
     }
 
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 42b3732..c17d7e8 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -5,10 +5,12 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:math';
 
 import 'package:collection/collection.dart';
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
+import 'package:pool/pool.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 
@@ -185,9 +187,10 @@
   /// Loads the entrypoint from a package at [rootDir].
   Entrypoint(
     String rootDir,
-    this.cache,
-  )   : root = Package.load(null, rootDir, cache.sources,
-            withPubspecOverrides: true),
+    this.cache, {
+    bool withPubspecOverrides = true,
+  })  : root = Package.load(null, rootDir, cache.sources,
+            withPubspecOverrides: withPubspecOverrides),
         globalDir = null;
 
   Entrypoint.inMemory(this.root, this.cache,
@@ -222,13 +225,17 @@
   Entrypoint? _example;
 
   /// Writes .packages and .dart_tool/package_config.json
-  Future<void> writePackagesFiles() async {
+  Future<void> writePackagesFiles({bool generateDotPackages = false}) async {
     final entrypointName = isGlobal ? null : root.name;
-    writeTextFile(
-        packagesFile,
-        lockFile.packagesFile(cache,
-            entrypoint: entrypointName,
-            relativeFrom: isGlobal ? null : root.dir));
+    if (generateDotPackages) {
+      writeTextFile(
+          packagesFile,
+          lockFile.packagesFile(cache,
+              entrypoint: entrypointName,
+              relativeFrom: isGlobal ? null : root.dir));
+    } else {
+      tryDeleteEntry(packagesFile);
+    }
     ensureDir(p.dirname(packageConfigFile));
     writeTextFile(
         packageConfigFile,
@@ -266,6 +273,7 @@
     Iterable<String>? unlock,
     bool dryRun = false,
     bool precompile = false,
+    required bool generateDotPackages,
     required PubAnalytics? analytics,
     bool onlyReportSuccessOrFailure = false,
   }) async {
@@ -339,7 +347,7 @@
       /// have to reload and reparse all the pubspecs.
       _packageGraph = PackageGraph.fromSolveResult(this, result);
 
-      await writePackagesFiles();
+      await writePackagesFiles(generateDotPackages: generateDotPackages);
 
       try {
         if (precompile) {
@@ -396,10 +404,13 @@
       } else {
         ensureDir(_snapshotPath);
       }
-      return waitAndPrintErrors(executables.map((executable) {
-        var dir = p.dirname(pathOfExecutable(executable));
-        cleanDir(dir);
-        return _precompileExecutable(executable);
+      // Don't do more than `Platform.numberOfProcessors - 1` compilations
+      // concurrently. Though at least one.
+      final pool = Pool(max(Platform.numberOfProcessors - 1, 1));
+      return waitAndPrintErrors(executables.map((executable) async {
+        await pool.withResource(() async {
+          return _precompileExecutable(executable);
+        });
       }));
     });
   }
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 5d8ea8b..3f8f6a9 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -308,6 +308,7 @@
         () => entrypoint.acquireDependencies(
           SolveType.get,
           analytics: analytics,
+          generateDotPackages: false,
         ),
       );
     } on ApplicationException catch (e) {
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index cc0bfdf..81c34f5 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -92,7 +92,7 @@
     String? path,
     String? ref,
   }) async {
-    var name = await cache.git.getPackageNameFromRepo(repo, cache);
+    var name = await cache.git.getPackageNameFromRepo(repo, ref, path, cache);
 
     // TODO(nweiz): Add some special handling for git repos that contain path
     // dependencies. Their executables shouldn't be cached, and there should
@@ -159,7 +159,11 @@
     var entrypoint = Entrypoint(path, cache);
 
     // Get the package's dependencies.
-    await entrypoint.acquireDependencies(SolveType.get, analytics: analytics);
+    await entrypoint.acquireDependencies(
+      SolveType.get,
+      analytics: analytics,
+      generateDotPackages: false,
+    );
     var name = entrypoint.root.name;
     _describeActive(name, cache);
 
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 2faf85f..0b382d1 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -493,9 +493,9 @@
       // #define	ENOTEMPTY	39	/* Directory not empty */
       // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/errno.h#n20
       (Platform.isLinux && errorCode == 39) ||
-          // On Windows this may fail with ERROR_DIR_NOT_EMPTY
+          // On Windows this may fail with ERROR_DIR_NOT_EMPTY or ERROR_ALREADY_EXISTS
           // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
-          (Platform.isWindows && errorCode == 145) ||
+          (Platform.isWindows && (errorCode == 145 || errorCode == 183)) ||
           // On MacOS rename will fail with ENOTEMPTY if directory exists.
           // #define ENOTEMPTY       66              /* Directory not empty */
           // https://github.com/apple-oss-distributions/xnu/blob/bb611c8fecc755a0d8e56e2fa51513527c5b7a0e/bsd/sys/errno.h#L190
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart
index 0efc3af..0950de1 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -81,8 +81,12 @@
   }
 
   /// Parses a lockfile whose text is [contents].
-  factory LockFile.parse(String contents, SourceRegistry sources) {
-    return LockFile._parse(null, contents, sources);
+  ///
+  /// If [filePath] is given, path-dependencies will be interpreted relative to
+  /// that.
+  factory LockFile.parse(String contents, SourceRegistry sources,
+      {String? filePath}) {
+    return LockFile._parse(filePath, contents, sources);
   }
 
   /// Parses the lockfile whose text is [contents].
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index 0493dcc..d64808b 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+
 import 'package:pub_semver/pub_semver.dart';
 
 import 'language_version.dart';
@@ -160,8 +162,6 @@
   /// Given as `<major>.<minor>` version, similar to the `// @dart = X.Y`
   /// comment. This is derived from the lower-bound on the Dart SDK requirement
   /// in the `pubspec.yaml` for the given package.
-  ///
-  /// `null` if not given.
   LanguageVersion? languageVersion;
 
   /// Additional properties not in the specification for the
@@ -173,10 +173,8 @@
     required this.rootUri,
     this.packageUri,
     this.languageVersion,
-    this.additionalProperties,
-  }) {
-    additionalProperties ??= {};
-  }
+    this.additionalProperties = const {},
+  });
 
   /// Create [PackageConfigEntry] from JSON [data].
   ///
@@ -249,7 +247,13 @@
   Map<String, Object?> toJson() => {
         'name': name,
         'rootUri': rootUri.toString(),
-        if (packageUri != null) 'packageUri': packageUri?.toString(),
+        if (packageUri != null) 'packageUri': packageUri.toString(),
         if (languageVersion != null) 'languageVersion': '$languageVersion',
       }..addAll(additionalProperties ?? {});
+
+  @override
+  String toString() {
+    // TODO: implement toString
+    return JsonEncoder.withIndent('  ').convert(toJson());
+  }
 }
diff --git a/lib/src/pubspec_utils.dart b/lib/src/pubspec_utils.dart
index 014c8a7..cd28e21 100644
--- a/lib/src/pubspec_utils.dart
+++ b/lib/src/pubspec_utils.dart
@@ -117,9 +117,7 @@
       final packageRange = constrained[name]!;
       var unconstrainedRange = packageRange;
 
-      /// We only need to remove the upper bound if it is a hosted package.
-      if (packageRange.description is HostedDescription &&
-          (stripOnly!.isEmpty || stripOnly.contains(packageRange.name))) {
+      if (stripOnly!.isEmpty || stripOnly.contains(packageRange.name)) {
         unconstrainedRange = PackageRange(
           packageRange.toRef(),
           stripUpperBound(packageRange.constraint),
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 44b3fee..cb3d3f8 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -196,11 +196,13 @@
 
   /// Given a Git repo that contains a pub package, gets the name of the pub
   /// package.
-  Future<String> getPackageNameFromRepo(String repo, SystemCache cache) {
+  Future<String> getPackageNameFromRepo(
+      String repo, String? ref, String? path, SystemCache cache) {
     // Clone the repo to a temp directory.
     return withTempDir((tempDir) async {
       await _clone(repo, tempDir, shallow: true);
-      var pubspec = Pubspec.load(tempDir, cache.sources);
+      if (ref != null) await _checkOut(tempDir, ref);
+      var pubspec = Pubspec.load(p.join(tempDir, path), cache.sources);
       return pubspec.name;
     });
   }
@@ -716,6 +718,13 @@
         other.path == path;
   }
 
+  GitDescription withRef(String newRef) => GitDescription._(
+        url: url,
+        relative: relative,
+        ref: newRef,
+        path: path,
+      );
+
   @override
   int get hashCode => Object.hash(url, ref, path);
 
@@ -751,8 +760,8 @@
   @override
   Object? serializeForLockfile({required String? containingDir}) {
     final url = description.relative && containingDir != null
-        ? p.url
-            .relative(description.url, from: Uri.file(containingDir).toString())
+        ? p.url.relative(description.url,
+            from: Uri.file(p.absolute(containingDir)).toString())
         : description.url;
     return {
       'url': url,
diff --git a/lib/src/validator/strict_dependencies.dart b/lib/src/validator/strict_dependencies.dart
index 84f7a09..ea481f5 100644
--- a/lib/src/validator/strict_dependencies.dart
+++ b/lib/src/validator/strict_dependencies.dart
@@ -42,11 +42,9 @@
 
       for (var directive in directives) {
         Uri? url;
-        try {
-          url = Uri.parse(directive.uri.stringValue!);
-        } on FormatException catch (_) {
-          // Ignore a format exception. [url] will be null, and we'll emit an
-          // "Invalid URL" warning below.
+        final uriString = directive.uri.stringValue;
+        if (uriString != null) {
+          url = Uri.tryParse(uriString);
         }
 
         // If the URL could not be parsed or it is a `package:` URL AND there
diff --git a/pubspec.yaml b/pubspec.yaml
index 54090a5..f39e27a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -6,7 +6,7 @@
 dependencies:
   # Note: Pub's test infrastructure assumes that any dependencies used in tests
   # will be hosted dependencies.
-  analyzer: ^3.3.1
+  analyzer: ^4.0.0
   args: ^2.1.0
   async: ^2.6.1
   cli_util: ^0.3.5
diff --git a/test/add/common/add_test.dart b/test/add/common/add_test.dart
index 1a1e9df..02459cc 100644
--- a/test/add/common/add_test.dart
+++ b/test/add/common/add_test.dart
@@ -7,6 +7,7 @@
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
@@ -46,7 +47,9 @@
       await pubAdd(args: ['foo:1.2.3']);
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
       await d.appDir({'foo': '1.2.3'}).validate();
     });
 
@@ -62,8 +65,11 @@
 
       await d.cacheDir(
           {'foo': '1.2.3', 'bar': '1.1.0', 'baz': '2.5.3'}).validate();
-      await d.appPackagesFile(
-          {'foo': '1.2.3', 'bar': '1.1.0', 'baz': '2.5.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+        d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+        d.packageConfigEntry(name: 'baz', version: '2.5.3'),
+      ]).validate();
       await d
           .appDir({'foo': '1.2.3', 'bar': '1.1.0', 'baz': '2.5.3'}).validate();
     });
@@ -90,7 +96,9 @@
       await pubAdd(args: ['foo:1.2.3']);
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
 
       await d.dir(appPath, [
         d.pubspec({
@@ -130,13 +138,52 @@
       server.serve('foo', '1.2.3');
 
       await d.dir(appPath, [
-        d.pubspec({'name': 'myapp'})
+        d.file('pubspec.yaml', '''
+name: myapp
+environment:
+  "sdk": ">=0.1.2 <1.0.0"
+''')
+      ]).create();
+
+      await pubAdd(args: ['foo:1.2.3']);
+      print(
+          File(p.join(d.sandbox, appPath, 'pubspec.yaml')).readAsStringSync());
+      final yaml = loadYaml(
+          File(p.join(d.sandbox, appPath, 'pubspec.yaml')).readAsStringSync());
+
+      expect(((yaml as YamlMap).nodes['dependencies'] as YamlMap).style,
+          CollectionStyle.BLOCK,
+          reason: 'Should create the mapping with block-style by default');
+      await d.cacheDir({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
+      await d.appDir({'foo': '1.2.3'}).validate();
+    });
+
+    test('Inserts correctly when the pubspec is flow-style at top-level',
+        () async {
+      final server = await servePackages();
+      server.serve('foo', '1.2.3');
+
+      await d.dir(appPath, [
+        d.file('pubspec.yaml',
+            '{"name":"myapp", "environment": {"sdk": ">=0.1.2 <1.0.0"}}')
       ]).create();
 
       await pubAdd(args: ['foo:1.2.3']);
 
+      final yaml = loadYaml(
+          File(p.join(d.sandbox, appPath, 'pubspec.yaml')).readAsStringSync());
+
+      expect(((yaml as YamlMap).nodes['dependencies'] as YamlMap).style,
+          CollectionStyle.FLOW,
+          reason: 'Should not break a pubspec in flow-style');
+
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
       await d.appDir({'foo': '1.2.3'}).validate();
     });
 
@@ -217,7 +264,9 @@
               'adding it to dependencies instead.'));
 
       await d.cacheDir({'foo': '1.2.3'}).validate();
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
 
       await d.dir(appPath, [
         d.pubspec({
@@ -244,7 +293,9 @@
         await pubAdd(args: ['foo']);
 
         await d.cacheDir({'foo': '1.2.2'}).validate();
-        await d.appPackagesFile({'foo': '1.2.2'}).validate();
+        await d.appPackageConfigFile([
+          d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+        ]).validate();
         await d.dir(appPath, [
           d.pubspec({
             'name': 'myapp',
@@ -456,7 +507,9 @@
 
       await pubAdd(args: ['--dev', 'foo:1.2.3']);
 
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
 
       await d.dir(appPath, [
         d.pubspec({
@@ -566,7 +619,9 @@
         await pubAdd(args: ['foo', '--dev']);
 
         await d.cacheDir({'foo': '1.2.2'}).validate();
-        await d.appPackagesFile({'foo': '1.2.2'}).validate();
+        await d.appPackageConfigFile([
+          d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+        ]).validate();
         await d.dir(appPath, [
           d.pubspec({
             'name': 'myapp',
diff --git a/test/add/common/version_constraint_test.dart b/test/add/common/version_constraint_test.dart
index 546642f..f058082 100644
--- a/test/add/common/version_constraint_test.dart
+++ b/test/add/common/version_constraint_test.dart
@@ -22,7 +22,9 @@
     await pubAdd(args: ['foo']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': '^1.2.3'}).validate();
   });
 
@@ -35,7 +37,9 @@
     await pubAdd(args: ['foo:1.2.3']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': '1.2.3'}).validate();
   });
 
@@ -48,7 +52,9 @@
     await pubAdd(args: ['foo:1.2.3-dev']);
 
     await d.cacheDir({'foo': '1.2.3-dev'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3-dev'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3-dev'),
+    ]).validate();
     await d.appDir({'foo': '1.2.3-dev'}).validate();
   });
 
@@ -65,7 +71,9 @@
     await pubAdd(args: ['foo:any']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': 'any'}).validate();
   });
 
@@ -78,7 +86,9 @@
     await pubAdd(args: ['foo:>1.2.0 <2.0.0']);
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'foo': '>1.2.0 <2.0.0'}).validate();
   });
 
@@ -98,7 +108,10 @@
     await d.appDir({'foo': '^0.1.0', 'bar': '2.0.3'}).validate();
 
     await d.cacheDir({'foo': '0.1.0', 'bar': '2.0.3'}).validate();
-    await d.appPackagesFile({'foo': '0.1.0', 'bar': '2.0.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '0.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.3'),
+    ]).validate();
   });
 
   group('does not update pubspec if no available version found', () {
diff --git a/test/add/common/version_resolution_test.dart b/test/add/common/version_resolution_test.dart
index 64b393a..0f44d2b 100644
--- a/test/add/common/version_resolution_test.dart
+++ b/test/add/common/version_resolution_test.dart
@@ -32,7 +32,10 @@
 
     await d.appDir({'foo': '^3.5.0', 'bar': '1.0.0'}).validate();
     await d.cacheDir({'foo': '3.5.0', 'bar': '1.0.0'}).validate();
-    await d.appPackagesFile({'foo': '3.5.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '3.5.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('chooses the appropriate version to not break other dependencies',
@@ -54,7 +57,10 @@
 
     await d.appDir({'foo': '^3.2.1', 'bar': '1.0.0'}).validate();
     await d.cacheDir({'foo': '3.2.1', 'bar': '1.0.0'}).validate();
-    await d.appPackagesFile({'foo': '3.2.1', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '3.2.1'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('may upgrade other packages if they allow a later version to be chosen',
@@ -78,6 +84,9 @@
 
     await d.appDir({'foo': '^4.0.0', 'bar': '^1.0.0'}).validate();
     await d.cacheDir({'foo': '4.0.0', 'bar': '1.5.0'}).validate();
-    await d.appPackagesFile({'foo': '4.0.0', 'bar': '1.5.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '4.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.5.0'),
+    ]).validate();
   });
 }
diff --git a/test/add/git/git_test.dart b/test/add/git/git_test.dart
index 00bd334..d976172 100644
--- a/test/add/git/git_test.dart
+++ b/test/add/git/git_test.dart
@@ -173,7 +173,9 @@
     await pubAdd(args: ['foo', '--git-url', '../foo.git']);
 
     await d.cacheDir({'foo': '1.2.2'}).validate();
-    await d.appPackagesFile({'foo': '1.2.2'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+    ]).validate();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
diff --git a/test/add/git/subdir_test.dart b/test/add/git/subdir_test.dart
index 0f78069..8e8c39c 100644
--- a/test/add/git/subdir_test.dart
+++ b/test/add/git/subdir_test.dart
@@ -30,10 +30,11 @@
         ])
       ])
     ]).validate();
-
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')),
+    ]).validate();
 
     await d.appDir({
       'sub': {
@@ -68,9 +69,11 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir')),
+    ]).validate();
 
     await d.appDir({
       'sub': {
diff --git a/test/add/hosted/non_default_pub_server_test.dart b/test/add/hosted/non_default_pub_server_test.dart
index c3f7afa..787bf32 100644
--- a/test/add/hosted/non_default_pub_server_test.dart
+++ b/test/add/hosted/non_default_pub_server_test.dart
@@ -27,7 +27,9 @@
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
 
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+    ]).validate();
 
     await d.appDir({
       'foo': {
@@ -60,8 +62,11 @@
     await d.cacheDir({'foo': '1.2.3', 'bar': '3.2.3', 'baz': '1.3.5'},
         port: server.port).validate();
 
-    await d.appPackagesFile(
-        {'foo': '1.2.3', 'bar': '3.2.3', 'baz': '1.3.5'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+      d.packageConfigEntry(name: 'bar', version: '3.2.3', server: server),
+      d.packageConfigEntry(name: 'baz', version: '1.3.5', server: server),
+    ]).validate();
 
     await d.appDir({
       'foo': {
@@ -121,7 +126,9 @@
     await pubAdd(args: ['foo', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': '^1.2.3',
@@ -148,7 +155,9 @@
     await pubAdd(args: ['foo', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': '^1.2.3',
@@ -176,7 +185,9 @@
     await pubAdd(args: ['foo:any', '--hosted-url', url]);
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+    ]).validate();
     await d.appDir({
       'foo': {
         'version': 'any',
diff --git a/test/add/path/absolute_path_test.dart b/test/add/path/absolute_path_test.dart
index b15ec2d..5e63679 100644
--- a/test/add/path/absolute_path_test.dart
+++ b/test/add/path/absolute_path_test.dart
@@ -20,7 +20,9 @@
 
     await pubAdd(args: ['foo', '--path', absolutePath]);
 
-    await d.appPackagesFile({'foo': absolutePath}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: absolutePath),
+    ]).validate();
 
     await d.appDir({
       'foo': {'path': absolutePath}
@@ -127,7 +129,9 @@
     await pubAdd(args: ['foo', '--path', absolutePath]);
 
     await d.cacheDir({'foo': '1.2.2'}).validate();
-    await d.appPackagesFile({'foo': '1.2.2'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+    ]).validate();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
diff --git a/test/add/path/relative_path_test.dart b/test/add/path/relative_path_test.dart
index c02b644..e08ba3c 100644
--- a/test/add/path/relative_path_test.dart
+++ b/test/add/path/relative_path_test.dart
@@ -19,7 +19,9 @@
 
     await pubAdd(args: ['foo', '--path', '../foo']);
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
 
     await d.appDir({
       'foo': {'path': '../foo'}
@@ -38,7 +40,9 @@
       output: contains('Changed 1 dependency in myapp!'),
     );
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
 
     await d.appDir({
       'foo': {'path': '../foo'}
@@ -118,7 +122,9 @@
     await pubAdd(args: ['foo', '--path', '../foo']);
 
     await d.cacheDir({'foo': '1.2.2'}).validate();
-    await d.appPackagesFile({'foo': '1.2.2'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+    ]).validate();
     await d.dir(appPath, [
       d.pubspec({
         'name': 'myapp',
diff --git a/test/add/sdk/sdk_test.dart b/test/add/sdk/sdk_test.dart
index 1a98b78..7c81727 100644
--- a/test/add/sdk/sdk_test.dart
+++ b/test/add/sdk/sdk_test.dart
@@ -42,11 +42,12 @@
           'foo': {'sdk': 'flutter'}
         }
       }),
-      d.packagesFile({
-        'myapp': '.',
-        'foo': p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-        'bar': '1.0.0'
-      })
+    ]).validate();
+
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'foo', path: p.join(d.sandbox, 'flutter', 'packages', 'foo')),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
     ]).validate();
   });
 
@@ -65,11 +66,11 @@
           'foo': {'sdk': 'flutter', 'version': '0.0.1'}
         }
       }),
-      d.packagesFile({
-        'myapp': '.',
-        'foo': p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-        'bar': '1.0.0'
-      })
+    ]).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'foo', path: p.join(d.sandbox, 'flutter', 'packages', 'foo')),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
     ]).validate();
   });
 
@@ -79,11 +80,10 @@
         args: ['baz', '--sdk', 'flutter'],
         environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
 
-    await d.dir(appPath, [
-      d.packagesFile({
-        'myapp': '.',
-        'baz': p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz')
-      })
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'baz',
+          path: p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz'))
     ]).validate();
   });
 
diff --git a/test/dependency_override_test.dart b/test/dependency_override_test.dart
index dd8dc0d..59f59e4 100644
--- a/test/dependency_override_test.dart
+++ b/test/dependency_override_test.dart
@@ -27,7 +27,9 @@
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '2.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      ]).validate();
     });
 
     test('treats override as implicit dependency', () async {
@@ -43,7 +45,9 @@
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      ]).validate();
     });
 
     test('ignores other constraints on overridden package', () async {
@@ -65,7 +69,10 @@
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '2.0.0', 'bar': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      ]).validate();
     });
 
     test('ignores SDK constraints', () async {
@@ -82,8 +89,9 @@
       ]).create();
 
       await pubCommand(command);
-
-      await d.appPackagesFile({'foo': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      ]).validate();
     });
 
     test('warns about overridden dependencies', () async {
diff --git a/test/dependency_services/dependency_services_test.dart b/test/dependency_services/dependency_services_test.dart
index ed21a19..08542ea 100644
--- a/test/dependency_services/dependency_services_test.dart
+++ b/test/dependency_services/dependency_services_test.dart
@@ -17,12 +17,18 @@
 
 void manifestAndLockfile(GoldenTestContext context) {
   String catFile(String filename) {
-    final contents = filterUnstableLines(
-        File(p.join(d.sandbox, appPath, filename)).readAsLinesSync());
+    final path = p.join(d.sandbox, appPath, filename);
+    if (File(path).existsSync()) {
+      final contents = filterUnstableLines(File(path).readAsLinesSync());
 
-    return '''
+      return '''
 \$ cat $filename
 ${contents.join('\n')}''';
+    } else {
+      return '''
+\$ cat $filename
+No such file $filename.''';
+    }
   }
 
   context.expectNextSection('''
@@ -127,7 +133,7 @@
     await pubGet();
     server.dontAllowDownloads();
     await listReportApply(context, [
-      _PackageVersion('foo', Version.parse('2.2.3')),
+      _PackageVersion('foo', '2.2.3'),
       _PackageVersion('transitive', null)
     ], reportAssertions: (report) {
       expect(
@@ -141,6 +147,36 @@
     });
   });
 
+  testWithGolden('No pubspec.lock', (context) async {
+    final server = (await servePackages())
+      ..serve('foo', '1.2.3', deps: {'transitive': '^1.0.0'})
+      ..serve('foo', '2.2.3')
+      ..serve('transitive', '1.0.0');
+
+    await d.git('bar.git', [d.libPubspec('bar', '1.0.0')]).create();
+
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'app',
+        'dependencies': {
+          'foo': '^1.0.0',
+          'bar': {
+            'git': {'url': '../bar.git'},
+          },
+        },
+      })
+    ]).create();
+
+    server.dontAllowDownloads();
+    await listReportApply(
+      context,
+      [
+        _PackageVersion('foo', '2.2.3'),
+        _PackageVersion('transitive', null),
+      ],
+    );
+  });
+
   testWithGolden('Compatible', (context) async {
     final server = (await servePackages())
       ..serve('foo', '1.2.3')
@@ -166,7 +202,7 @@
     server.dontAllowDownloads();
 
     await listReportApply(context, [
-      _PackageVersion('foo', Version.parse('1.2.4')),
+      _PackageVersion('foo', '1.2.4'),
     ], reportAssertions: (report) {
       expect(
         findChangeVersion(report, 'compatible', 'foo'),
@@ -193,8 +229,8 @@
     server.dontAllowDownloads();
 
     await listReportApply(context, [
-      _PackageVersion('foo', Version.parse('2.2.3')),
-      _PackageVersion('transitive', Version.parse('1.0.0'))
+      _PackageVersion('foo', '2.2.3'),
+      _PackageVersion('transitive', '1.0.0')
     ], reportAssertions: (report) {
       expect(
         findChangeVersion(report, 'singleBreaking', 'foo'),
@@ -236,9 +272,9 @@
     server.dontAllowDownloads();
 
     await listReportApply(context, [
-      _PackageVersion('foo', Version.parse('3.0.1'),
+      _PackageVersion('foo', '3.0.1',
           constraint: VersionConstraint.parse('^3.0.0')),
-      _PackageVersion('bar', Version.parse('2.0.0'))
+      _PackageVersion('bar', '2.0.0')
     ], reportAssertions: (report) {
       expect(
         findChangeVersion(report, 'multiBreaking', 'foo'),
@@ -250,22 +286,77 @@
       );
     });
   });
+  testWithGolden('Relative paths are allowed', (context) async {
+    // We cannot update path-dependencies, but they should be allowed.
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    await d.dir('bar', [d.libPubspec('bar', '1.0.0')]).create();
+
+    await d.appDir({
+      'foo': '^1.0.0',
+      'bar': {'path': '../bar'}
+    }).create();
+    await pubGet();
+    server.serve('foo', '2.0.0');
+    await listReportApply(context, [
+      _PackageVersion('foo', '2.0.0',
+          constraint: VersionConstraint.parse('^2.0.0')),
+    ], reportAssertions: (report) {
+      expect(
+        findChangeVersion(report, 'multiBreaking', 'foo'),
+        '2.0.0',
+      );
+    });
+  });
+
+  testWithGolden('Can update a git package', (context) async {
+    await d.git('foo.git', [d.libPubspec('foo', '1.0.0')]).create();
+    await d.git('bar.git', [d.libPubspec('bar', '1.0.0')]).create();
+
+    await d.appDir({
+      'foo': {
+        'git': {'url': '../foo.git'}
+      },
+      'bar': {
+        // A git dependency with a version constraint.
+        'git': {'url': '../bar.git'},
+        'version': '^1.0.0',
+      }
+    }).create();
+    await pubGet();
+    final secondVersion = d.git('foo.git', [d.libPubspec('foo', '2.0.0')]);
+    await secondVersion.commit();
+    final newRef = await secondVersion.revParse('HEAD');
+
+    final barSecondVersion = d.git('bar.git', [d.libPubspec('bar', '2.0.0')]);
+    await barSecondVersion.commit();
+
+    await listReportApply(context, [
+      _PackageVersion('foo', newRef),
+    ], reportAssertions: (report) {
+      expect(
+        findChangeVersion(report, 'multiBreaking', 'foo'),
+        newRef,
+      );
+    });
+  });
 }
 
 dynamic findChangeVersion(dynamic json, String updateType, String name) {
   final dep = json['dependencies'].firstWhere((p) => p['name'] == 'foo');
+  if (dep == null) return null;
   return dep[updateType].firstWhere((p) => p['name'] == name)['version'];
 }
 
 class _PackageVersion {
   String name;
-  Version? version;
+  String? version;
   VersionConstraint? constraint;
   _PackageVersion(this.name, this.version, {this.constraint});
 
   Map<String, Object?> toJson() => {
         'name': name,
-        'version': version?.toString(),
+        'version': version,
         if (constraint != null) 'constraint': constraint.toString()
       };
 }
diff --git a/test/descriptor.dart b/test/descriptor.dart
index 2491df4..b3f8a55 100644
--- a/test/descriptor.dart
+++ b/test/descriptor.dart
@@ -287,6 +287,23 @@
 }) =>
     PackageConfigFileDescriptor(packages, generatorVersion);
 
+Descriptor appPackageConfigFile(
+  List<PackageConfigEntry> packages, {
+  String generatorVersion = '0.1.2+3',
+}) =>
+    dir(
+      appPath,
+      [
+        packageConfigFile(
+          [
+            packageConfigEntry(name: 'myapp', path: '.'),
+            ...packages,
+          ],
+          generatorVersion: generatorVersion,
+        ),
+      ],
+    );
+
 /// Create a [PackageConfigEntry] which assumes package with [name] is either
 /// a cached package with given [version] or a path dependency at given [path].
 PackageConfigEntry packageConfigEntry({
@@ -294,6 +311,7 @@
   String? version,
   String? path,
   String? languageVersion,
+  PackageServer? server,
 }) {
   if (version != null && path != null) {
     throw ArgumentError.value(
@@ -305,7 +323,7 @@
   }
   Uri rootUri;
   if (version != null) {
-    rootUri = p.toUri(globalServer.pathInCache(name, version));
+    rootUri = p.toUri((server ?? globalServer).pathInCache(name, version));
   } else {
     rootUri = p.toUri(p.join('..', path));
   }
diff --git a/test/descriptor/git.dart b/test/descriptor/git.dart
index edfc7bd..9209815 100644
--- a/test/descriptor/git.dart
+++ b/test/descriptor/git.dart
@@ -62,7 +62,10 @@
       'GIT_AUTHOR_NAME': 'Pub Test',
       'GIT_AUTHOR_EMAIL': 'pub@dartlang.org',
       'GIT_COMMITTER_NAME': 'Pub Test',
-      'GIT_COMMITTER_EMAIL': 'pub@dartlang.org'
+      'GIT_COMMITTER_EMAIL': 'pub@dartlang.org',
+      // To make stable commits ids we fix the date.
+      'GIT_COMMITTER_DATE': DateTime(1970).toIso8601String(),
+      'GIT_AUTHOR_DATE': DateTime(1970).toIso8601String(),
     };
 
     return git.run(args,
diff --git a/test/descriptor/packages.dart b/test/descriptor/packages.dart
index de52a80..b670322 100644
--- a/test/descriptor/packages.dart
+++ b/test/descriptor/packages.dart
@@ -156,10 +156,20 @@
 
     // Compare packages as sets to ignore ordering.
     expect(
-      config.packages.map((e) => e.toJson()).toSet(),
-      equals(_packages.map((e) => e.toJson()).toSet()),
-      reason:
-          '"packages" property in "$packageConfigFile" does not expected values',
+      config.packages,
+      _packages
+          .map(
+            (p) => isA<PackageConfigEntry>()
+                .having((p0) => p0.name, 'name', p.name)
+                .having(
+                    (p0) => p0.languageVersion,
+                    'languageVersion',
+                    // If the expected entry has no language-version we don't check it.
+                    p.languageVersion ?? anything)
+                .having((p0) => p0.rootUri, 'rootUri', p.rootUri)
+                .having((p0) => p0.packageUri, 'packageUri', p.packageUri),
+          )
+          .toSet(),
     );
 
     final expected = PackageConfig.fromJson(_config.toJson());
diff --git a/test/dev_dependency_test.dart b/test/dev_dependency_test.dart
index 88f321b..b192444 100644
--- a/test/dev_dependency_test.dart
+++ b/test/dev_dependency_test.dart
@@ -27,7 +27,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo', 'bar': '../bar'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+    ]).validate();
   });
 
   test("includes dev dependency's transitive dependencies", () async {
@@ -52,7 +55,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo', 'bar': '../bar'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+    ]).validate();
   });
 
   test("ignores transitive dependency's dev dependencies", () async {
@@ -78,6 +84,8 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
   });
 }
diff --git a/test/downgrade/unlock_if_necessary_test.dart b/test/downgrade/unlock_if_necessary_test.dart
index 0299e27..c8b55af 100644
--- a/test/downgrade/unlock_if_necessary_test.dart
+++ b/test/downgrade/unlock_if_necessary_test.dart
@@ -19,13 +19,19 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '2.0.0', 'foo_dep': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '2.0.0'),
+    ]).validate();
 
     server.serve('foo', '1.0.0', deps: {'foo_dep': '<2.0.0'});
     server.serve('foo_dep', '1.0.0');
 
     await pubDowngrade(args: ['foo']);
 
-    await d.appPackagesFile({'foo': '1.0.0', 'foo_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/downgrade/unlock_single_package_test.dart b/test/downgrade/unlock_single_package_test.dart
index cfd315d..1188e41 100644
--- a/test/downgrade/unlock_single_package_test.dart
+++ b/test/downgrade/unlock_single_package_test.dart
@@ -16,22 +16,34 @@
     await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
     await pubGet();
-    await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.1.0'),
+    ]).validate();
 
     server.serve('foo', '1.0.0', deps: {'bar': 'any'});
     server.serve('bar', '1.0.0');
 
     await pubDowngrade(args: ['bar']);
-    await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.1.0'),
+    ]).validate();
 
     server.serve('foo', '2.0.0', deps: {'bar': 'any'});
     server.serve('bar', '2.0.0');
 
     await pubDowngrade(args: ['bar']);
-    await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+    ]).validate();
 
     await pubDowngrade();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('will not downgrade below constraint #2629', () async {
@@ -43,11 +55,14 @@
     await d.appDir({'foo': '^2.0.0'}).create();
 
     await pubGet();
-
-    await d.appPackagesFile({'foo': '2.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.1.0'),
+    ]).validate();
 
     await pubDowngrade(args: ['foo']);
 
-    await d.appPackagesFile({'foo': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/git/check_out_test.dart b/test/get/git/check_out_test.dart
index f3caaa2..ada5461 100644
--- a/test/get/git/check_out_test.dart
+++ b/test/get/git/check_out_test.dart
@@ -9,6 +9,7 @@
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:shelf/shelf_io.dart' as shelf_io;
 import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
@@ -26,6 +27,12 @@
 
     await pubGet();
 
+    final lockfile = loadYaml(
+        File(p.join(d.sandbox, appPath, 'pubspec.lock')).readAsStringSync());
+    expect(lockfile['packages']['foo']['description']['url'], '../foo.git',
+        reason:
+            'The relative path should be preserved, and be a url (forward slashes on all platforms)');
+
     await d.dir(cachePath, [
       d.dir('git', [
         d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
diff --git a/test/get/git/path_test.dart b/test/get/git/path_test.dart
index d26d9cb..d9c9195 100644
--- a/test/get/git/path_test.dart
+++ b/test/get/git/path_test.dart
@@ -39,9 +39,11 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir')),
+    ]).validate();
   });
 
   test('depends on a package in a deep subdirectory', () async {
@@ -73,9 +75,12 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path:
+              pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')),
+    ]).validate();
 
     final lockFile = LockFile.load(
         p.join(d.sandbox, appPath, 'pubspec.lock'), SystemCache().sources);
@@ -198,9 +203,12 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub': pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub',
+          path:
+              pathInCache('git/foo-${await repo.revParse('HEAD')}/sub/dir%25')),
+    ]).validate();
 
     final lockFile = LockFile.load(
         p.join(d.sandbox, appPath, 'pubspec.lock'), SystemCache().sources);
@@ -244,10 +252,14 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub1': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir1'),
-      'sub2': pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir2')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub1',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir1')),
+      d.packageConfigEntry(
+          name: 'sub2',
+          path: pathInCache('git/foo-${await repo.revParse('HEAD')}/subdir2')),
+    ]).validate();
   });
 
   test('depends on packages in the same subdirectory at different revisions',
@@ -292,9 +304,11 @@
       ])
     ]).validate();
 
-    await d.appPackagesFile({
-      'sub1': pathInCache('git/foo-$oldRevision/subdir'),
-      'sub2': pathInCache('git/foo-$newRevision/subdir')
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(
+          name: 'sub1', path: pathInCache('git/foo-$oldRevision/subdir')),
+      d.packageConfigEntry(
+          name: 'sub2', path: pathInCache('git/foo-$newRevision/subdir')),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/avoid_network_requests_test.dart b/test/get/hosted/avoid_network_requests_test.dart
index 8778ae6..c62fa7c 100644
--- a/test/get/hosted/avoid_network_requests_test.dart
+++ b/test/get/hosted/avoid_network_requests_test.dart
@@ -31,8 +31,10 @@
 
     // Run the solver again.
     await pubGet();
-
-    await d.appPackagesFile({'foo': '1.2.0', 'bar': '1.2.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.2.0'),
+    ]).validate();
 
     // The get should not have done any network requests since the lock file is
     // up to date.
diff --git a/test/get/hosted/cached_pubspec_test.dart b/test/get/hosted/cached_pubspec_test.dart
index beb4e16..fa9decc 100644
--- a/test/get/hosted/cached_pubspec_test.dart
+++ b/test/get/hosted/cached_pubspec_test.dart
@@ -22,7 +22,9 @@
     server.requestedPaths.clear();
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
 
     // Run the solver again now that it's cached.
     await pubGet();
diff --git a/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart b/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart
index 00b2444..44fe075 100644
--- a/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart
+++ b/test/get/hosted/do_not_upgrade_on_removed_constraints_test.dart
@@ -21,13 +21,19 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '1.0.0', 'bar': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+    ]).validate();
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/does_no_network_requests_when_possible_test.dart b/test/get/hosted/does_no_network_requests_when_possible_test.dart
index ea1f4a7..4324776 100644
--- a/test/get/hosted/does_no_network_requests_when_possible_test.dart
+++ b/test/get/hosted/does_no_network_requests_when_possible_test.dart
@@ -27,7 +27,9 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.0'}).validate();
-    await d.appPackagesFile({'foo': '1.2.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.0'),
+    ]).validate();
 
     // The get should not have done any network requests since the lock file is
     // up to date.
diff --git a/test/get/hosted/get_stress_test.dart b/test/get/hosted/get_stress_test.dart
index 86a8da9..026c0c1 100644
--- a/test/get/hosted/get_stress_test.dart
+++ b/test/get/hosted/get_stress_test.dart
@@ -26,10 +26,10 @@
       'foo': '1.2.3',
       for (var i = 0; i < 20; i++) 'pkg$i': '1.$i.0',
     }).validate();
-
-    await d.appPackagesFile({
-      'foo': '1.2.3',
-      for (var i = 0; i < 20; i++) 'pkg$i': '1.$i.0',
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      for (var i = 0; i < 20; i++)
+        d.packageConfigEntry(name: 'pkg$i', version: '1.$i.0')
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/get_test.dart b/test/get/hosted/get_test.dart
index 30dd4b8..f0270a3 100644
--- a/test/get/hosted/get_test.dart
+++ b/test/get/hosted/get_test.dart
@@ -21,7 +21,9 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
   });
 
   test('URL encodes the package name', () async {
@@ -57,7 +59,9 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3'}, port: server.port).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3', server: server),
+    ]).validate();
   });
 
   group('categorizes dependency types in the lockfile', () {
diff --git a/test/get/hosted/get_transitive_test.dart b/test/get/hosted/get_transitive_test.dart
index 74635bc..0ae18ac 100644
--- a/test/get/hosted/get_transitive_test.dart
+++ b/test/get/hosted/get_transitive_test.dart
@@ -20,6 +20,9 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3', 'bar': '2.0.4'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3', 'bar': '2.0.4'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.4'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart b/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart
index 0d75f95..585cfba 100644
--- a/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart
+++ b/test/get/hosted/gets_a_package_with_busted_dev_dependencies_test.dart
@@ -24,6 +24,8 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.2.3'}).validate();
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/resolve_constraints_test.dart b/test/get/hosted/resolve_constraints_test.dart
index c9c73d2..466902e 100644
--- a/test/get/hosted/resolve_constraints_test.dart
+++ b/test/get/hosted/resolve_constraints_test.dart
@@ -22,8 +22,10 @@
 
     await d
         .cacheDir({'foo': '1.2.3', 'bar': '2.3.4', 'baz': '2.0.4'}).validate();
-
-    await d.appPackagesFile(
-        {'foo': '1.2.3', 'bar': '2.3.4', 'baz': '2.0.4'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      d.packageConfigEntry(name: 'bar', version: '2.3.4'),
+      d.packageConfigEntry(name: 'baz', version: '2.0.4'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/resolve_with_retracted_package_versions_test.dart b/test/get/hosted/resolve_with_retracted_package_versions_test.dart
index efd3a0a..d9b33a4 100644
--- a/test/get/hosted/resolve_with_retracted_package_versions_test.dart
+++ b/test/get/hosted/resolve_with_retracted_package_versions_test.dart
@@ -23,7 +23,10 @@
     await pubGet();
 
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('Error when the only available package version is retracted', () async {
@@ -53,22 +56,34 @@
 
     await pubGet();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
     server.retractPackageVersion('bar', '1.1.0');
     await pubUpgrade();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
     server.serve('bar', '2.0.0');
     await pubUpgrade();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
     server.serve('bar', '1.2.0');
     await pubUpgrade();
     await d.cacheDir({'foo': '1.0.0', 'bar': '1.2.0'}).validate();
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.2.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.2.0'),
+    ]).validate();
   });
 
   test('Offline versions of pub commands also handle retracted packages',
@@ -102,7 +117,10 @@
     await pubUpgrade(args: ['--offline']);
 
     // We choose bar 1.1.0 since we already have it in pubspec.lock
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
 
     // Delete lockfile so that retracted versions are not considered.
     final lockFile = p.join(d.sandbox, appPath, 'pubspec.lock');
@@ -110,7 +128,10 @@
     deleteEntry(lockFile);
 
     await pubGet(args: ['--offline']);
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
   });
 
   test('Allow retracted version when pinned in dependency_overrides', () async {
@@ -130,7 +151,9 @@
     server.retractPackageVersion('foo', '2.0.0');
 
     await pubGet();
-    await d.appPackagesFile({'foo': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+    ]).validate();
   });
 
   test('Prefer retracted version in dependency_overrides over pubspec.lock',
@@ -147,7 +170,9 @@
     server.retractPackageVersion('foo', '3.0.0');
 
     await pubUpgrade();
-    await d.appPackagesFile({'foo': '3.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '3.0.0'),
+    ]).validate();
 
     await d.dir(appPath, [
       d.pubspec({
@@ -158,6 +183,8 @@
     ]).create();
 
     await pubUpgrade();
-    await d.appPackagesFile({'foo': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/stay_locked_if_compatible_test.dart b/test/get/hosted/stay_locked_if_compatible_test.dart
index bb5e63a..df04579 100644
--- a/test/get/hosted/stay_locked_if_compatible_test.dart
+++ b/test/get/hosted/stay_locked_if_compatible_test.dart
@@ -17,8 +17,9 @@
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
-
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
 
     server.serve('foo', '1.0.1');
 
@@ -26,6 +27,8 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart b/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart
index dbe6039..c75c435 100644
--- a/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart
+++ b/test/get/hosted/stay_locked_if_new_is_satisfied_test.dart
@@ -19,9 +19,11 @@
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
-
-    await d.appPackagesFile(
-        {'foo': '1.0.0', 'bar': '1.0.0', 'baz': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '1.0.0'),
+    ]).validate();
 
     server.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
     server.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
@@ -32,11 +34,11 @@
 
     await pubGet();
 
-    await d.appPackagesFile({
-      'foo': '1.0.0',
-      'bar': '1.0.0',
-      'baz': '1.0.0',
-      'newdep': '2.0.0'
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '1.0.0'),
+      d.packageConfigEntry(name: 'newdep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/stay_locked_test.dart b/test/get/hosted/stay_locked_test.dart
index 134e8b5..8b819ea 100644
--- a/test/get/hosted/stay_locked_test.dart
+++ b/test/get/hosted/stay_locked_test.dart
@@ -20,8 +20,9 @@
 
     // This should lock the foo dependency to version 1.0.0.
     await pubGet();
-
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
 
     // Delete the .dart_tool/package_config.json file to simulate a new checkout of the application.
     deleteEntry(path.join(d.sandbox, packageConfigFilePath));
@@ -32,6 +33,8 @@
     // This shouldn't upgrade the foo dependency due to the lockfile.
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/unlock_if_incompatible_test.dart b/test/get/hosted/unlock_if_incompatible_test.dart
index 11bf909..007231e 100644
--- a/test/get/hosted/unlock_if_incompatible_test.dart
+++ b/test/get/hosted/unlock_if_incompatible_test.dart
@@ -18,12 +18,16 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
     server.serve('foo', '1.0.1');
     await d.appDir({'foo': '>1.0.0'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.1'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.1'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart b/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart
index 1cedc5a..ae6a4e6 100644
--- a/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart
+++ b/test/get/hosted/unlock_if_new_is_unsatisfied_test.dart
@@ -22,12 +22,12 @@
 
     await pubGet();
 
-    await d.appPackagesFile({
-      'foo': '1.0.0',
-      'bar': '1.0.0',
-      'baz': '1.0.0',
-      'qux': '1.0.0'
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '1.0.0'),
+      d.packageConfigEntry(name: 'qux', version: '1.0.0'),
+    ]).validate();
 
     server.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
     server.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
@@ -39,12 +39,12 @@
 
     await pubGet();
 
-    await d.appPackagesFile({
-      'foo': '2.0.0',
-      'bar': '2.0.0',
-      'baz': '2.0.0',
-      'qux': '1.0.0',
-      'newdep': '2.0.0'
-    }).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+      d.packageConfigEntry(name: 'baz', version: '2.0.0'),
+      d.packageConfigEntry(name: 'qux', version: '1.0.0'),
+      d.packageConfigEntry(name: 'newdep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/get/hosted/unlock_if_version_doesnt_exist_test.dart b/test/get/hosted/unlock_if_version_doesnt_exist_test.dart
index 7eb4246..e4cc01b 100644
--- a/test/get/hosted/unlock_if_version_doesnt_exist_test.dart
+++ b/test/get/hosted/unlock_if_version_doesnt_exist_test.dart
@@ -17,7 +17,9 @@
 
     await d.appDir({'foo': 'any'}).create();
     await pubGet();
-    await d.appPackagesFile({'foo': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
 
     deleteEntry(p.join(d.sandbox, cachePath));
 
@@ -25,6 +27,8 @@
     server.serve('foo', '1.0.1');
 
     await pubGet();
-    await d.appPackagesFile({'foo': '1.0.1'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.1'),
+    ]).validate();
   });
 }
diff --git a/test/get/package_name_test.dart b/test/get/package_name_test.dart
index 46ca386..b1328cc 100644
--- a/test/get/package_name_test.dart
+++ b/test/get/package_name_test.dart
@@ -56,7 +56,8 @@
     await pubGet();
 
     await d.dir(appPath, [
-      d.packagesFile({'foo.bar.baz': '.'}),
+      d.packageConfigFile(
+          [d.packageConfigEntry(name: 'foo.bar.baz', path: '.')])
     ]).validate();
   });
 }
diff --git a/test/get/path/absolute_path_test.dart b/test/get/path/absolute_path_test.dart
index 0fc987a..e1897e9 100644
--- a/test/get/path/absolute_path_test.dart
+++ b/test/get/path/absolute_path_test.dart
@@ -21,6 +21,8 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': path.join(d.sandbox, 'foo')}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: path.join(d.sandbox, 'foo')),
+    ]).validate();
   });
 }
diff --git a/test/get/path/absolute_symlink_test.dart b/test/get/path/absolute_symlink_test.dart
index 1a2d912..83d3433 100644
--- a/test/get/path/absolute_symlink_test.dart
+++ b/test/get/path/absolute_symlink_test.dart
@@ -24,8 +24,8 @@
 
     await pubGet();
 
-    await d.dir(appPath, [
-      d.packagesFile({'myapp': '.', 'foo': fooPath})
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: fooPath),
     ]).validate();
 
     await d.dir('moved').create();
@@ -35,9 +35,9 @@
     renameInSandbox(appPath, path.join('moved', appPath));
 
     await d.dir('moved', [
-      d.dir(appPath, [
-        d.packagesFile({'myapp': '.', 'foo': fooPath})
-      ])
+      d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', path: fooPath),
+      ]),
     ]).validate();
   });
 }
diff --git a/test/get/path/relative_path_test.dart b/test/get/path/relative_path_test.dart
index ff19c6f..3a7fb8f 100644
--- a/test/get/path/relative_path_test.dart
+++ b/test/get/path/relative_path_test.dart
@@ -27,7 +27,9 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
   });
 
   test('path is relative to containing pubspec', () async {
@@ -49,8 +51,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '../relative/foo', 'bar': '../relative/bar'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../relative/foo'),
+      d.packageConfigEntry(name: 'bar', path: '../relative/bar'),
+    ]).validate();
   });
 
   test('path is relative to containing pubspec when using --directory',
@@ -76,9 +80,10 @@
         workingDirectory: d.sandbox,
         output: contains('Changed 2 dependencies in myapp!'));
 
-    await d.appPackagesFile(
-      {'foo': '../relative/foo', 'bar': '../relative/bar'},
-    ).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../relative/foo'),
+      d.packageConfigEntry(name: 'bar', path: '../relative/bar'),
+    ]).validate();
   });
 
   test('relative path preserved in the lockfile', () async {
diff --git a/test/get/path/relative_symlink_test.dart b/test/get/path/relative_symlink_test.dart
index af7185b..9136a34 100644
--- a/test/get/path/relative_symlink_test.dart
+++ b/test/get/path/relative_symlink_test.dart
@@ -28,8 +28,8 @@
 
     await pubGet();
 
-    await d.dir(appPath, [
-      d.packagesFile({'myapp': '.', 'foo': '../foo'})
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
     ]).validate();
 
     await d.dir('moved').create();
@@ -41,8 +41,8 @@
     renameInSandbox(appPath, path.join('moved', appPath));
 
     await d.dir('moved', [
-      d.dir(appPath, [
-        d.packagesFile({'myapp': '.', 'foo': '../foo'})
+      d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', path: '../foo'),
       ])
     ]).validate();
   });
diff --git a/test/get/path/shared_dependency_symlink_test.dart b/test/get/path/shared_dependency_symlink_test.dart
index 46a78c6..d450838 100644
--- a/test/get/path/shared_dependency_symlink_test.dart
+++ b/test/get/path/shared_dependency_symlink_test.dart
@@ -40,13 +40,10 @@
 
     await pubGet();
 
-    await d.dir(appPath, [
-      d.packagesFile({
-        'myapp': '.',
-        'foo': '../foo',
-        'bar': '../bar',
-        'shared': '../shared'
-      })
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+      d.packageConfigEntry(name: 'shared', path: '../shared'),
     ]).validate();
   });
 }
diff --git a/test/get/path/shared_dependency_test.dart b/test/get/path/shared_dependency_test.dart
index 7072221..3ef91ae 100644
--- a/test/get/path/shared_dependency_test.dart
+++ b/test/get/path/shared_dependency_test.dart
@@ -35,8 +35,11 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '../foo', 'bar': '../bar', 'shared': '../shared'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+      d.packageConfigEntry(name: 'shared', path: '../shared'),
+    ]).validate();
   });
 
   test('shared dependency with paths that normalize the same', () async {
@@ -66,7 +69,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile(
-        {'foo': '../foo', 'bar': '../bar', 'shared': '../shared'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+      d.packageConfigEntry(name: 'bar', path: '../bar'),
+      d.packageConfigEntry(name: 'shared', path: '../shared'),
+    ]).validate();
   });
 }
diff --git a/test/get/sdk_constraint_required_test.dart b/test/get/sdk_constraint_required_test.dart
index 1b6fbc5..3f1e5c0 100644
--- a/test/get/sdk_constraint_required_test.dart
+++ b/test/get/sdk_constraint_required_test.dart
@@ -25,8 +25,8 @@
       d.nothing('pubspec.lock'),
       // The "packages" directory should not have been generated.
       d.nothing('packages'),
-      // The ".packages" file should not have been created.
-      d.nothing('.packages'),
+      // The package config file should not have been created.
+      d.nothing('.dart_tool/package_config.json'),
     ]).validate();
   });
 }
diff --git a/test/get/switch_source_test.dart b/test/get/switch_source_test.dart
index ffbf8b4..7e56652 100644
--- a/test/get/switch_source_test.dart
+++ b/test/get/switch_source_test.dart
@@ -21,11 +21,15 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '../foo'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', path: '../foo'),
+    ]).validate();
     await d.appDir({'foo': 'any'}).create();
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+    ]).validate();
   });
 }
diff --git a/test/get/with_empty_environment_test.dart b/test/get/with_empty_environment_test.dart
index 30768c4..5dab9b3 100644
--- a/test/get/with_empty_environment_test.dart
+++ b/test/get/with_empty_environment_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:io';
-
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -18,10 +16,6 @@
 
     await pubGet(environment: {
       '_PUB_TEST_CONFIG_DIR': null,
-      if (Platform.isWindows) ...{
-        'SYSTEMROOT': Platform.environment['SYSTEMROOT'],
-        'TMP': Platform.environment['TMP'],
-      },
-    }, includeParentEnvironment: false);
+    }, includeParentHomeAndPath: false);
   });
 }
diff --git a/test/global/activate/git_package_test.dart b/test/global/activate/git_package_test.dart
index 02d06db..ad3ff27 100644
--- a/test/global/activate/git_package_test.dart
+++ b/test/global/activate/git_package_test.dart
@@ -38,7 +38,7 @@
         'sub',
         [
           d.libPubspec('foo', '1.0.0'),
-          d.dir('bin', [d.file('foo.dart', "main() => print('1');")])
+          d.dir('bin', [d.file('sub.dart', "main() => print('1');")])
         ],
       ),
     ]).create();
@@ -46,8 +46,8 @@
       d.dir(
         'sub',
         [
-          d.libPubspec('foo', '2.0.0'),
-          d.dir('bin', [d.file('foo.dart', "main() => print('2');")])
+          d.libPubspec('sub', '2.0.0'),
+          d.dir('bin', [d.file('sub.dart', "main() => print('2');")])
         ],
       ),
     ]).commit();
@@ -55,8 +55,8 @@
       d.dir(
         'sub',
         [
-          d.libPubspec('foo', '3.0.0'),
-          d.dir('bin', [d.file('foo.dart', "main() => print('3');")])
+          d.libPubspec('sub', '3.0.0'),
+          d.dir('bin', [d.file('sub.dart', "main() => print('3');")])
         ],
       ),
     ]).commit();
@@ -72,19 +72,19 @@
       ],
       output: allOf(
         startsWith('Resolving dependencies...\n'
-            '+ foo 2.0.0 from git ..${p.separator}foo.git at'),
+            '+ sub 2.0.0 from git ..${p.separator}foo.git at'),
         // Specific revision number goes here.
         contains('in sub'),
         endsWith('Building package executables...\n'
-            'Built foo:foo.\n'
-            'Activated foo 2.0.0 from Git repository "..${p.separator}foo.git".'),
+            'Built sub:sub.\n'
+            'Activated sub 2.0.0 from Git repository "..${p.separator}foo.git".'),
       ),
     );
     await runPub(
       args: [
         'global',
         'run',
-        'foo',
+        'sub',
       ],
       output: contains('2'),
     );
diff --git a/test/hosted/offline_test.dart b/test/hosted/offline_test.dart
index 6482656..ca3dc80 100644
--- a/test/hosted/offline_test.dart
+++ b/test/hosted/offline_test.dart
@@ -44,8 +44,10 @@
       }
 
       await pubCommand(command, args: ['--offline'], warning: warning);
-
-      await d.appPackagesFile({'foo': '1.2.3', 'bar': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+        d.packageConfigEntry(name: 'bar', version: '1.2.3'),
+      ]).validate();
     });
 
     test('supports prerelease versions', () async {
@@ -66,7 +68,9 @@
 
       await pubCommand(command, args: ['--offline'], warning: warning);
 
-      await d.appPackagesFile({'foo': '1.2.3-alpha.1'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3-alpha.1'),
+      ]).validate();
     });
 
     test('fails gracefully if a dependency is not cached', () async {
@@ -143,7 +147,9 @@
 
       await pubCommand(command, args: ['--offline']);
 
-      await d.appPackagesFile({'foo': '1.2.3'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.3'),
+      ]).validate();
     });
 
     test('skips invalid cached versions', () async {
@@ -164,7 +170,9 @@
 
       await pubCommand(command, args: ['--offline']);
 
-      await d.appPackagesFile({'foo': '1.2.2'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+      ]).validate();
     });
 
     test('skips invalid locked versions', () async {
@@ -186,7 +194,9 @@
 
       await pubCommand(command, args: ['--offline']);
 
-      await d.appPackagesFile({'foo': '1.2.2'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.2.2'),
+      ]).validate();
     });
   });
 }
diff --git a/test/hosted/remove_removed_dependency_test.dart b/test/hosted/remove_removed_dependency_test.dart
index bb2e091..a0446c0 100644
--- a/test/hosted/remove_removed_dependency_test.dart
+++ b/test/hosted/remove_removed_dependency_test.dart
@@ -17,14 +17,18 @@
       await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
       await pubCommand(command);
-
-      await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      ]).validate();
 
       await d.appDir({'foo': 'any'}).create();
 
       await pubCommand(command);
 
-      await d.appPackagesFile({'foo': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      ]).validate();
     });
   });
 }
diff --git a/test/hosted/remove_removed_transitive_dependency_test.dart b/test/hosted/remove_removed_transitive_dependency_test.dart
index ac3a452..182ec3f 100644
--- a/test/hosted/remove_removed_transitive_dependency_test.dart
+++ b/test/hosted/remove_removed_transitive_dependency_test.dart
@@ -21,20 +21,21 @@
       await d.appDir({'foo': 'any', 'bar': 'any'}).create();
 
       await pubCommand(command);
-
-      await d.appPackagesFile({
-        'foo': '1.0.0',
-        'bar': '1.0.0',
-        'shared_dep': '1.0.0',
-        'bar_dep': '1.0.0',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+        d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar_dep', version: '1.0.0'),
+      ]).validate();
 
       await d.appDir({'foo': 'any'}).create();
 
       await pubCommand(command);
 
-      await d
-          .appPackagesFile({'foo': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+      ]).validate();
     });
   });
 }
diff --git a/test/must_pub_get_test.dart b/test/must_pub_get_test.dart
index dfc09d7..71d9c16 100644
--- a/test/must_pub_get_test.dart
+++ b/test/must_pub_get_test.dart
@@ -213,7 +213,7 @@
           d.appPubspec({'foo': '1.0.0'})
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         deleteEntry(p.join(d.sandbox, cachePath));
 
@@ -235,7 +235,7 @@
           })
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         await createPackagesFile(appPath);
 
@@ -257,7 +257,7 @@
           })
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         await d.dir(appPath, [
           d.file('.packages', '''
@@ -284,7 +284,7 @@
           })
         ]).create();
 
-        await pubGet();
+        await pubGet(args: ['--legacy-packages-file']);
 
         await createPackagesFile(appPath, dependenciesInSandBox: ['foo']);
 
@@ -452,7 +452,7 @@
   group("doesn't require the user to run pub get first if", () {
     group(
         'the pubspec is older than the lockfile which is older than the '
-        'packages file, even if the contents are wrong', () {
+        'package-config, even if the contents are wrong', () {
       setUp(() async {
         await d.dir(appPath, [
           d.appPubspec({'foo': '1.0.0'})
@@ -461,7 +461,6 @@
         await _touch('pubspec.yaml');
 
         await _touch('pubspec.lock');
-        await _touch('.packages');
         await _touch('.dart_tool/package_config.json');
       });
 
@@ -600,14 +599,11 @@
           File(p.join(d.sandbox, 'myapp/pubspec.yaml')).lastModifiedSync();
       var lockFileModified =
           File(p.join(d.sandbox, 'myapp/pubspec.lock')).lastModifiedSync();
-      var packagesModified =
-          File(p.join(d.sandbox, 'myapp/.packages')).lastModifiedSync();
       var packageConfigModified =
           File(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json'))
               .lastModifiedSync();
 
       expect(!pubspecModified.isAfter(lockFileModified), isTrue);
-      expect(!lockFileModified.isAfter(packagesModified), isTrue);
       expect(!lockFileModified.isAfter(packageConfigModified), isTrue);
     });
   }
diff --git a/test/outdated/outdated_test.dart b/test/outdated/outdated_test.dart
index 31113c7..5a90fae 100644
--- a/test/outdated/outdated_test.dart
+++ b/test/outdated/outdated_test.dart
@@ -109,6 +109,30 @@
     await ctx.runOutdatedTests();
   });
 
+  testWithGolden('show discontinued', (ctx) async {
+    final builder = await servePackages();
+    builder
+      ..serve('foo', '1.2.3', deps: {'transitive': '^1.0.0'})
+      ..serve('bar', '1.0.0')
+      ..serve('baz', '1.0.0')
+      ..serve('transitive', '1.2.3');
+
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'app',
+        'dependencies': {
+          'foo': '^1.0.0',
+          'bar': '^1.0.0',
+          'baz': '^1.0.0',
+        },
+      })
+    ]).create();
+    await pubGet();
+    builder.discontinue('foo');
+    builder.discontinue('baz', replacementText: 'newbaz');
+    await ctx.runOutdatedTests();
+  });
+
   testWithGolden('circular dependency on root', (ctx) async {
     final server = await servePackages();
     server.serve('foo', '1.2.3', deps: {'app': '^1.0.0'});
diff --git a/test/packages_file_test.dart b/test/packages_file_test.dart
index e7736be..c245a9a 100644
--- a/test/packages_file_test.dart
+++ b/test/packages_file_test.dart
@@ -11,7 +11,7 @@
 
 void main() {
   forBothPubGetAndUpgrade((command) {
-    test('.packages file is created', () async {
+    test('.packages file is created with flag', () async {
       await servePackages()
         ..serve('foo', '1.2.3',
             deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])])
@@ -24,7 +24,7 @@
         d.dir('lib')
       ]).create();
 
-      await pubCommand(command);
+      await pubCommand(command, args: ['--legacy-packages-file']);
 
       await d.dir(appPath, [
         d.packagesFile(
@@ -32,7 +32,7 @@
       ]).validate();
     });
 
-    test('.packages file is overwritten', () async {
+    test('.packages file is overwritten with flag', () async {
       await servePackages()
         ..serve('foo', '1.2.3',
             deps: {'baz': '2.2.2'}, contents: [d.dir('lib', [])])
@@ -51,7 +51,7 @@
       await oldFile.create();
       await oldFile.validate(); // Sanity-check that file was created correctly.
 
-      await pubCommand(command);
+      await pubCommand(command, args: ['--legacy-packages-file']);
 
       await d.dir(appPath, [
         d.packagesFile(
@@ -59,24 +59,28 @@
       ]).validate();
     });
 
-    test('.packages file is not created if pub command fails', () async {
+    test('.packages file is not created if pub command fails with flag',
+        () async {
       await d.dir(appPath, [
         d.appPubspec({'foo': '1.2.3'}),
         d.dir('lib')
       ]).create();
 
       await pubCommand(command,
-          args: ['--offline'], error: equalsIgnoringWhitespace("""
+          args: ['--offline', '--legacy-packages-file'],
+          error: equalsIgnoringWhitespace("""
             Because myapp depends on foo any which doesn't exist (could not find
               package foo in cache), version solving failed.
 
             Try again without --offline!
-          """), exitCode: exit_codes.UNAVAILABLE);
+          """),
+          exitCode: exit_codes.UNAVAILABLE);
 
       await d.dir(appPath, [d.nothing('.packages')]).validate();
     });
 
-    test('.packages file has relative path to path dependency', () async {
+    test('.packages file has relative path to path dependency with flag',
+        () async {
       await servePackages()
         ..serve('foo', '1.2.3',
             deps: {'baz': 'any'}, contents: [d.dir('lib', [])])
@@ -100,7 +104,7 @@
         d.dir('lib')
       ]).create();
 
-      await pubCommand(command);
+      await pubCommand(command, args: ['--legacy-packages-file']);
 
       await d.dir(appPath, [
         d.packagesFile({'myapp': '.', 'baz': '../local_baz', 'foo': '1.2.3'}),
diff --git a/test/pub_get_and_upgrade_test.dart b/test/pub_get_and_upgrade_test.dart
index 62004a4..f64f93b 100644
--- a/test/pub_get_and_upgrade_test.dart
+++ b/test/pub_get_and_upgrade_test.dart
@@ -45,7 +45,8 @@
       await pubCommand(command);
 
       await d.dir('myapp', [
-        d.packagesFile({'myapp_name': '.'})
+        d.packageConfigFile(
+            [d.packageConfigEntry(name: 'myapp_name', path: '.')]),
       ]).validate();
     });
 
diff --git a/test/pubspec_overrides_test.dart b/test/pubspec_overrides_test.dart
index 2e9234f..514d52f 100644
--- a/test/pubspec_overrides_test.dart
+++ b/test/pubspec_overrides_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:test/test.dart';
 
 import 'descriptor.dart' as d;
@@ -9,7 +10,7 @@
 
 void main() {
   forBothPubGetAndUpgrade((command) {
-    test('pubspec overrides', () async {
+    test('supports dependency_overrides', () async {
       await servePackages()
         ..serve('lib', '1.0.0')
         ..serve('lib', '2.0.0');
@@ -46,4 +47,18 @@
       ]).validate();
     });
   });
+
+  test('is ignored by publish command', () async {
+    await d.validPackage.create();
+    await d.dir(appPath, [
+      d.pubspecOverrides({
+        'dependency_overrides': {'lib': '1.0.0'}
+      }),
+    ]).create();
+
+    await runPub(
+      args: ['lish', '--dry-run'],
+      exitCode: exit_codes.SUCCESS,
+    );
+  });
 }
diff --git a/test/remove/remove_test.dart b/test/remove/remove_test.dart
index e369c85..8c82527 100644
--- a/test/remove/remove_test.dart
+++ b/test/remove/remove_test.dart
@@ -21,7 +21,7 @@
     await pubRemove(args: ['foo']);
 
     await d.cacheDir({}).validate();
-    await d.appPackagesFile({}).validate();
+    await d.appPackageConfigFile([]).validate();
     await d.appDir().validate();
   });
 
@@ -49,7 +49,9 @@
     await pubRemove(args: ['foo']);
 
     await d.cacheDir({'bar': '2.0.0'}).validate();
-    await d.appPackagesFile({'bar': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+    ]).validate();
 
     await d.dir(appPath, [
       d.pubspec({
@@ -113,7 +115,7 @@
     await pubRemove(args: ['foo']);
 
     await d.cacheDir({}).validate();
-    await d.appPackagesFile({}).validate();
+    await d.appPackageConfigFile([]).validate();
 
     await d.dir(appPath, [
       d.pubspec({'name': 'myapp'})
@@ -140,7 +142,10 @@
     await pubRemove(args: ['foo', 'bar', 'baz']);
 
     await d.cacheDir({'jfj': '0.2.1'}).validate();
-    await d.appPackagesFile({'jfj': '0.2.1'}).validate();
+
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'jfj', version: '0.2.1'),
+    ]).validate();
 
     await d.dir(appPath, [
       d.pubspec({
@@ -170,7 +175,10 @@
     await pubGet();
 
     await pubRemove(args: ['foo']);
-    await d.appPackagesFile({'bar': '1.2.3'}).validate();
+
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'bar': '1.2.3'}).validate();
   });
 
@@ -188,7 +196,9 @@
     await pubGet();
 
     await pubRemove(args: ['foo']);
-    await d.appPackagesFile({'bar': '1.2.3'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '1.2.3'),
+    ]).validate();
     await d.appDir({'bar': '1.2.3'}).validate();
   });
 
@@ -210,7 +220,9 @@
     await pubGet();
 
     await pubRemove(args: ['foo']);
-    await d.appPackagesFile({'bar': '2.0.1'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'bar', version: '2.0.1'),
+    ]).validate();
     await d.appDir({'bar': '2.0.1'}).validate();
   });
 
diff --git a/test/sdk_test.dart b/test/sdk_test.dart
index 6e50ff0..6165cd5 100644
--- a/test/sdk_test.dart
+++ b/test/sdk_test.dart
@@ -37,13 +37,10 @@
       }).create();
       await pubCommand(command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
-
-      await d.dir(appPath, [
-        d.packagesFile({
-          'myapp': '.',
-          'foo': p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-          'bar': '1.0.0'
-        })
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(
+            name: 'foo', path: p.join(d.sandbox, 'flutter', 'packages', 'foo')),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
       ]).validate();
     });
 
@@ -54,11 +51,10 @@
       await pubCommand(command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
 
-      await d.dir(appPath, [
-        d.packagesFile({
-          'myapp': '.',
-          'baz': p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz')
-        })
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(
+            name: 'baz',
+            path: p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz')),
       ]).validate();
     });
 
@@ -93,10 +89,7 @@
       deleteEntry(p.join(d.sandbox, 'flutter', 'version'));
       await pubCommand(command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')});
-
-      await d.dir(appPath, [
-        d.packagesFile({'myapp': '.'})
-      ]).validate();
+      await d.appPackageConfigFile([]).validate();
     });
 
     group('fails if', () {
@@ -169,13 +162,10 @@
       }).create();
       await pubCommand(command,
           environment: {'FUCHSIA_DART_SDK_ROOT': p.join(d.sandbox, 'fuchsia')});
-
-      await d.dir(appPath, [
-        d.packagesFile({
-          'myapp': '.',
-          'foo': p.join(d.sandbox, 'fuchsia', 'packages', 'foo'),
-          'bar': '1.0.0'
-        })
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(
+            name: 'foo', path: p.join(d.sandbox, 'fuchsia', 'packages', 'foo')),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
       ]).validate();
     });
   });
diff --git a/test/test_pub.dart b/test/test_pub.dart
index e7e7bc7..3c2f102 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -130,7 +130,7 @@
   int? exitCode,
   Map<String, String?>? environment,
   String? workingDirectory,
-  includeParentEnvironment = true,
+  includeParentHomeAndPath = true,
 }) async {
   if (error != null && warning != null) {
     throw ArgumentError("Cannot pass both 'error' and 'warning'.");
@@ -155,7 +155,7 @@
       exitCode: exitCode,
       environment: environment,
       workingDirectory: workingDirectory,
-      includeParentEnvironment: includeParentEnvironment);
+      includeParentHomeAndPath: includeParentHomeAndPath);
 }
 
 Future<void> pubAdd({
@@ -186,7 +186,7 @@
   int? exitCode,
   Map<String, String?>? environment,
   String? workingDirectory,
-  bool includeParentEnvironment = true,
+  bool includeParentHomeAndPath = true,
 }) async =>
     await pubCommand(
       RunCommand.get,
@@ -197,7 +197,7 @@
       exitCode: exitCode,
       environment: environment,
       workingDirectory: workingDirectory,
-      includeParentEnvironment: includeParentEnvironment,
+      includeParentHomeAndPath: includeParentHomeAndPath,
     );
 
 Future<void> pubUpgrade(
@@ -322,7 +322,7 @@
     String? workingDirectory,
     Map<String, String?>? environment,
     List<String>? input,
-    includeParentEnvironment = true}) async {
+    includeParentHomeAndPath = true}) async {
   exitCode ??= exit_codes.SUCCESS;
   // Cannot pass both output and outputJson.
   assert(output == null || outputJson == null);
@@ -331,7 +331,7 @@
     args: args,
     workingDirectory: workingDirectory,
     environment: environment,
-    includeParentEnvironment: includeParentEnvironment,
+    includeParentHomeAndPath: includeParentHomeAndPath,
   );
 
   if (input != null) {
@@ -446,7 +446,7 @@
     String? workingDirectory,
     Map<String, String?>? environment,
     bool verbose = true,
-    includeParentEnvironment = true}) async {
+    includeParentHomeAndPath = true}) async {
   args ??= [];
 
   ensureDir(_pathInSandbox(appPath));
@@ -468,7 +468,21 @@
     ..addAll([pubPath, if (!verbose) '--verbosity=normal'])
     ..addAll(args);
 
-  final mergedEnvironment = getPubTestEnvironment(tokenEndpoint);
+  final systemRoot = Platform.environment['SYSTEMROOT'];
+  final tmp = Platform.environment['TMP'];
+
+  final mergedEnvironment = {
+    if (includeParentHomeAndPath) ...{
+      'HOME': Platform.environment['HOME'] ?? '',
+      'PATH': Platform.environment['PATH'] ?? '',
+    },
+    // These seem to be needed for networking to work.
+    if (Platform.isWindows) ...{
+      if (systemRoot != null) 'SYSTEMROOT': systemRoot,
+      if (tmp != null) 'TMP': tmp,
+    },
+    ...getPubTestEnvironment(tokenEndpoint)
+  };
   for (final e in (environment ?? {}).entries) {
     var value = e.value;
     if (value == null) {
@@ -482,7 +496,7 @@
       environment: mergedEnvironment,
       workingDirectory: workingDirectory ?? _pathInSandbox(appPath),
       description: args.isEmpty ? 'pub' : 'pub ${args.first}',
-      includeParentEnvironment: includeParentEnvironment);
+      includeParentEnvironment: false);
 }
 
 /// A subclass of [TestProcess] that parses pub's verbose logging output and
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt
index 7b8290e..7b01ac0 100644
--- a/test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Adding transitive.txt
@@ -25,7 +25,14 @@
       "name": "foo",
       "version": "1.2.3",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      }
     }
   ]
 }
@@ -40,6 +47,13 @@
       "name": "foo",
       "version": "1.2.3",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "2.2.3",
       "constraint": "^1.0.0",
       "compatible": [],
@@ -48,21 +62,43 @@
           "name": "foo",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         },
         {
           "name": "transitive",
           "version": "1.0.0",
           "kind": "transitive",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "transitive",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": null,
           "constraintWidened": null,
           "constraintBumpedIfNeeded": null,
           "previousVersion": null,
-          "previousConstraint": null
+          "previousConstraint": null,
+          "previousSource": null
         }
       ],
       "multiBreaking": [
@@ -70,21 +106,43 @@
           "name": "foo",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         },
         {
           "name": "transitive",
           "version": "1.0.0",
           "kind": "transitive",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "transitive",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": null,
           "constraintWidened": null,
           "constraintBumpedIfNeeded": null,
           "previousVersion": null,
-          "previousConstraint": null
+          "previousConstraint": null,
+          "previousSource": null
         }
       ]
     }
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Can update a git package.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Can update a git package.txt
new file mode 100644
index 0000000..1f90e4c
--- /dev/null
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Can update a git package.txt
@@ -0,0 +1,235 @@
+# GENERATED BY: test/dependency_services/dependency_services_test.dart
+
+$ cat pubspec.yaml
+{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":{"git":{"url":"../foo.git"}},"bar":{"git":{"url":"../bar.git"},"version":"^1.0.0"}}}
+$ cat pubspec.lock
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  bar:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: HEAD
+      resolved-ref: "92cdd4ac724a6da0db2a69ac149820aa220e8518"
+      url: "../bar.git"
+    source: git
+    version: "1.0.0"
+  foo:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: HEAD
+      resolved-ref: "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba"
+      url: "../foo.git"
+    source: git
+    version: "1.0.0"
+sdks:
+  dart: ">=0.1.2 <1.0.0"
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section list
+$ dependency_services list
+{
+  "dependencies": [
+    {
+      "name": "bar",
+      "version": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+      "kind": "direct",
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "git",
+        "description": {
+          "url": "../bar.git",
+          "ref": "HEAD",
+          "resolved-ref": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+          "path": "."
+        }
+      }
+    },
+    {
+      "name": "foo",
+      "version": "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba",
+      "kind": "direct",
+      "constraint": "any",
+      "source": {
+        "type": "git",
+        "description": {
+          "url": "../foo.git",
+          "ref": "HEAD",
+          "resolved-ref": "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba",
+          "path": "."
+        }
+      }
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section report
+$ dependency_services report
+{
+  "dependencies": [
+    {
+      "name": "bar",
+      "version": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+      "kind": "direct",
+      "source": {
+        "type": "git",
+        "description": {
+          "url": "../bar.git",
+          "ref": "HEAD",
+          "resolved-ref": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+          "path": "."
+        }
+      },
+      "latest": "cf473c33e39eeec4615bc2cde8bf68cbb10ad02c",
+      "constraint": "^1.0.0",
+      "compatible": [],
+      "singleBreaking": [
+        {
+          "name": "bar",
+          "version": "cf473c33e39eeec4615bc2cde8bf68cbb10ad02c",
+          "kind": "direct",
+          "source": {
+            "type": "git",
+            "description": {
+              "url": "../bar.git",
+              "ref": "HEAD",
+              "resolved-ref": "cf473c33e39eeec4615bc2cde8bf68cbb10ad02c",
+              "path": "."
+            }
+          },
+          "constraintBumped": "^2.0.0",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.0.0",
+          "previousVersion": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "git",
+            "description": {
+              "url": "../bar.git",
+              "ref": "HEAD",
+              "resolved-ref": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+              "path": "."
+            }
+          }
+        }
+      ],
+      "multiBreaking": [
+        {
+          "name": "bar",
+          "version": "cf473c33e39eeec4615bc2cde8bf68cbb10ad02c",
+          "kind": "direct",
+          "source": {
+            "type": "git",
+            "description": {
+              "url": "../bar.git",
+              "ref": "HEAD",
+              "resolved-ref": "cf473c33e39eeec4615bc2cde8bf68cbb10ad02c",
+              "path": "."
+            }
+          },
+          "constraintBumped": "^2.0.0",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.0.0",
+          "previousVersion": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "git",
+            "description": {
+              "url": "../bar.git",
+              "ref": "HEAD",
+              "resolved-ref": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+              "path": "."
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "foo",
+      "version": "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba",
+      "kind": "direct",
+      "source": {
+        "type": "git",
+        "description": {
+          "url": "../foo.git",
+          "ref": "HEAD",
+          "resolved-ref": "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba",
+          "path": "."
+        }
+      },
+      "latest": "ad2d659389d4475f6c3a34282e2204160b753fd9",
+      "constraint": "any",
+      "compatible": [],
+      "singleBreaking": [],
+      "multiBreaking": [
+        {
+          "name": "foo",
+          "version": "ad2d659389d4475f6c3a34282e2204160b753fd9",
+          "kind": "direct",
+          "source": {
+            "type": "git",
+            "description": {
+              "url": "../foo.git",
+              "ref": "HEAD",
+              "resolved-ref": "ad2d659389d4475f6c3a34282e2204160b753fd9",
+              "path": "."
+            }
+          },
+          "constraintBumped": "^2.0.0",
+          "constraintWidened": "any",
+          "constraintBumpedIfNeeded": "any",
+          "previousVersion": "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba",
+          "previousConstraint": "any",
+          "previousSource": {
+            "type": "git",
+            "description": {
+              "url": "../foo.git",
+              "ref": "HEAD",
+              "resolved-ref": "1ae220cc484311a7a1e2e31d1ccc6ea995acb6ba",
+              "path": "."
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section apply
+$ echo '{"dependencyChanges":[{"name":"foo","version":"ad2d659389d4475f6c3a34282e2204160b753fd9"}]}' | dependency_services apply
+{"dependencies":[]}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+$ cat pubspec.yaml
+{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":{"git":{"url":"../foo.git"}},"bar":{"git":{"url":"../bar.git"},"version":"^1.0.0"}}}
+$ cat pubspec.lock
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  bar:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: HEAD
+      resolved-ref: "92cdd4ac724a6da0db2a69ac149820aa220e8518"
+      url: "../bar.git"
+    source: git
+    version: "1.0.0"
+  foo:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: HEAD
+      resolved-ref: ad2d659389d4475f6c3a34282e2204160b753fd9
+      url: "../foo.git"
+    source: git
+    version: "2.0.0"
+sdks:
+  dart: ">=0.1.2 <1.0.0"
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Compatible.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Compatible.txt
index c133458..b4726c5 100644
--- a/test/testdata/goldens/dependency_services/dependency_services_test/Compatible.txt
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Compatible.txt
@@ -39,19 +39,40 @@
       "name": "bar",
       "version": "1.2.3",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "bar",
+          "url": "http://localhost:$PORT"
+        }
+      }
     },
     {
       "name": "boo",
       "version": "1.2.3",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "boo",
+          "url": "http://localhost:$PORT"
+        }
+      }
     },
     {
       "name": "foo",
       "version": "1.2.3",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      }
     }
   ]
 }
@@ -66,6 +87,13 @@
       "name": "bar",
       "version": "1.2.3",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "bar",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "2.2.3",
       "constraint": "^1.0.0",
       "compatible": [],
@@ -74,11 +102,25 @@
           "name": "bar",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "multiBreaking": [
@@ -86,11 +128,25 @@
           "name": "bar",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     },
@@ -98,6 +154,13 @@
       "name": "boo",
       "version": "1.2.3",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "boo",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "1.2.4",
       "constraint": "^1.0.0",
       "compatible": [
@@ -105,11 +168,25 @@
           "name": "boo",
           "version": "1.2.4",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "boo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.0.0",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.0.0",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "boo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "singleBreaking": [
@@ -117,11 +194,25 @@
           "name": "boo",
           "version": "1.2.4",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "boo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.2.4",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.0.0",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "boo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "multiBreaking": [
@@ -129,11 +220,25 @@
           "name": "boo",
           "version": "1.2.4",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "boo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.2.4",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.0.0",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "boo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     },
@@ -141,6 +246,13 @@
       "name": "foo",
       "version": "1.2.3",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "2.2.3",
       "constraint": "^1.0.0",
       "compatible": [
@@ -148,11 +260,25 @@
           "name": "foo",
           "version": "1.2.4",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.0.0",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.0.0",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "singleBreaking": [
@@ -160,11 +286,25 @@
           "name": "foo",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "multiBreaking": [
@@ -172,11 +312,25 @@
           "name": "foo",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     }
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/No pubspec.lock.txt b/test/testdata/goldens/dependency_services/dependency_services_test/No pubspec.lock.txt
new file mode 100644
index 0000000..ca80a34
--- /dev/null
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/No pubspec.lock.txt
@@ -0,0 +1,205 @@
+# GENERATED BY: test/dependency_services/dependency_services_test.dart
+
+$ cat pubspec.yaml
+{"name":"app","dependencies":{"foo":"^1.0.0","bar":{"git":{"url":"../bar.git"}}},"environment":{"sdk":">=0.1.2 <1.0.0"}}
+$ cat pubspec.lock
+No such file pubspec.lock.
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section list
+$ dependency_services list
+{
+  "dependencies": [
+    {
+      "name": "bar",
+      "version": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+      "kind": "direct",
+      "constraint": "any",
+      "source": {
+        "type": "git",
+        "description": {
+          "url": "../bar.git",
+          "ref": "HEAD",
+          "resolved-ref": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+          "path": "."
+        }
+      }
+    },
+    {
+      "name": "foo",
+      "version": "1.2.3",
+      "kind": "direct",
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      }
+    },
+    {
+      "name": "transitive",
+      "version": "1.0.0",
+      "kind": "transitive",
+      "constraint": "null",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "transitive",
+          "url": "http://localhost:$PORT"
+        }
+      }
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section report
+$ dependency_services report
+{
+  "dependencies": [
+    {
+      "name": "bar",
+      "version": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+      "kind": "direct",
+      "source": {
+        "type": "git",
+        "description": {
+          "url": "../bar.git",
+          "ref": "HEAD",
+          "resolved-ref": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+          "path": "."
+        }
+      },
+      "latest": "92cdd4ac724a6da0db2a69ac149820aa220e8518",
+      "constraint": "any",
+      "compatible": [],
+      "singleBreaking": [],
+      "multiBreaking": [
+        {
+          "name": "foo",
+          "version": "2.2.3",
+          "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
+          "constraintBumped": "^2.2.3",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.2.3",
+          "previousVersion": "1.2.3",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "foo",
+      "version": "1.2.3",
+      "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      },
+      "latest": "2.2.3",
+      "constraint": "^1.0.0",
+      "compatible": [],
+      "singleBreaking": [
+        {
+          "name": "foo",
+          "version": "2.2.3",
+          "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
+          "constraintBumped": "^2.2.3",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.2.3",
+          "previousVersion": "1.2.3",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
+        }
+      ],
+      "multiBreaking": [
+        {
+          "name": "foo",
+          "version": "2.2.3",
+          "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
+          "constraintBumped": "^2.2.3",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.2.3",
+          "previousVersion": "1.2.3",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "transitive",
+      "version": "1.0.0",
+      "kind": "transitive",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "transitive",
+          "url": "http://localhost:$PORT"
+        }
+      },
+      "latest": "1.0.0",
+      "constraint": null,
+      "compatible": [],
+      "singleBreaking": [],
+      "multiBreaking": []
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section apply
+$ echo '{"dependencyChanges":[{"name":"foo","version":"2.2.3"},{"name":"transitive","version":null}]}' | dependency_services apply
+{"dependencies":[]}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+$ cat pubspec.yaml
+{"name":"app","dependencies":{"foo":^2.2.3,"bar":{"git":{"url":"../bar.git"}}},"environment":{"sdk":">=0.1.2 <1.0.0"}}
+$ cat pubspec.lock
+No such file pubspec.lock.
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Relative paths are allowed.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Relative paths are allowed.txt
new file mode 100644
index 0000000..69a06d4
--- /dev/null
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Relative paths are allowed.txt
@@ -0,0 +1,182 @@
+# GENERATED BY: test/dependency_services/dependency_services_test.dart
+
+$ cat pubspec.yaml
+{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":"^1.0.0","bar":{"path":"../bar"}}}
+$ cat pubspec.lock
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  bar:
+    dependency: "direct main"
+    description:
+      path: "../bar"
+      relative: true
+    source: path
+    version: "1.0.0"
+  foo:
+    dependency: "direct main"
+    description:
+      name: foo
+      url: "http://localhost:$PORT"
+    source: hosted
+    version: "1.0.0"
+sdks:
+  dart: ">=0.1.2 <1.0.0"
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section list
+$ dependency_services list
+{
+  "dependencies": [
+    {
+      "name": "bar",
+      "version": "1.0.0",
+      "kind": "direct",
+      "constraint": "any",
+      "source": {
+        "type": "path",
+        "description": {
+          "path": "../bar",
+          "relative": true
+        }
+      }
+    },
+    {
+      "name": "foo",
+      "version": "1.0.0",
+      "kind": "direct",
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      }
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section report
+$ dependency_services report
+{
+  "dependencies": [
+    {
+      "name": "bar",
+      "version": "1.0.0",
+      "kind": "direct",
+      "source": {
+        "type": "path",
+        "description": {
+          "path": "../bar",
+          "relative": true
+        }
+      },
+      "latest": "1.0.0",
+      "constraint": "any",
+      "compatible": [],
+      "singleBreaking": [],
+      "multiBreaking": []
+    },
+    {
+      "name": "foo",
+      "version": "1.0.0",
+      "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      },
+      "latest": "2.0.0",
+      "constraint": "^1.0.0",
+      "compatible": [],
+      "singleBreaking": [
+        {
+          "name": "foo",
+          "version": "2.0.0",
+          "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
+          "constraintBumped": "^2.0.0",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.0.0",
+          "previousVersion": "1.0.0",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
+        }
+      ],
+      "multiBreaking": [
+        {
+          "name": "foo",
+          "version": "2.0.0",
+          "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
+          "constraintBumped": "^2.0.0",
+          "constraintWidened": ">=1.0.0 <3.0.0",
+          "constraintBumpedIfNeeded": "^2.0.0",
+          "previousVersion": "1.0.0",
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section apply
+$ echo '{"dependencyChanges":[{"name":"foo","version":"2.0.0","constraint":"^2.0.0"}]}' | dependency_services apply
+{"dependencies":[]}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+$ cat pubspec.yaml
+{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":^2.0.0,"bar":{"path":"../bar"}}}
+$ cat pubspec.lock
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  bar:
+    dependency: "direct main"
+    description:
+      path: "../bar"
+      relative: true
+    source: path
+    version: "1.0.0"
+  foo:
+    dependency: "direct main"
+    description:
+      name: foo
+      url: "http://localhost:$PORT"
+    source: hosted
+    version: "2.0.0"
+sdks:
+  dart: ">=0.1.2 <1.0.0"
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt
index bfc5a78..50ee46f 100644
--- a/test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/Removing transitive.txt
@@ -32,13 +32,27 @@
       "name": "foo",
       "version": "1.2.3",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      }
     },
     {
       "name": "transitive",
       "version": "1.0.0",
       "kind": "transitive",
-      "constraint": "null"
+      "constraint": "null",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "transitive",
+          "url": "http://localhost:$PORT"
+        }
+      }
     }
   ]
 }
@@ -53,6 +67,13 @@
       "name": "foo",
       "version": "1.2.3",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "2.2.3",
       "constraint": "^1.0.0",
       "compatible": [],
@@ -61,11 +82,25 @@
           "name": "foo",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         },
         {
           "name": "transitive",
@@ -75,7 +110,14 @@
           "constraintWidened": null,
           "constraintBumpedIfNeeded": null,
           "previousVersion": "1.0.0",
-          "previousConstraint": null
+          "previousConstraint": null,
+          "previous": {
+            "type": "hosted",
+            "description": {
+              "name": "transitive",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "multiBreaking": [
@@ -83,11 +125,25 @@
           "name": "foo",
           "version": "2.2.3",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.2.3",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.2.3",
           "previousVersion": "1.2.3",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         },
         {
           "name": "transitive",
@@ -97,7 +153,14 @@
           "constraintWidened": null,
           "constraintBumpedIfNeeded": null,
           "previousVersion": "1.0.0",
-          "previousConstraint": null
+          "previousConstraint": null,
+          "previous": {
+            "type": "hosted",
+            "description": {
+              "name": "transitive",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     },
@@ -105,6 +168,13 @@
       "name": "transitive",
       "version": "1.0.0",
       "kind": "transitive",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "transitive",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "1.0.0",
       "constraint": null,
       "compatible": [],
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/multibreaking.txt b/test/testdata/goldens/dependency_services/dependency_services_test/multibreaking.txt
index 32bc04e..da9222d 100644
--- a/test/testdata/goldens/dependency_services/dependency_services_test/multibreaking.txt
+++ b/test/testdata/goldens/dependency_services/dependency_services_test/multibreaking.txt
@@ -39,19 +39,40 @@
       "name": "bar",
       "version": "1.0.0",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "bar",
+          "url": "http://localhost:$PORT"
+        }
+      }
     },
     {
       "name": "baz",
       "version": "1.0.0",
       "kind": "direct",
-      "constraint": "1.0.0"
+      "constraint": "1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "baz",
+          "url": "http://localhost:$PORT"
+        }
+      }
     },
     {
       "name": "foo",
       "version": "1.0.0",
       "kind": "direct",
-      "constraint": "^1.0.0"
+      "constraint": "^1.0.0",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      }
     }
   ]
 }
@@ -66,6 +87,13 @@
       "name": "bar",
       "version": "1.0.0",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "bar",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "2.0.0",
       "constraint": "^1.0.0",
       "compatible": [],
@@ -75,21 +103,49 @@
           "name": "bar",
           "version": "2.0.0",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.0.0",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.0.0",
           "previousVersion": "1.0.0",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          }
         },
         {
           "name": "foo",
           "version": "3.0.1",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^3.0.1",
           "constraintWidened": ">=1.0.0 <4.0.0",
           "constraintBumpedIfNeeded": "^3.0.1",
           "previousVersion": "1.0.0",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     },
@@ -97,6 +153,13 @@
       "name": "baz",
       "version": "1.0.0",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "baz",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "1.1.0",
       "constraint": "1.0.0",
       "compatible": [],
@@ -105,11 +168,25 @@
           "name": "baz",
           "version": "1.1.0",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "baz",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.1.0",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.1.0",
           "previousVersion": "1.0.0",
-          "previousConstraint": "1.0.0"
+          "previousConstraint": "1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "baz",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "multiBreaking": [
@@ -117,11 +194,25 @@
           "name": "baz",
           "version": "1.1.0",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "baz",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.1.0",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.1.0",
           "previousVersion": "1.0.0",
-          "previousConstraint": "1.0.0"
+          "previousConstraint": "1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "baz",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     },
@@ -129,6 +220,13 @@
       "name": "foo",
       "version": "1.0.0",
       "kind": "direct",
+      "source": {
+        "type": "hosted",
+        "description": {
+          "name": "foo",
+          "url": "http://localhost:$PORT"
+        }
+      },
       "latest": "3.0.1",
       "constraint": "^1.0.0",
       "compatible": [
@@ -136,11 +234,25 @@
           "name": "foo",
           "version": "1.5.0",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^1.0.0",
           "constraintWidened": "^1.0.0",
           "constraintBumpedIfNeeded": "^1.0.0",
           "previousVersion": "1.0.0",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "singleBreaking": [
@@ -148,11 +260,25 @@
           "name": "foo",
           "version": "2.0.0",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.0.0",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.0.0",
           "previousVersion": "1.0.0",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ],
       "multiBreaking": [
@@ -160,21 +286,49 @@
           "name": "foo",
           "version": "3.0.1",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^3.0.1",
           "constraintWidened": ">=1.0.0 <4.0.0",
           "constraintBumpedIfNeeded": "^3.0.1",
           "previousVersion": "1.0.0",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "foo",
+              "url": "http://localhost:$PORT"
+            }
+          }
         },
         {
           "name": "bar",
           "version": "2.0.0",
           "kind": "direct",
+          "source": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          },
           "constraintBumped": "^2.0.0",
           "constraintWidened": ">=1.0.0 <3.0.0",
           "constraintBumpedIfNeeded": "^2.0.0",
           "previousVersion": "1.0.0",
-          "previousConstraint": "^1.0.0"
+          "previousConstraint": "^1.0.0",
+          "previousSource": {
+            "type": "hosted",
+            "description": {
+              "name": "bar",
+              "url": "http://localhost:$PORT"
+            }
+          }
         }
       ]
     }
diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
index 1d3a5f5..fc69b03 100644
--- a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -81,16 +81,6 @@
 [E]    |   version: "1.0.0"
 [E]    | sdks:
 [E]    |   dart: ">=0.1.2 <1.0.0"
-[E] IO  : Writing $N characters to text file .packages.
-[E] FINE: Contents:
-[E]    | # This file is deprecated. Tools should instead consume 
-[E]    | # `.dart_tool/package_config.json`.
-[E]    | # 
-[E]    | # For more info see: https://dart.dev/go/dot-packages-deprecation
-[E]    | # 
-[E]    | # Generated by pub on $TIME
-[E]    | foo:file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0/lib/
-[E]    | myapp:lib/
 [E] IO  : Writing $N characters to text file .dart_tool/package_config.json.
 [E] FINE: Contents:
 [E]    | {
@@ -230,16 +220,6 @@
    | sdks:
    |   dart: ">=0.1.2 <1.0.0"
 MSG : Changed 1 dependency!
-IO  : Writing $N characters to text file .packages.
-FINE: Contents:
-   | # This file is deprecated. Tools should instead consume 
-   | # `.dart_tool/package_config.json`.
-   | # 
-   | # For more info see: https://dart.dev/go/dot-packages-deprecation
-   | # 
-   | # Generated by pub on $TIME
-   | foo:file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0/lib/
-   | myapp:lib/
 IO  : Writing $N characters to text file .dart_tool/package_config.json.
 FINE: Contents:
    | {
diff --git a/test/testdata/goldens/help_test/pub add --help.txt b/test/testdata/goldens/help_test/pub add --help.txt
index 2a37c21..3d1f429 100644
--- a/test/testdata/goldens/help_test/pub add --help.txt
+++ b/test/testdata/goldens/help_test/pub add --help.txt
@@ -5,20 +5,22 @@
 Add dependencies to pubspec.yaml.
 
 Usage: pub add <package>[:<constraint>] [<package2>[:<constraint2>]...] [options]
--h, --help               Print this usage information.
--d, --dev                Adds to the development dependencies instead.
-    --git-url            Git URL of the package
-    --git-ref            Git branch or commit to be retrieved
-    --git-path           Path of git package in repository
-    --hosted-url         URL of package host server
-    --path               Add package from local path
-    --sdk=<[flutter]>    add package from SDK source
-                         [flutter]
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
--C, --directory=<dir>    Run this in the directory <dir>.
+-h, --help                    Print this usage information.
+-d, --dev                     Adds to the development dependencies instead.
+    --git-url                 Git URL of the package
+    --git-ref                 Git branch or commit to be retrieved
+    --git-path                Path of git package in repository
+    --hosted-url              URL of package host server
+    --path                    Add package from local path
+    --sdk=<[flutter]>         add package from SDK source
+                              [flutter]
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Build executables in immediate dependencies.
+-C, --directory=<dir>         Run this in the directory <dir>.
+    --legacy-packages-file    Generate the legacy ".packages" file
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-add for detailed documentation.
diff --git a/test/testdata/goldens/help_test/pub downgrade --help.txt b/test/testdata/goldens/help_test/pub downgrade --help.txt
index 4497da2..ddf71e7 100644
--- a/test/testdata/goldens/help_test/pub downgrade --help.txt
+++ b/test/testdata/goldens/help_test/pub downgrade --help.txt
@@ -7,11 +7,13 @@
 
 
 Usage: pub downgrade [dependencies...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
--C, --directory=<dir>    Run this in the directory<dir>.
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+-C, --directory=<dir>         Run this in the directory<dir>.
+    --legacy-packages-file    Generate the legacy ".packages" file
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-downgrade for detailed documentation.
diff --git a/test/testdata/goldens/help_test/pub get --help.txt b/test/testdata/goldens/help_test/pub get --help.txt
index 74648a2..104f69c 100644
--- a/test/testdata/goldens/help_test/pub get --help.txt
+++ b/test/testdata/goldens/help_test/pub get --help.txt
@@ -5,12 +5,14 @@
 Get the current package's dependencies.
 
 Usage: pub get <subcommand> [arguments...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
--C, --directory=<dir>    Run this in the directory<dir>.
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Build executables in immediate dependencies.
+    --legacy-packages-file    Generate the legacy ".packages" file
+-C, --directory=<dir>         Run this in the directory<dir>.
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-get for detailed documentation.
diff --git a/test/testdata/goldens/help_test/pub remove --help.txt b/test/testdata/goldens/help_test/pub remove --help.txt
index 7d14ed0..b82f9b6 100644
--- a/test/testdata/goldens/help_test/pub remove --help.txt
+++ b/test/testdata/goldens/help_test/pub remove --help.txt
@@ -5,12 +5,14 @@
 Removes a dependency from the current package.
 
 Usage: pub remove <package>
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Precompile executables in immediate dependencies.
--C, --directory=<dir>    Run this in the directory<dir>.
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Precompile executables in immediate dependencies.
+-C, --directory=<dir>         Run this in the directory<dir>.
+    --legacy-packages-file    Generate the legacy ".packages" file
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-remove for detailed documentation.
diff --git a/test/testdata/goldens/help_test/pub upgrade --help.txt b/test/testdata/goldens/help_test/pub upgrade --help.txt
index d1a29b9..c787951 100644
--- a/test/testdata/goldens/help_test/pub upgrade --help.txt
+++ b/test/testdata/goldens/help_test/pub upgrade --help.txt
@@ -5,16 +5,18 @@
 Upgrade the current package's dependencies to latest versions.
 
 Usage: pub upgrade [dependencies...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Precompile executables in immediate dependencies.
-    --null-safety        Upgrade constraints in pubspec.yaml to null-safety
-                         versions
-    --major-versions     Upgrades packages to their latest resolvable versions,
-                         and updates pubspec.yaml.
--C, --directory=<dir>    Run this in the directory<dir>.
+-h, --help                    Print this usage information.
+    --[no-]offline            Use cached packages instead of accessing the
+                              network.
+-n, --dry-run                 Report what dependencies would change but don't
+                              change any.
+    --[no-]precompile         Precompile executables in immediate dependencies.
+    --null-safety             Upgrade constraints in pubspec.yaml to null-safety
+                              versions
+    --legacy-packages-file    Generate the legacy ".packages" file
+    --major-versions          Upgrades packages to their latest resolvable
+                              versions, and updates pubspec.yaml.
+-C, --directory=<dir>         Run this in the directory<dir>.
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-upgrade for detailed documentation.
diff --git a/test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt b/test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt
index c9bc73f..5a9578a 100644
--- a/test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt
+++ b/test/testdata/goldens/outdated/outdated_test/Handles SDK dependencies.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.1.0"
       },
@@ -189,6 +190,7 @@
   "packages": [
     {
       "package": "flutter_test",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -208,6 +210,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.1.0",
         "nullSafety": false
@@ -236,6 +239,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.1.0"
       },
diff --git a/test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt b/test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt
index b9ab0a5..c01a007 100644
--- a/test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt
+++ b/test/testdata/goldens/outdated/outdated_test/circular dependency on root.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
@@ -168,6 +169,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3",
         "nullSafety": false
@@ -196,6 +198,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
diff --git a/test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt b/test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt
index 12c2ee3..92a4ba1 100644
--- a/test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt
+++ b/test/testdata/goldens/outdated/outdated_test/latest version reported while locked on a prerelease can be a prerelease.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0-dev.1"
       },
@@ -21,6 +22,7 @@
     },
     {
       "package": "mop",
+      "isDiscontinued": false,
       "current": {
         "version": "0.10.0-dev"
       },
@@ -197,6 +199,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "0.9.0",
         "nullSafety": false
@@ -216,6 +219,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0-dev.1",
         "nullSafety": false
@@ -235,6 +239,7 @@
     },
     {
       "package": "mop",
+      "isDiscontinued": false,
       "current": {
         "version": "0.10.0-dev",
         "nullSafety": false
@@ -263,6 +268,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0-dev.1"
       },
@@ -278,6 +284,7 @@
     },
     {
       "package": "mop",
+      "isDiscontinued": false,
       "current": {
         "version": "0.10.0-dev"
       },
diff --git a/test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt b/test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt
index 18baa88..c8d7e0b 100644
--- a/test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt
+++ b/test/testdata/goldens/outdated/outdated_test/mutually incompatible newer versions.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -21,6 +22,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -183,6 +185,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -202,6 +205,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -230,6 +234,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -245,6 +250,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
diff --git a/test/testdata/goldens/outdated/outdated_test/newer versions available.txt b/test/testdata/goldens/outdated/outdated_test/newer versions available.txt
index d6c0ff9..3e71426 100644
--- a/test/testdata/goldens/outdated/outdated_test/newer versions available.txt
+++ b/test/testdata/goldens/outdated/outdated_test/newer versions available.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "builder",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
@@ -21,6 +22,7 @@
     },
     {
       "package": "dev_trans",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -34,6 +36,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
@@ -49,6 +52,7 @@
     },
     {
       "package": "transitive",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
@@ -64,6 +68,7 @@
     },
     {
       "package": "transitive2",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": null,
       "resolvable": {
@@ -75,6 +80,7 @@
     },
     {
       "package": "transitive3",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": null,
       "resolvable": {
@@ -335,6 +341,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -354,6 +361,7 @@
     },
     {
       "package": "builder",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3",
         "nullSafety": false
@@ -373,6 +381,7 @@
     },
     {
       "package": "dev_trans",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -389,6 +398,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3",
         "nullSafety": false
@@ -408,6 +418,7 @@
     },
     {
       "package": "local_package",
+      "isDiscontinued": false,
       "current": {
         "version": "0.0.1",
         "nullSafety": false
@@ -427,6 +438,7 @@
     },
     {
       "package": "transitive",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3",
         "nullSafety": false
@@ -446,6 +458,7 @@
     },
     {
       "package": "transitive2",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": null,
       "resolvable": {
@@ -459,6 +472,7 @@
     },
     {
       "package": "transitive3",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": null,
       "resolvable": {
@@ -481,6 +495,7 @@
   "packages": [
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
@@ -496,6 +511,7 @@
     },
     {
       "package": "transitive",
+      "isDiscontinued": false,
       "current": {
         "version": "1.2.3"
       },
diff --git a/test/testdata/goldens/outdated/outdated_test/no lockfile.txt b/test/testdata/goldens/outdated/outdated_test/no lockfile.txt
index 7f670db..87a8997 100644
--- a/test/testdata/goldens/outdated/outdated_test/no lockfile.txt
+++ b/test/testdata/goldens/outdated/outdated_test/no lockfile.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": {
         "version": "1.2.3"
@@ -19,6 +20,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": {
         "version": "1.2.3"
@@ -215,6 +217,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": {
         "version": "1.2.3",
@@ -231,6 +234,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": {
         "version": "1.2.3",
@@ -256,6 +260,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": {
         "version": "1.2.3"
@@ -269,6 +274,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": null,
       "upgradable": {
         "version": "1.2.3"
diff --git a/test/testdata/goldens/outdated/outdated_test/null safety compliance.txt b/test/testdata/goldens/outdated/outdated_test/null safety compliance.txt
index d345f55..507d194 100644
--- a/test/testdata/goldens/outdated/outdated_test/null safety compliance.txt
+++ b/test/testdata/goldens/outdated/outdated_test/null safety compliance.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -21,6 +22,7 @@
     },
     {
       "package": "fails_analysis",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -36,6 +38,7 @@
     },
     {
       "package": "fails_analysis_in_dependency",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -51,6 +54,7 @@
     },
     {
       "package": "file_in_dependency_opts_out",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -66,6 +70,7 @@
     },
     {
       "package": "file_opts_out",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -81,6 +86,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -276,6 +282,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -295,6 +302,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -323,6 +331,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -338,6 +347,7 @@
     },
     {
       "package": "fails_analysis",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -353,6 +363,7 @@
     },
     {
       "package": "fails_analysis_in_dependency",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -368,6 +379,7 @@
     },
     {
       "package": "file_in_dependency_opts_out",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -383,6 +395,7 @@
     },
     {
       "package": "file_opts_out",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
@@ -398,6 +411,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0"
       },
diff --git a/test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt b/test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt
index 3d227ec..5d3cf9b 100644
--- a/test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt
+++ b/test/testdata/goldens/outdated/outdated_test/null-safety already migrated.txt
@@ -127,6 +127,7 @@
   "packages": [
     {
       "package": "devTransitive",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
diff --git a/test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt b/test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt
index 1ab20ad..bc19cb6 100644
--- a/test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt
+++ b/test/testdata/goldens/outdated/outdated_test/null-safety no resolution.txt
@@ -125,6 +125,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
@@ -141,6 +142,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "nullSafety": false
diff --git a/test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt b/test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt
index 909ee0a..521da52 100644
--- a/test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt
+++ b/test/testdata/goldens/outdated/outdated_test/overridden dependencies - no resolution.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "overridden": true
@@ -24,6 +25,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "overridden": true
@@ -188,6 +190,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "overridden": true,
@@ -210,6 +213,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "overridden": true,
@@ -241,6 +245,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "overridden": true
@@ -259,6 +264,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.0",
         "overridden": true
diff --git a/test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt b/test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt
index 49abe1b..a761ce0 100644
--- a/test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt
+++ b/test/testdata/goldens/outdated/outdated_test/overridden dependencies.txt
@@ -6,6 +6,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.1",
         "overridden": true
@@ -24,6 +25,7 @@
     },
     {
       "package": "baz",
+      "isDiscontinued": false,
       "current": {
         "version": "2.0.0",
         "overridden": true
@@ -42,6 +44,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.1",
         "overridden": true
@@ -220,6 +223,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.1",
         "overridden": true,
@@ -242,6 +246,7 @@
     },
     {
       "package": "baz",
+      "isDiscontinued": false,
       "current": {
         "version": "2.0.0",
         "overridden": true,
@@ -264,6 +269,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.1",
         "overridden": true,
@@ -295,6 +301,7 @@
   "packages": [
     {
       "package": "bar",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.1",
         "overridden": true
@@ -313,6 +320,7 @@
     },
     {
       "package": "baz",
+      "isDiscontinued": false,
       "current": {
         "version": "2.0.0",
         "overridden": true
@@ -331,6 +339,7 @@
     },
     {
       "package": "foo",
+      "isDiscontinued": false,
       "current": {
         "version": "1.0.1",
         "overridden": true
diff --git a/test/testdata/goldens/outdated/outdated_test/show discontinued.txt b/test/testdata/goldens/outdated/outdated_test/show discontinued.txt
new file mode 100644
index 0000000..97e2717
--- /dev/null
+++ b/test/testdata/goldens/outdated/outdated_test/show discontinued.txt
@@ -0,0 +1,243 @@
+# GENERATED BY: test/outdated/outdated_test.dart
+
+## Section 0
+$ pub outdated --json
+{
+  "packages": []
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 1
+$ pub outdated --no-color
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 2
+$ pub outdated --no-color --no-transitive
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 3
+$ pub outdated --no-color --up-to-date
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable  Resolvable  Latest                
+
+direct dependencies:
+bar           1.0.0    1.0.0       1.0.0       1.0.0                 
+baz           1.0.0    1.0.0       1.0.0       1.0.0 (discontinued)  
+foo           1.2.3    1.2.3       1.2.3       1.2.3 (discontinued)  
+
+transitive dependencies:
+transitive    1.2.3    1.2.3       1.2.3       1.2.3                 
+You are already using the newest resolvable versions listed in the 'Resolvable' column.
+Newer versions, listed in 'Latest', may not be mutually compatible.
+
+baz
+    Package baz has been discontinued, replaced by newbaz.
+foo
+    Package foo has been discontinued.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 4
+$ pub outdated --no-color --prereleases
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 5
+$ pub outdated --no-color --no-dev-dependencies
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 6
+$ pub outdated --no-color --no-dependency-overrides
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 7
+$ pub outdated --no-color --mode=null-safety
+Showing dependencies that are currently not opted in to null-safety.
+[✗] indicates versions without null safety support.
+[✓] indicates versions opting in to null safety.
+
+Package Name  Current  Upgradable  Resolvable  Latest                 
+
+direct dependencies:
+bar           ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0                 
+baz           ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0 (discontinued)  
+foo           ✗1.2.3   ✗1.2.3      ✗1.2.3      ✗1.2.3 (discontinued)  
+You are already using the newest resolvable versions listed in the 'Resolvable' column.
+Newer versions, listed in 'Latest', may not be mutually compatible.
+
+baz
+    Package baz has been discontinued, replaced by newbaz.
+foo
+    Package foo has been discontinued.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 8
+$ pub outdated --no-color --mode=null-safety --transitive
+Showing dependencies that are currently not opted in to null-safety.
+[✗] indicates versions without null safety support.
+[✓] indicates versions opting in to null safety.
+
+Package Name  Current  Upgradable  Resolvable  Latest                 
+
+direct dependencies:
+bar           ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0                 
+baz           ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0 (discontinued)  
+foo           ✗1.2.3   ✗1.2.3      ✗1.2.3      ✗1.2.3 (discontinued)  
+
+transitive dependencies:
+transitive    ✗1.2.3   ✗1.2.3      ✗1.2.3      ✗1.2.3                 
+You are already using the newest resolvable versions listed in the 'Resolvable' column.
+Newer versions, listed in 'Latest', may not be mutually compatible.
+
+baz
+    Package baz has been discontinued, replaced by newbaz.
+foo
+    Package foo has been discontinued.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 9
+$ pub outdated --no-color --mode=null-safety --no-prereleases
+Showing dependencies that are currently not opted in to null-safety.
+[✗] indicates versions without null safety support.
+[✓] indicates versions opting in to null safety.
+
+Package Name  Current  Upgradable  Resolvable  Latest                 
+
+direct dependencies:
+bar           ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0                 
+baz           ✗1.0.0   ✗1.0.0      ✗1.0.0      ✗1.0.0 (discontinued)  
+foo           ✗1.2.3   ✗1.2.3      ✗1.2.3      ✗1.2.3 (discontinued)  
+You are already using the newest resolvable versions listed in the 'Resolvable' column.
+Newer versions, listed in 'Latest', may not be mutually compatible.
+
+baz
+    Package baz has been discontinued, replaced by newbaz.
+foo
+    Package foo has been discontinued.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 10
+$ pub outdated --json --mode=null-safety
+{
+  "packages": [
+    {
+      "package": "bar",
+      "isDiscontinued": false,
+      "current": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "upgradable": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "resolvable": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "latest": {
+        "version": "1.0.0",
+        "nullSafety": false
+      }
+    },
+    {
+      "package": "baz",
+      "isDiscontinued": true,
+      "current": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "upgradable": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "resolvable": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "latest": {
+        "version": "1.0.0",
+        "nullSafety": false
+      }
+    },
+    {
+      "package": "foo",
+      "isDiscontinued": true,
+      "current": {
+        "version": "1.2.3",
+        "nullSafety": false
+      },
+      "upgradable": {
+        "version": "1.2.3",
+        "nullSafety": false
+      },
+      "resolvable": {
+        "version": "1.2.3",
+        "nullSafety": false
+      },
+      "latest": {
+        "version": "1.2.3",
+        "nullSafety": false
+      }
+    },
+    {
+      "package": "transitive",
+      "isDiscontinued": false,
+      "current": {
+        "version": "1.2.3",
+        "nullSafety": false
+      },
+      "upgradable": {
+        "version": "1.2.3",
+        "nullSafety": false
+      },
+      "resolvable": {
+        "version": "1.2.3",
+        "nullSafety": false
+      },
+      "latest": {
+        "version": "1.2.3",
+        "nullSafety": false
+      }
+    }
+  ]
+}
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+## Section 11
+$ pub outdated --json --no-dev-dependencies
+{
+  "packages": []
+}
+
diff --git a/test/token/add_token_test.dart b/test/token/add_token_test.dart
index 65f7d56..e8d0dc4 100644
--- a/test/token/add_token_test.dart
+++ b/test/token/add_token_test.dart
@@ -147,7 +147,7 @@
       error: contains('No config dir found.'),
       exitCode: exit_codes.DATA,
       environment: {'_PUB_TEST_CONFIG_DIR': null},
-      includeParentEnvironment: false,
+      includeParentHomeAndPath: false,
     );
   });
 
diff --git a/test/token/remove_token_test.dart b/test/token/remove_token_test.dart
index bfe2428..df880a4 100644
--- a/test/token/remove_token_test.dart
+++ b/test/token/remove_token_test.dart
@@ -66,7 +66,7 @@
       error: contains('No config dir found.'),
       exitCode: exit_codes.DATA,
       environment: {'_PUB_TEST_CONFIG_DIR': null},
-      includeParentEnvironment: false,
+      includeParentHomeAndPath: false,
     );
   });
 }
diff --git a/test/unknown_source_test.dart b/test/unknown_source_test.dart
index fc53621..37591d0 100644
--- a/test/unknown_source_test.dart
+++ b/test/unknown_source_test.dart
@@ -72,7 +72,9 @@
       await pubCommand(command);
 
       // Should upgrade to the new one.
-      await d.appPackagesFile({'foo': '../foo'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', path: '../foo'),
+      ]).validate();
     });
   });
 }
diff --git a/test/upgrade/hosted/unlock_if_necessary_test.dart b/test/upgrade/hosted/unlock_if_necessary_test.dart
index c285e66..2707100 100644
--- a/test/upgrade/hosted/unlock_if_necessary_test.dart
+++ b/test/upgrade/hosted/unlock_if_necessary_test.dart
@@ -20,13 +20,19 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'foo_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '1.0.0'),
+    ]).validate();
 
     server.serve('foo', '2.0.0', deps: {'foo_dep': '>1.0.0'});
     server.serve('foo_dep', '2.0.0');
 
     await pubUpgrade(args: ['foo']);
 
-    await d.appPackagesFile({'foo': '2.0.0', 'foo_dep': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'foo_dep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/upgrade/hosted/unlock_single_package_test.dart b/test/upgrade/hosted/unlock_single_package_test.dart
index bc78a3d..e443a6a 100644
--- a/test/upgrade/hosted/unlock_single_package_test.dart
+++ b/test/upgrade/hosted/unlock_single_package_test.dart
@@ -18,7 +18,10 @@
 
     await pubGet();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
 
     server.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
     server.serve('bar', '2.0.0');
@@ -26,16 +29,23 @@
     // This can't upgrade 'bar'
     await pubUpgrade(args: ['bar']);
 
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
-
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+    ]).validate();
     // Introducing foo and bar 1.1.0, to show that only 'bar' will be upgraded
     server.serve('foo', '1.1.0', deps: {'bar': '<2.0.0'});
     server.serve('bar', '1.1.0');
 
     await pubUpgrade(args: ['bar']);
-    await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
-
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.1.0'),
+    ]).validate();
     await pubUpgrade();
-    await d.appPackagesFile({'foo': '2.0.0', 'bar': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/upgrade/hosted/upgrade_removed_constraints_test.dart b/test/upgrade/hosted/upgrade_removed_constraints_test.dart
index 248ac90..cf72c07 100644
--- a/test/upgrade/hosted/upgrade_removed_constraints_test.dart
+++ b/test/upgrade/hosted/upgrade_removed_constraints_test.dart
@@ -19,13 +19,19 @@
 
     await pubUpgrade();
 
-    await d.appPackagesFile(
-        {'foo': '1.0.0', 'bar': '1.0.0', 'shared_dep': '1.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '1.0.0'),
+    ]).validate();
 
     await d.appDir({'foo': 'any'}).create();
 
     await pubUpgrade();
 
-    await d.appPackagesFile({'foo': '1.0.0', 'shared_dep': '2.0.0'}).validate();
+    await d.appPackageConfigFile([
+      d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+      d.packageConfigEntry(name: 'shared_dep', version: '2.0.0'),
+    ]).validate();
   });
 }
diff --git a/test/upgrade/upgrade_major_versions_test.dart b/test/upgrade/upgrade_major_versions_test.dart
index 932ca17..c5f0994 100644
--- a/test/upgrade/upgrade_major_versions_test.dart
+++ b/test/upgrade/upgrade_major_versions_test.dart
@@ -41,12 +41,11 @@
         'bar': '^0.2.0',
         'baz': '^1.0.0',
       }).validate();
-
-      await d.appPackagesFile({
-        'foo': '2.0.0',
-        'bar': '0.2.0',
-        'baz': '1.0.1',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '0.2.0'),
+        d.packageConfigEntry(name: 'baz', version: '1.0.1'),
+      ]).validate();
     });
 
     test('bumps dev_dependency constraints and shows summary report', () async {
@@ -92,11 +91,11 @@
         }),
       ]).validate();
 
-      await d.appPackagesFile({
-        'foo': '2.0.0',
-        'bar': '0.2.0',
-        'baz': '1.0.1',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '0.2.0'),
+        d.packageConfigEntry(name: 'baz', version: '1.0.1'),
+      ]).validate();
     });
 
     test('upgrades only the selected package', () async {
@@ -128,7 +127,10 @@
         'bar': '^0.1.0',
       }).validate();
 
-      await d.appPackagesFile({'foo': '2.0.0', 'bar': '0.1.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '2.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '0.1.0'),
+      ]).validate();
     });
 
     test('chooses the latest version where possible', () async {
@@ -159,7 +161,9 @@
         d.file('pubspec.lock', contains('3.0.0'))
       ]).validate();
 
-      await d.appPackagesFile({'foo': '3.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '3.0.0'),
+      ]).validate();
     });
 
     test('overridden dependencies - no resolution', () async {
@@ -211,7 +215,10 @@
         })
       ]).validate();
 
-      await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+      ]).validate();
     });
 
     test('upgrade should not downgrade any versions', () async {
@@ -249,10 +256,10 @@
         'bar': '^4.0.0',
       }).validate();
 
-      await d.appPackagesFile({
-        'foo': '1.0.0',
-        'bar': '4.0.0',
-      }).validate();
+      await d.appPackageConfigFile([
+        d.packageConfigEntry(name: 'foo', version: '1.0.0'),
+        d.packageConfigEntry(name: 'bar', version: '4.0.0'),
+      ]).validate();
     });
   });
 }
diff --git a/test/validator/strict_dependencies_test.dart b/test/validator/strict_dependencies_test.dart
index a6b0833..18ee9c7 100644
--- a/test/validator/strict_dependencies_test.dart
+++ b/test/validator/strict_dependencies_test.dart
@@ -183,6 +183,14 @@
   group('should consider a package invalid if it', () {
     setUp(d.validPackage.create);
 
+    test('has an invalid String value', () async {
+      await d.file(path.join(appPath, 'lib', 'library.dart'), r'''
+        import 'package:$bad';
+      ''').create();
+
+      await expectValidation(strictDeps, errors: [matches('Invalid URL.')]);
+    });
+
     test('does not declare an "import" as a dependency', () async {
       await d.file(path.join(appPath, 'lib', 'library.dart'), r'''
         import 'package:silly_monkey/silly_monkey.dart';
diff --git a/tool/test.dart b/tool/test.dart
index 7d98156..3aabf88 100755
--- a/tool/test.dart
+++ b/tool/test.dart
@@ -17,6 +17,11 @@
 import 'package:pub/src/exceptions.dart';
 
 Future<void> main(List<String> args) async {
+  if (Platform.environment['FLUTTER_ROOT'] != null) {
+    print(
+      'WARNING: The tests will not run correctly with dart from a flutter checkout!',
+    );
+  }
   Process? testProcess;
   final sub = ProcessSignal.sigint.watch().listen((signal) {
     testProcess?.kill(signal);