Adds `minVersion` to `findPackageConfig{,Uri}` methods. (#125)

* Adds `minVersion` to `findPackageConfig{,Uri}` methods.

This parameter currently allows you to ignore `.packages` files
while searching for a configuration file.
If we later add a version 3 of the configuration, it should
also allow ignoring version 2 configurations.

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ecfe22..b6e91bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,11 @@
-## 2.0.3-dev
+## 2.1.0
+
+- Adds `minVersion` to `findPackageConfig` and `findPackageConfigVersion`
+  which allows ignoring earlier versions (which currently only means
+  ignoring version 1, aka. `.packages` files.)
+
+- Changes the version number of `SimplePackageConfig.empty` to the
+  current maximum version.
 
 - Improve file read performance; improve lookup performance.
 - Emit an error when a package is inside the package root of another package.
diff --git a/lib/package_config.dart b/lib/package_config.dart
index bd227e4..a2c0321 100644
--- a/lib/package_config.dart
+++ b/lib/package_config.dart
@@ -111,10 +111,21 @@
 /// a valid configuration from the invalid configuration file.
 /// If no [onError] is provided, errors are thrown immediately.
 ///
+/// If [minVersion] is set to something greater than its default,
+/// any lower-version configuration files are ignored in the search.
+///
 /// Returns `null` if no configuration file is found.
 Future<PackageConfig?> findPackageConfig(Directory directory,
-        {bool recurse = true, void Function(Object error)? onError}) =>
-    discover.findPackageConfig(directory, recurse, onError ?? throwError);
+    {bool recurse = true,
+    void Function(Object error)? onError,
+    int minVersion = 1}) {
+  if (minVersion > PackageConfig.maxVersion) {
+    throw ArgumentError.value(minVersion, 'minVersion',
+        'Maximum known version is ${PackageConfig.maxVersion}');
+  }
+  return discover.findPackageConfig(
+      directory, minVersion, recurse, onError ?? throwError);
+}
 
 /// Finds a package configuration relative to [location].
 ///
@@ -155,13 +166,22 @@
 /// a valid configuration from the invalid configuration file.
 /// If no [onError] is provided, errors are thrown immediately.
 ///
+/// If [minVersion] is set to something greater than its default,
+/// any lower-version configuration files are ignored in the search.
+///
 /// Returns `null` if no configuration file is found.
 Future<PackageConfig?> findPackageConfigUri(Uri location,
-        {bool recurse = true,
-        Future<Uint8List?> Function(Uri uri)? loader,
-        void Function(Object error)? onError}) =>
-    discover.findPackageConfigUri(
-        location, loader, onError ?? throwError, recurse);
+    {bool recurse = true,
+    int minVersion = 1,
+    Future<Uint8List?> Function(Uri uri)? loader,
+    void Function(Object error)? onError}) {
+  if (minVersion > PackageConfig.maxVersion) {
+    throw ArgumentError.value(minVersion, 'minVersion',
+        'Maximum known version is ${PackageConfig.maxVersion}');
+  }
+  return discover.findPackageConfigUri(
+      location, minVersion, loader, onError ?? throwError, recurse);
+}
 
 /// Writes a package configuration to the provided directory.
 ///
diff --git a/lib/src/discovery.dart b/lib/src/discovery.dart
index a6cc451..ccc86ea 100644
--- a/lib/src/discovery.dart
+++ b/lib/src/discovery.dart
@@ -25,15 +25,20 @@
 /// and stopping when something is found.
 ///
 /// * Check if a `.dart_tool/package_config.json` file exists in the directory.
-/// * Check if a `.packages` file exists in the directory.
+/// * Check if a `.packages` file exists in the directory
+///   (if `minVersion <= 1`).
 /// * Repeat these checks for the parent directories until reaching the
 ///   root directory if [recursive] is true.
 ///
 /// If any of these tests succeed, a `PackageConfig` class is returned.
 /// Returns `null` if no configuration was found. If a configuration
 /// is needed, then the caller can supply [PackageConfig.empty].
+///
+/// If [minVersion] is greated than 1, `.packages` files are ignored.
+/// If [minVersion] is greater than the version read from the
+/// `package_config.json` file, it too is ignored.
 Future<PackageConfig?> findPackageConfig(Directory baseDirectory,
-    bool recursive, void Function(Object error) onError) async {
+    int minVersion, bool recursive, void Function(Object error) onError) async {
   var directory = baseDirectory;
   if (!directory.isAbsolute) directory = directory.absolute;
   if (!await directory.exists()) {
@@ -41,7 +46,8 @@
   }
   do {
     // Check for $cwd/.packages
-    var packageConfig = await findPackagConfigInDirectory(directory, onError);
+    var packageConfig =
+        await findPackagConfigInDirectory(directory, minVersion, onError);
     if (packageConfig != null) return packageConfig;
     if (!recursive) break;
     // Check in parent directories.
@@ -55,6 +61,7 @@
 /// Similar to [findPackageConfig] but based on a URI.
 Future<PackageConfig?> findPackageConfigUri(
     Uri location,
+    int minVersion,
     Future<Uint8List?> Function(Uri uri)? loader,
     void Function(Object error) onError,
     bool recursive) async {
@@ -67,6 +74,7 @@
     if (location.isScheme('file')) {
       return findPackageConfig(
           Directory.fromUri(location.resolveUri(currentPath)),
+          minVersion,
           recursive,
           onError);
     }
@@ -77,12 +85,15 @@
     var file = location.resolveUri(packageConfigJsonPath);
     var bytes = await loader(file);
     if (bytes != null) {
-      return parsePackageConfigBytes(bytes, file, onError);
+      var config = parsePackageConfigBytes(bytes, file, onError);
+      if (config.version >= minVersion) return config;
     }
-    file = location.resolveUri(dotPackagesPath);
-    bytes = await loader(file);
-    if (bytes != null) {
-      return packages_file.parse(bytes, file, onError);
+    if (minVersion <= 1) {
+      file = location.resolveUri(dotPackagesPath);
+      bytes = await loader(file);
+      if (bytes != null) {
+        return packages_file.parse(bytes, file, onError);
+      }
     }
     if (!recursive) break;
     var parent = location.resolveUri(parentPath);
@@ -102,15 +113,23 @@
 /// If [onError] is supplied, parsing errors are reported using that, and
 /// a best-effort attempt is made to return a package configuration.
 /// This may be the empty package configuration.
-Future<PackageConfig?> findPackagConfigInDirectory(
-    Directory directory, void Function(Object error) onError) async {
+///
+/// If [minVersion] is greated than 1, `.packages` files are ignored.
+/// If [minVersion] is greater than the version read from the
+/// `package_config.json` file, it too is ignored.
+Future<PackageConfig?> findPackagConfigInDirectory(Directory directory,
+    int minVersion, void Function(Object error) onError) async {
   var packageConfigFile = await checkForPackageConfigJsonFile(directory);
   if (packageConfigFile != null) {
-    return await readPackageConfigJsonFile(packageConfigFile, onError);
+    var config = await readPackageConfigJsonFile(packageConfigFile, onError);
+    if (config.version < minVersion) return null;
+    return config;
   }
-  packageConfigFile = await checkForDotPackagesFile(directory);
-  if (packageConfigFile != null) {
-    return await readDotPackagesFile(packageConfigFile, onError);
+  if (minVersion <= 1) {
+    packageConfigFile = await checkForDotPackagesFile(directory);
+    if (packageConfigFile != null) {
+      return await readDotPackagesFile(packageConfigFile, onError);
+    }
   }
   return null;
 }
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index 3c5cc51..ba52c14 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -39,7 +39,7 @@
   /// including the two paths being the same.
   ///
   /// * No package's root must be the same as another package's root.
-  /// * The package-root of a package must be inside the pacakge's root.
+  /// * The package-root of a package must be inside the package's root.
   /// * If one package's package-root is inside another package's root,
   ///   then the latter package's package root must not be inside the former
   ///   package's root. (No getting between a package and its package root!)
diff --git a/lib/src/package_config_impl.dart b/lib/src/package_config_impl.dart
index aef8176..4c8f234 100644
--- a/lib/src/package_config_impl.dart
+++ b/lib/src/package_config_impl.dart
@@ -37,8 +37,11 @@
   ///
   /// The empty configuration can be used in cases where no configuration is
   /// found, but code expects a non-null configuration.
+  ///
+  /// The version number is [PackageConfig.maxVersion] to avoid
+  /// minimum-version filters discarding the configuration.
   const SimplePackageConfig.empty()
-      : version = 1,
+      : version = PackageConfig.maxVersion,
         _packageTree = const EmptyPackageTree(),
         _packages = const <String, Package>{},
         extraData = null;
diff --git a/pubspec.yaml b/pubspec.yaml
index d4ed6b9..56e30b5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: package_config
-version: 2.0.3-dev
+version: 2.1.0
 description: Support for reading and writing Dart Package Configuration files.
 repository: https://github.com/dart-lang/package_config
 
diff --git a/test/discovery_test.dart b/test/discovery_test.dart
index 4a1bba0..17b6aa0 100644
--- a/test/discovery_test.dart
+++ b/test/discovery_test.dart
@@ -201,6 +201,32 @@
         expect(hadError, true);
       });
     });
+
+    // Does not find .packages if no package_config.json and minVersion > 1.
+    fileTest('.packages ignored', {
+      '.packages': packagesFile,
+      'script.dart': 'main(){}'
+    }, (Directory directory) async {
+      var config = (await findPackageConfig(directory, minVersion: 2));
+      expect(config, null);
+    });
+
+    // Finds package_config.json in super-directory, with .packages in
+    // subdir and minVersion > 1.
+    fileTest('package_config.json recursive .packages ignored', {
+      '.dart_tool': {
+        'package_config.json': packageConfigFile,
+      },
+      'subdir': {
+        '.packages': packagesFile,
+        'script.dart': 'main(){}',
+      }
+    }, (Directory directory) async {
+      var config = (await findPackageConfig(subdir(directory, 'subdir/'),
+          minVersion: 2))!;
+      expect(config.version, 2);
+      validatePackagesFile(config, directory);
+    });
   });
 
   group('loadPackageConfig', () {
diff --git a/test/discovery_uri_test.dart b/test/discovery_uri_test.dart
index e487e47..6183ce3 100644
--- a/test/discovery_uri_test.dart
+++ b/test/discovery_uri_test.dart
@@ -145,6 +145,33 @@
       expect(() => findPackageConfigUri(directory, loader: loader),
           throwsA(TypeMatcher<FormatException>()));
     });
+
+    // Does not find .packages if no package_config.json and minVersion > 1.
+    loaderTest('.packages ignored', {
+      '.packages': packagesFile,
+      'script.dart': 'main(){}'
+    }, (directory, loader) async {
+      var config = (await findPackageConfigUri(directory,
+          minVersion: 2, loader: loader));
+      expect(config, null);
+    });
+
+    // Finds package_config.json in super-directory, with .packages in
+    // subdir and minVersion > 1.
+    loaderTest('package_config.json recursive ignores .packages', {
+      '.dart_tool': {
+        'package_config.json': packageConfigFile,
+      },
+      'subdir': {
+        '.packages': packagesFile,
+        'script.dart': 'main(){}',
+      }
+    }, (directory, loader) async {
+      var config = (await findPackageConfigUri(directory.resolve('subdir/'),
+          minVersion: 2, loader: loader))!;
+      expect(config.version, 2);
+      validatePackagesFile(config, directory);
+    });
   });
 
   group('loadPackageConfig', () {