diff --git a/packages/aft/lib/src/commands/constraints_command.dart b/packages/aft/lib/src/commands/constraints_command.dart index ea7eba676c..f2d43afdab 100644 --- a/packages/aft/lib/src/commands/constraints_command.dart +++ b/packages/aft/lib/src/commands/constraints_command.dart @@ -5,13 +5,13 @@ import 'dart:convert'; import 'dart:io'; import 'package:aft/aft.dart'; +import 'package:aft/src/constraints_checker.dart'; import 'package:aft/src/options/glob_options.dart'; import 'package:collection/collection.dart'; import 'package:pub_api_client/pub_api_client.dart'; import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_parse/pubspec_parse.dart'; -enum _ConstraintsAction { +enum ConstraintsAction { check( 'Checks whether all constraints in the repo match the global config', 'All constraints matched!', @@ -26,7 +26,7 @@ enum _ConstraintsAction { 'Constraints successfully applied!', ); - const _ConstraintsAction(this.description, this.successMessage); + const ConstraintsAction(this.description, this.successMessage); final String description; final String successMessage; @@ -35,8 +35,8 @@ enum _ConstraintsAction { /// Command to manage dependencies across all Dart/Flutter packages in the repo. class ConstraintsCommand extends AmplifyCommand { ConstraintsCommand() { - addSubcommand(_ConstraintsSubcommand(_ConstraintsAction.check)); - addSubcommand(_ConstraintsSubcommand(_ConstraintsAction.apply)); + addSubcommand(_ConstraintsSubcommand(ConstraintsAction.check)); + addSubcommand(_ConstraintsSubcommand(ConstraintsAction.apply)); addSubcommand(_ConstraintsUpdateCommand()); addSubcommand(_ConstraintsPubVerifyCommand()); } @@ -52,7 +52,7 @@ class ConstraintsCommand extends AmplifyCommand { class _ConstraintsSubcommand extends AmplifyCommand with GlobOptions { _ConstraintsSubcommand(this.action); - final _ConstraintsAction action; + final ConstraintsAction action; @override String get description => action.description; @@ -60,121 +60,21 @@ class _ConstraintsSubcommand extends AmplifyCommand with GlobOptions { @override String get name => action.name; - final _mismatchedDependencies = []; - - /// Checks the [local] constraint against the [global] and returns whether - /// an update is required. - void _checkConstraint( - PackageInfo package, - List dependencyPath, - VersionConstraint global, - VersionConstraint local, - ) { - // Packages are not allowed to diverge from `aft.yaml`, even to specify - // more precise constraints. - final satisfiesGlobalConstraint = global == local; - if (satisfiesGlobalConstraint) { - return; - } - switch (action) { - case _ConstraintsAction.check: - final dependencyName = dependencyPath.last; - _mismatchedDependencies.add( - '${package.path}\n' - 'Mismatched `$dependencyName`:\n' - 'Expected $global\n' - 'Found $local\n', - ); - return; - case _ConstraintsAction.apply: - case _ConstraintsAction.update: - package.pubspecInfo.pubspecYamlEditor.update( - dependencyPath, - global.toString(), - ); - } - } - - /// Checks the package's environment constraints against the global config. - void _checkEnvironment( - PackageInfo package, - Map environment, - Environment globalEnvironment, - ) { - // Check Dart SDK contraint - final globalSdkConstraint = globalEnvironment.sdk; - final localSdkConstraint = environment['sdk'] ?? VersionConstraint.any; - _checkConstraint( - package, - ['environment', 'sdk'], - globalSdkConstraint, - localSdkConstraint, - ); - - // Check Flutter SDK constraint - if (package.flavor == PackageFlavor.flutter) { - final globalFlutterConstraint = globalEnvironment.flutter; - final localFlutterConstraint = - environment['flutter'] ?? VersionConstraint.any; - _checkConstraint( - package, - ['environment', 'flutter'], - globalFlutterConstraint, - localFlutterConstraint, - ); - } - } - - /// Checks the package's dependency constraints against the global config. - void _checkDependency( - PackageInfo package, - Map dependencies, - DependencyType dependencyType, - MapEntry globalDep, - ) { - final dependencyName = globalDep.key; - final globalDepConstraint = globalDep.value; - final localDep = dependencies[dependencyName]; - if (localDep is! HostedDependency) { - return; - } - final localDepConstraint = localDep.version; - _checkConstraint( - package, - [dependencyType.key, dependencyName], - globalDepConstraint, - localDepConstraint, - ); - } - - Future _run(_ConstraintsAction action) async { - final globalDependencyConfig = aftConfig.dependencies; - final globalEnvironmentConfig = aftConfig.environment; + Future _run(ConstraintsAction action) async { + final constraintsCheckers = [ + GlobalConstraintChecker( + action, + repo.aftConfig.dependencies.asMap(), + repo.aftConfig.environment, + ), + PublishConstraintsChecker( + action, + repo.getPackageGraph(includeDevDependencies: true), + ), + ]; for (final package in commandPackages.values) { - _checkEnvironment( - package, - package.pubspecInfo.pubspec.environment ?? const {}, - globalEnvironmentConfig, - ); - for (final globalDep in globalDependencyConfig.entries) { - _checkDependency( - package, - package.pubspecInfo.pubspec.dependencies, - DependencyType.dependency, - globalDep, - ); - _checkDependency( - package, - package.pubspecInfo.pubspec.dependencyOverrides, - DependencyType.dependencyOverride, - globalDep, - ); - _checkDependency( - package, - package.pubspecInfo.pubspec.devDependencies, - DependencyType.devDependency, - globalDep, - ); + for (final constraintsChecker in constraintsCheckers) { + constraintsChecker.checkConstraints(package); } if (package.pubspecInfo.pubspecYamlEditor.edits.isNotEmpty) { @@ -183,9 +83,17 @@ class _ConstraintsSubcommand extends AmplifyCommand with GlobOptions { ); } } - if (_mismatchedDependencies.isNotEmpty) { - for (final mismatched in _mismatchedDependencies) { - logger.error(mismatched); + final mismatchedDependencies = constraintsCheckers.expand( + (checker) => checker.mismatchedDependencies, + ); + if (mismatchedDependencies.isNotEmpty) { + for (final mismatched in mismatchedDependencies) { + final (:package, :dependencyName, :message) = mismatched; + logger.error( + '${package.path}\n' + 'Mismatched `$dependencyName`:\n' + '$message\n', + ); } exit(1); } @@ -200,7 +108,7 @@ class _ConstraintsSubcommand extends AmplifyCommand with GlobOptions { } class _ConstraintsUpdateCommand extends _ConstraintsSubcommand { - _ConstraintsUpdateCommand() : super(_ConstraintsAction.update); + _ConstraintsUpdateCommand() : super(ConstraintsAction.update); @override Future run() async { @@ -336,7 +244,7 @@ class _ConstraintsUpdateCommand extends _ConstraintsSubcommand { } if (hasUpdates) { - await _run(_ConstraintsAction.apply); + await _run(ConstraintsAction.apply); } } } diff --git a/packages/aft/lib/src/commands/publish_command.dart b/packages/aft/lib/src/commands/publish_command.dart index e20c8862e3..51c2cb7ca8 100644 --- a/packages/aft/lib/src/commands/publish_command.dart +++ b/packages/aft/lib/src/commands/publish_command.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:aft/aft.dart'; +import 'package:aft/src/constraints_checker.dart'; import 'package:aft/src/options/glob_options.dart'; import 'package:aws_common/aws_common.dart'; import 'package:collection/collection.dart'; @@ -27,6 +28,26 @@ mixin PublishHelpers on AmplifyCommand { .whereType() .toList(); + final constraintsChecker = PublishConstraintsChecker( + dryRun ? ConstraintsAction.update : ConstraintsAction.check, + repo.getPackageGraph(includeDevDependencies: true), + ); + for (final package in unpublishedPackages) { + constraintsChecker.checkConstraints(package); + } + final mismatchedDependencies = constraintsChecker.mismatchedDependencies; + if (mismatchedDependencies.isNotEmpty) { + for (final mismatched in mismatchedDependencies) { + final (:package, :dependencyName, :message) = mismatched; + logger.error( + '${package.path}\n' + 'Mismatched `$dependencyName`:\n' + '$message\n', + ); + } + exit(1); + } + try { sortPackagesTopologically( unpublishedPackages, diff --git a/packages/aft/lib/src/config/config.dart b/packages/aft/lib/src/config/config.dart index 59b8e43df8..43dea9fecb 100644 --- a/packages/aft/lib/src/config/config.dart +++ b/packages/aft/lib/src/config/config.dart @@ -291,6 +291,21 @@ class PackageInfo return found; } + /// The type of dependency [package] is in `this`, or `null` if [package] + /// is not listed in this package's pubspec. + DependencyType? dependencyType(PackageInfo package) { + if (pubspecInfo.pubspec.dependencies.containsKey(package.name)) { + return DependencyType.dependency; + } + if (pubspecInfo.pubspec.devDependencies.containsKey(package.name)) { + return DependencyType.devDependency; + } + if (pubspecInfo.pubspec.dependencyOverrides.containsKey(package.name)) { + return DependencyType.dependencyOverride; + } + return null; + } + /// The parsed `CHANGELOG.md`. Changelog get changelog { final changelogMd = File(p.join(path, 'CHANGELOG.md')).readAsStringSync(); diff --git a/packages/aft/lib/src/constraints_checker.dart b/packages/aft/lib/src/constraints_checker.dart new file mode 100644 index 0000000000..a7b354b7b3 --- /dev/null +++ b/packages/aft/lib/src/constraints_checker.dart @@ -0,0 +1,367 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:aft/aft.dart'; +import 'package:aws_common/aws_common.dart'; +import 'package:collection/collection.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:pubspec_parse/pubspec_parse.dart'; + +typedef MismatchedDependency = ({ + PackageInfo package, + String dependencyName, + String message, +}); + +sealed class ConstraintsChecker { + ConstraintsChecker(this.action); + + /// The constraints action being performed. + final ConstraintsAction action; + + /// Checks the constraints of [package] against the rules for this + /// checker. + /// + /// Returns `true` if all constraint checks pass and `false` otherwise. + bool checkConstraints(PackageInfo package); + + /// Populated by [checkConstraints] if a constraint check fails. + List get mismatchedDependencies; + + /// Processes the [expectedConstraint] for [dependencyPath] in [package]. + /// + /// If [action] is `apply` or `update`, the constraint is updated when there's + /// a mismatch. Otherwise, if it is `check`, the mismatched constraint is recorded + /// as an error in [mismatchedDependencies]. + bool _processConstraint({ + required PackageInfo package, + required List dependencyPath, + required VersionConstraint expectedConstraint, + required String errorMessage, + }) { + switch (action) { + case ConstraintsAction.check: + mismatchedDependencies.add( + ( + package: package, + dependencyName: dependencyPath.last, + message: errorMessage, + ), + ); + case ConstraintsAction.apply: + case ConstraintsAction.update: + package.pubspecInfo.pubspecYamlEditor.update( + dependencyPath, + expectedConstraint.toString(), + ); + } + return false; + } +} + +final class GlobalConstraintChecker extends ConstraintsChecker { + GlobalConstraintChecker( + super.action, + this.globalConstraints, + this.globalEnvironment, + ); + + final Map globalConstraints; + final Environment globalEnvironment; + + @override + final List mismatchedDependencies = []; + + /// Checks the package's dependency constraints against the global config. + bool _checkDependency( + PackageInfo package, + String dependencyName, + Dependency dependency, + DependencyType dependencyType, + ) { + final globalConstraint = globalConstraints[dependencyName]; + if (globalConstraint == null) { + return true; + } + // Packages are not allowed to diverge from `aft.yaml`, even to specify + // more precise constraints. + final (currentConstraint, satisfiesGlobalConstraint) = switch (dependency) { + HostedDependency(:final version) => ( + version, + version == globalConstraint, + ), + _ => (null, false), + }; + if (satisfiesGlobalConstraint) { + return true; + } + return _processConstraint( + package: package, + dependencyPath: [dependencyType.key, dependencyName], + expectedConstraint: globalConstraint, + errorMessage: 'Expected $globalConstraint\n' + 'Found $currentConstraint', + ); + } + + /// Checks the package's environment constraints against the global config. + bool _checkEnvironment(PackageInfo package) { + final environment = package.pubspecInfo.pubspec.environment ?? const {}; + + // Check Dart SDK contraint + final globalSdkConstraint = globalEnvironment.sdk; + final localSdkConstraint = environment['sdk']; + final satisfiesSdkConstraint = globalSdkConstraint == localSdkConstraint; + if (!satisfiesSdkConstraint) { + _processConstraint( + package: package, + dependencyPath: ['environment', 'sdk'], + expectedConstraint: globalSdkConstraint, + errorMessage: 'Expected $globalSdkConstraint\n' + 'Found $localSdkConstraint', + ); + } + + // Check Flutter SDK constraint + var satisfiesFlutterConstraint = true; + if (package.flavor == PackageFlavor.flutter) { + final globalFlutterConstraint = globalEnvironment.flutter; + final localFlutterConstraint = environment['flutter']; + satisfiesFlutterConstraint = + globalFlutterConstraint == localFlutterConstraint; + if (!satisfiesFlutterConstraint) { + _processConstraint( + package: package, + dependencyPath: ['environment', 'flutter'], + expectedConstraint: globalFlutterConstraint, + errorMessage: 'Expected $globalFlutterConstraint\n' + 'Found $localFlutterConstraint', + ); + } + } + + return satisfiesSdkConstraint && satisfiesFlutterConstraint; + } + + /// Runs [action] for each dependency in [package]. + bool _forEachDependency( + PackageInfo package, + bool Function( + PackageInfo package, + String dependencyName, + Dependency dependency, + DependencyType dependencyType, + ) action, + ) { + var result = true; + for (final (dependencies, dependencyType) in [ + ( + package.pubspecInfo.pubspec.dependencies, + DependencyType.dependency, + ), + ( + package.pubspecInfo.pubspec.devDependencies, + DependencyType.devDependency, + ), + ( + package.pubspecInfo.pubspec.dependencyOverrides, + DependencyType.dependencyOverride, + ), + ]) { + for (final MapEntry(key: dependencyName, value: dependency) + in dependencies.entries) { + result = result && + action( + package, + dependencyName, + dependency, + dependencyType, + ); + } + } + return result; + } + + @override + bool checkConstraints(PackageInfo package) { + final satisfiesEnvironmentConstraints = _checkEnvironment(package); + final satisfiesDependencyConstraints = + _forEachDependency(package, _checkDependency); + return satisfiesEnvironmentConstraints && satisfiesDependencyConstraints; + } +} + +final class PublishConstraintsChecker extends ConstraintsChecker { + PublishConstraintsChecker(super.action, this.repoGraph); + + final Map> repoGraph; + + @override + final List mismatchedDependencies = []; + + /// Returns the intersection of all [constraints]. + VersionConstraint _intersection(Iterable constraints) { + var constraint = VersionConstraint.any; + for (final other in constraints) { + constraint = constraint.intersect(other); + } + return constraint; + } + + @override + bool checkConstraints(PackageInfo package) { + if (!package.isPublishable) { + return true; + } + final allConstraints = _DependencyConstraintMap(); + final rootPackage = package; + dfs( + repoGraph, + root: rootPackage, + (package) { + final dependencies = { + ...package.pubspecInfo.pubspec.dependencies.map( + (key, value) => MapEntry( + key, + (DependencyType.dependency, value), + ), + ), + ...package.pubspecInfo.pubspec.devDependencies.map( + (key, value) => MapEntry( + key, + (DependencyType.devDependency, value), + ), + ), + }; + for (final MapEntry( + key: dependencyName, + value: (dependencyType, dependency) + ) in dependencies.entries) { + final repoDependency = repoGraph.keys.singleWhereOrNull( + (pkg) => pkg.name == dependencyName, + ); + if (repoDependency == null) { + continue; + } + switch (dependency) { + case HostedDependency(version: final constraint): + allConstraints.recordConstraint( + repoDependency: repoDependency, + inPackage: package, + constraint: constraint, + ); + + // Do not verify the constraints in the `dev_dependencies` + // block since this only affects the `pub` algorithm transitively. + case _ when dependencyType == DependencyType.devDependency: + return; + + // Do not verify the constraint of non-publishable packages listed + // in the `dependencies` block of other non-publishable packages. + // We only care about publishable dependencies listed in the `dependencies` + // block of non-publishable packages. + case _ when !repoDependency.isPublishable: + if (package.isPublishable) { + throw AssertionError( + 'Non-publishable package ($dependencyName) found in ' + 'the `dependencies` block of ${package.name}.', + ); + } + return; + + // Otherwise, we have a constraint which might cause an error. This + // is most often caused by path dependency on a publishable package + // in a test package's `dependencies` block. + case _: + _processConstraint( + package: package, + dependencyPath: [dependencyType.key, dependencyName], + expectedConstraint: package.isPublishable + ? repoDependency.currentConstraint + : VersionConstraint.any, + errorMessage: + 'Invalid constraint type: ${dependency.runtimeType}. ' + 'A hosted dependency is required when listing any publishable ' + 'package in the `dependencies` block.', + ); + } + } + }, + ); + for (final MapEntry(key: repoDependency, value: constraints) + in allConstraints.entries) { + final intersection = _intersection(constraints.values); + if (intersection.isEmpty) { + for (final MapEntry(key: package, value: constraint) + in constraints.entries) { + // A package will cause an empty intersection if it lists anything + // other than `any` or a valid constraint range for the current + // version. + if (constraint.allows(repoDependency.version)) { + continue; + } + _processConstraint( + package: package, + dependencyPath: [ + package.dependencyType(repoDependency)!.key, + repoDependency.name + ], + expectedConstraint: package.isPublishable + ? repoDependency.currentConstraint + : VersionConstraint.any, + errorMessage: + 'Constraint for dependency causes an empty intersection ' + 'for ${rootPackage.name}: $constraint', + ); + } + } + } + return mismatchedDependencies.isEmpty; + } +} + +final class _DependencyConstraintMap + extends DelegatingMap + with AWSDebuggable, AWSSerializable> { + _DependencyConstraintMap() : super({}); + + /// Records the [constraint] for [repoDependency] found in [inPackage]. + void recordConstraint({ + required PackageInfo repoDependency, + required PackageInfo inPackage, + required VersionConstraint constraint, + }) { + (this[repoDependency] ??= _ConstraintMap())[inPackage] = constraint; + } + + @override + String get runtimeTypeName => 'DependencyConstraintMap'; + + @override + Map toJson() => map((repoDependency, constraints) { + return MapEntry(repoDependency.name, constraints.toJson()); + }); +} + +final class _ConstraintMap extends DelegatingMap + with AWSDebuggable, AWSSerializable> { + _ConstraintMap() : super({}); + + @override + String get runtimeTypeName => 'ConstraintMap'; + + @override + Map toJson() => map((package, constraint) { + return MapEntry(package.name, constraint.toString()); + }); +} + +extension on PackageInfo { + /// The current constraint for `this` to use in publishable packages' + /// `dependencies` block. + VersionRange get currentConstraint => VersionRange( + min: Version(version.major, version.minor, 0), + includeMin: true, + max: version.nextMinor, + ); +} diff --git a/packages/aft/pubspec.yaml b/packages/aft/pubspec.yaml index d8c7033762..f3086fb1ee 100644 --- a/packages/aft/pubspec.yaml +++ b/packages/aft/pubspec.yaml @@ -17,10 +17,7 @@ dependencies: code_builder: 4.5.0 collection: ^1.16.0 file: ">=6.0.0 <8.0.0" - git: - git: - url: https://github.com/dnys1/git - ref: feat/existing-worktree + git: any # override glob: ^2.1.0 graphs: ^2.1.0 json_annotation: ">=4.8.1 <4.9.0" @@ -52,6 +49,12 @@ dependency_overrides: path: ../aws_common aws_signature_v4: path: ../aws_signature_v4 + git: + git: + url: https://github.com/dnys1/git + ref: feat/existing-worktree + pub_server: + path: ../test/pub_server smithy: path: ../smithy/smithy smithy_aws: diff --git a/packages/aft/test/constraints_checker_test.dart b/packages/aft/test/constraints_checker_test.dart new file mode 100644 index 0000000000..9b8f612e9f --- /dev/null +++ b/packages/aft/test/constraints_checker_test.dart @@ -0,0 +1,180 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:aft/aft.dart'; +import 'package:aft/src/constraints_checker.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:pubspec_parse/pubspec_parse.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +void main() { + group('PublishConstraintsChecker', () { + for (final action in ConstraintsAction.values) { + final result = switch (action) { + ConstraintsAction.check => 'fails', + ConstraintsAction.apply => 'applies', + ConstraintsAction.update => 'updates', + }; + test( + '$result when a direct dep and transitive dev dep conflict ' + 'for a published package', + () { + final amplifyCore = dummyPackage( + 'amplify_core', + version: Version(1, 0, 0), + ); + final amplifyTest = dummyPackage( + 'amplify_test', + publishable: false, + deps: { + // An outdated constraint + amplifyCore.key: VersionConstraint.parse('<1.0.0'), + }, + ); + final amplifyFlutter = dummyPackage( + 'amplify_flutter', + version: Version(1, 0, 0), + deps: { + amplifyCore.key: VersionConstraint.parse('>=1.0.0 <1.1.0'), + }, + devDeps: { + amplifyTest.key: VersionConstraint.any, + }, + ); + final repoGraph = Map.fromEntries([ + amplifyCore, + amplifyFlutter, + amplifyTest, + ]); + final constraintsChecker = PublishConstraintsChecker( + action, + repoGraph, + ); + + { + expect( + constraintsChecker.checkConstraints(amplifyCore.key), + isTrue, + ); + } + + { + expect( + constraintsChecker.checkConstraints(amplifyTest.key), + isTrue, + reason: + "amplify_test's constraint on amplify_core is fine by itself", + ); + } + + { + switch (action) { + case ConstraintsAction.apply || ConstraintsAction.update: + expect( + constraintsChecker.checkConstraints(amplifyFlutter.key), + isTrue, + ); + expect( + amplifyTest.key.pubspecInfo.pubspecYamlEditor.edits.single, + isA().having( + (edit) => edit.replacement, + 'replacement', + 'any', + ), + ); + expect(constraintsChecker.mismatchedDependencies, isEmpty); + case ConstraintsAction.check: + expect( + constraintsChecker.checkConstraints(amplifyFlutter.key), + isFalse, + reason: + 'The constraint amplify_test has on amplify_core would ' + "cause a publish error since it conflicts with amplify_flutter's " + 'direct constraint', + ); + expect( + constraintsChecker.mismatchedDependencies.single, + isA() + .having( + (err) => err.package.name, + 'packageName', + 'amplify_test', + ) + .having( + (err) => err.dependencyName, + 'dependencyName', + 'amplify_core', + ), + ); + expect( + amplifyTest.key.pubspecInfo.pubspecYamlEditor.edits, + isEmpty, + ); + } + } + }, + ); + } + }); +} + +MapEntry> dummyPackage( + String name, { + Version? version, + bool publishable = true, + Map deps = const {}, + Map devDeps = const {}, +}) { + final path = 'packages/$name'; + + final pubspecEditor = YamlEditor(''' +name: $name + +environment: + sdk: ^3.0.0 + +dependencies: {} + +dev_dependencies: {} +'''); + + if (version != null) { + pubspecEditor.update(['version'], version.toString()); + } + + void addConstraints( + Map constraints, + DependencyType type, + ) { + for (final MapEntry(key: dep, value: constraint) in constraints.entries) { + final path = [type.key, dep.name]; + pubspecEditor.update(path, constraint.toString()); + } + } + + addConstraints(deps, DependencyType.dependency); + addConstraints(devDeps, DependencyType.devDependency); + + if (!publishable) { + pubspecEditor.update(['publish_to'], 'none'); + } + + final pubspecYaml = pubspecEditor.toString(); + final pubspec = Pubspec.parse(pubspecYaml); + final pubspecMap = loadYamlNode(pubspecYaml) as YamlMap; + + final package = PackageInfo( + name: name, + path: path, + pubspecInfo: PubspecInfo( + pubspec: pubspec, + pubspecYaml: pubspecYaml, + pubspecMap: pubspecMap, + uri: Uri.base.resolve(path), + ), + flavor: PackageFlavor.dart, + ); + return MapEntry(package, [...deps.keys, ...devDeps.keys]); +}