[url_launcher] Support new desktop implementation versions (#4779)

diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index 5eb5b4a..1634dcd 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.0.20
+
+* Fixes a typo in `default_package` registration for Windows, macOS, and Linux.
+
 ## 6.0.19
 
 * Updates README:
diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml
index 5d6548a..feb0a2c 100644
--- a/packages/url_launcher/url_launcher/pubspec.yaml
+++ b/packages/url_launcher/url_launcher/pubspec.yaml
@@ -3,7 +3,7 @@
   web, phone, SMS, and email schemes.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.0.19
+version: 6.0.20
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -17,24 +17,26 @@
       ios:
         default_package: url_launcher_ios
       linux:
-        default_package: url_laucher_linux
+        default_package: url_launcher_linux
       macos:
-        default_package: url_laucher_macos
+        default_package: url_launcher_macos
       web:
         default_package: url_launcher_web
       windows:
-        default_package: url_laucher_windows
+        default_package: url_launcher_windows
 
 dependencies:
   flutter:
     sdk: flutter
   url_launcher_android: ^6.0.13
   url_launcher_ios: ^6.0.13
-  url_launcher_linux: ^2.0.0
-  url_launcher_macos: ^2.0.0
+  # Allow either the pure-native or Dart/native hybrid versions of the desktop
+  # implementations, as both are compatible.
+  url_launcher_linux: ">=2.0.0 <4.0.0"
+  url_launcher_macos: ">=2.0.0 <4.0.0"
   url_launcher_platform_interface: ^2.0.3
   url_launcher_web: ^2.0.0
-  url_launcher_windows: ^2.0.0
+  url_launcher_windows: ">=2.0.0 <4.0.0"
 
 dev_dependencies:
   flutter_test:
diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md
index 101bfac..fbf7610 100644
--- a/script/tool/CHANGELOG.md
+++ b/script/tool/CHANGELOG.md
@@ -17,6 +17,7 @@
   for flake issues.
 - Adds support for `CHROME_EXECUTABLE` in `drive-examples` to match similar
   `flutter` behavior.
+- Validates `default_package` entries in plugins.
 
 ## 0.7.3
 
diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart
index 4f2b208..2c27c91 100644
--- a/script/tool/lib/src/pubspec_check_command.dart
+++ b/script/tool/lib/src/pubspec_check_command.dart
@@ -6,6 +6,7 @@
 import 'package:git/git.dart';
 import 'package:platform/platform.dart';
 import 'package:pubspec_parse/pubspec_parse.dart';
+import 'package:yaml/yaml.dart';
 
 import 'common/core.dart';
 import 'common/package_looping_command.dart';
@@ -100,9 +101,17 @@
     }
 
     if (isPlugin) {
-      final String? error = _checkForImplementsError(pubspec, package: package);
-      if (error != null) {
-        printError('$indentation$error');
+      final String? implementsError =
+          _checkForImplementsError(pubspec, package: package);
+      if (implementsError != null) {
+        printError('$indentation$implementsError');
+        passing = false;
+      }
+
+      final String? defaultPackageError =
+          _checkForDefaultPackageError(pubspec, package: package);
+      if (defaultPackageError != null) {
+        printError('$indentation$defaultPackageError');
         passing = false;
       }
     }
@@ -243,6 +252,49 @@
     return null;
   }
 
+  // Validates any "default_package" entries a plugin, returning an error
+  // string if there are any issues.
+  //
+  // Should only be called on plugin packages.
+  String? _checkForDefaultPackageError(
+    Pubspec pubspec, {
+    required RepositoryPackage package,
+  }) {
+    final dynamic platformsEntry = pubspec.flutter!['plugin']!['platforms'];
+    if (platformsEntry == null) {
+      logWarning('Does not implement any platforms');
+      return null;
+    }
+    final YamlMap platforms = platformsEntry as YamlMap;
+    final String packageName = package.directory.basename;
+
+    // Validate that the default_package entries look correct (e.g., no typos).
+    final Set<String> defaultPackages = <String>{};
+    for (final MapEntry<dynamic, dynamic> platformEntry in platforms.entries) {
+      final String? defaultPackage =
+          platformEntry.value['default_package'] as String?;
+      if (defaultPackage != null) {
+        defaultPackages.add(defaultPackage);
+        if (!defaultPackage.startsWith('${packageName}_')) {
+          return '"$defaultPackage" is not an expected implementation name '
+              'for "$packageName"';
+        }
+      }
+    }
+
+    // Validate that all default_packages are also dependencies.
+    final Iterable<String> dependencies = pubspec.dependencies.keys;
+    final Iterable<String> missingPackages = defaultPackages
+        .where((String package) => !dependencies.contains(package));
+    if (missingPackages.isNotEmpty) {
+      return 'The following default_packages are missing '
+              'corresponding dependencies:\n  ' +
+          missingPackages.join('\n  ');
+    }
+
+    return null;
+  }
+
   // Returns true if [packageName] appears to be an implementation package
   // according to repository conventions.
   bool _isImplementationPackage(RepositoryPackage package) {
diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart
index 9ad1eaa..42d2024 100644
--- a/script/tool/test/pubspec_check_command_test.dart
+++ b/script/tool/test/pubspec_check_command_test.dart
@@ -70,12 +70,27 @@
 String _flutterSection({
   bool isPlugin = false,
   String? implementedPackage,
+  Map<String, Map<String, String>> pluginPlatformDetails =
+      const <String, Map<String, String>>{},
 }) {
-  final String pluginEntry = '''
+  String pluginEntry = '''
   plugin:
 ${implementedPackage == null ? '' : '    implements: $implementedPackage'}
     platforms:
 ''';
+
+  for (final MapEntry<String, Map<String, String>> platform
+      in pluginPlatformDetails.entries) {
+    pluginEntry += '''
+      ${platform.key}:
+''';
+    for (final MapEntry<String, String> detail in platform.value.entries) {
+      pluginEntry += '''
+        ${detail.key}: ${detail.value}
+''';
+    }
+  }
+
   return '''
 flutter:
 ${isPlugin ? pluginEntry : ''}
@@ -647,6 +662,82 @@
       );
     });
 
+    test('fails when a "default_package" looks incorrect', () async {
+      final Directory pluginDirectory =
+          createFakePlugin('plugin_a', packagesDir.childDirectory('plugin_a'));
+
+      pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
+${_headerSection(
+        'plugin_a',
+        isPlugin: true,
+        repositoryPackagesDirRelativePath: 'plugin_a/plugin_a',
+      )}
+${_environmentSection()}
+${_flutterSection(
+        isPlugin: true,
+        pluginPlatformDetails: <String, Map<String, String>>{
+          'android': <String, String>{'default_package': 'plugin_b_android'}
+        },
+      )}
+${_dependenciesSection()}
+${_devDependenciesSection()}
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['pubspec-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains(
+              '"plugin_b_android" is not an expected implementation name for "plugin_a"'),
+        ]),
+      );
+    });
+
+    test(
+        'fails when a "default_package" does not have a corresponding dependency',
+        () async {
+      final Directory pluginDirectory =
+          createFakePlugin('plugin_a', packagesDir.childDirectory('plugin_a'));
+
+      pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
+${_headerSection(
+        'plugin_a',
+        isPlugin: true,
+        repositoryPackagesDirRelativePath: 'plugin_a/plugin_a',
+      )}
+${_environmentSection()}
+${_flutterSection(
+        isPlugin: true,
+        pluginPlatformDetails: <String, Map<String, String>>{
+          'android': <String, String>{'default_package': 'plugin_a_android'}
+        },
+      )}
+${_dependenciesSection()}
+${_devDependenciesSection()}
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['pubspec-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('The following default_packages are missing corresponding '
+              'dependencies:\n  plugin_a_android'),
+        ]),
+      );
+    });
+
     test('passes for an app-facing package without "implements"', () async {
       final Directory pluginDirectory =
           createFakePlugin('plugin_a', packagesDir.childDirectory('plugin_a'));