create SDK package allowlist for core packages (#80318)

diff --git a/dev/bots/allowlist.dart b/dev/bots/allowlist.dart
new file mode 100644
index 0000000..64dfedf
--- /dev/null
+++ b/dev/bots/allowlist.dart
@@ -0,0 +1,76 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// The SDK package allowlist for the flutter, flutter_test, flutter_driver, flutter_localizations,
+/// and integration_test packages.
+///
+/// The goal of the allowlist is to make it more difficult to accidentally add new dependencies
+/// to the core SDK packages that users depend on. Any dependencies added to this set can have a
+/// large impact on the allowed version solving of a given flutter application because of how
+/// the SDK pins to an exact version.
+///
+/// Before adding a new Dart Team owned dependency to this set, please clear with natebosch@
+/// or jakemac53@. For other packages please contact hixie@ or jonahwilliams@ .
+const Set<String> kCorePackageAllowList = <String>{
+  'characters',
+  'clock',
+  'collection',
+  'fake_async',
+  'file',
+  'intl',
+  'meta',
+  'path',
+  'stack_trace',
+  'test',
+  'test_api',
+  'typed_data',
+  'vector_math',
+  'vm_service',
+  'webdriver',
+  '_fe_analyzer_shared',
+  'analyzer',
+  'archive',
+  'args',
+  'async',
+  'boolean_selector',
+  'charcode',
+  'cli_util',
+  'convert',
+  'coverage',
+  'crypto',
+  'glob',
+  'http_multi_server',
+  'http_parser',
+  'io',
+  'js',
+  'logging',
+  'matcher',
+  'mime',
+  'node_preamble',
+  'package_config',
+  'pedantic',
+  'pool',
+  'pub_semver',
+  'shelf',
+  'shelf_packages_handler',
+  'shelf_static',
+  'shelf_web_socket',
+  'source_map_stack_trace',
+  'source_maps',
+  'source_span',
+  'stream_channel',
+  'string_scanner',
+  'sync_http',
+  'term_glyph',
+  'test_core',
+  'watcher',
+  'web_socket_channel',
+  'webkit_inspection_protocol',
+  'yaml',
+  'flutter',
+  'flutter_driver',
+  'flutter_localizations',
+  'flutter_test',
+  'integration_test'
+};
diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart
index 7cf2571..b39c24d 100644
--- a/dev/bots/analyze.dart
+++ b/dev/bots/analyze.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:convert';
 import 'dart:core' hide print;
 import 'dart:io' hide exit;
@@ -11,6 +12,7 @@
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
+import 'allowlist.dart';
 import 'run_command.dart';
 import 'utils.dart';
 
@@ -76,6 +78,11 @@
     workingDirectory: flutterRoot,
   );
 
+  /// Ensure that no new dependencies have been accidentally
+  /// added to core packages.
+  print('$clock Package Allowlist...');
+  await _checkConsumerDependencies();
+
   // Analyze all the Dart code in the repo.
   print('$clock Dart analysis...');
   await _runFlutterAnalyze(flutterRoot, options: <String>[
@@ -1129,6 +1136,62 @@
   return result;
 }
 
+Future<void> _checkConsumerDependencies() async {
+  final ProcessResult result = await Process.run(flutter, <String>[
+    'update-packages',
+    '--transitive-closure',
+    '--consumer-only',
+  ]);
+  if (result.exitCode != 0) {
+    print(result.stdout);
+    print(result.stderr);
+    exit(result.exitCode);
+  }
+  final Set<String> dependencySet = <String>{};
+  for (final String line in result.stdout.toString().split('\n')) {
+    if (!line.contains('->')) {
+      continue;
+    }
+    final List<String> parts = line.split('->');
+    final String name = parts[0].trim();
+    dependencySet.add(name);
+  }
+  final List<String> dependencies = dependencySet.toList()
+    ..sort();
+  final List<String> disallowed = <String>[];
+  final StreamController<Digest> controller = StreamController<Digest>();
+  final ByteConversionSink hasher = sha256.startChunkedConversion(controller.sink);
+  for (final String dependency in dependencies) {
+    hasher.add(utf8.encode(dependency));
+    if (!kCorePackageAllowList.contains(dependency)) {
+      disallowed.add(dependency);
+    }
+  }
+  hasher.close();
+  final Digest digest = await controller.stream.last;
+  final String signature = base64.encode(digest.bytes);
+
+  // Do not change this signature without following the directions in
+  // dev/bots/allowlist.dart
+  const String kExpected = '3S20q38QbN+dDAp+jApXiTRaDgVGGBZ0t4bMJgD3AUY=';
+
+  if (disallowed.isNotEmpty) {
+    exitWithError(<String>[
+      'Warning: transitive closure contained non-allowlisted packages:',
+      '${disallowed..join(', ')}',
+      'See dev/bots/allowlist.dart for instructions on how to update the package allowlist.',
+    ]);
+  }
+
+  if (signature != kExpected) {
+    exitWithError(<String>[
+      'Warning: transitive closure sha256 does not match expected signature.',
+      'See dev/bots/allowlist.dart for instructions on how to update the package allowlist.',
+      '$signature != $kExpected',
+    ]);
+  }
+}
+
 Future<void> _runFlutterAnalyze(String workingDirectory, {
   List<String> options = const <String>[],
 }) async {