diff --git a/packages/aft/lib/src/commands/version_bump_command.dart b/packages/aft/lib/src/commands/version_bump_command.dart index 6260ad434f..3086f235dc 100644 --- a/packages/aft/lib/src/commands/version_bump_command.dart +++ b/packages/aft/lib/src/commands/version_bump_command.dart @@ -10,19 +10,12 @@ import 'package:aft/src/changelog/printer.dart'; import 'package:aft/src/options/git_ref_options.dart'; import 'package:aft/src/options/glob_options.dart'; import 'package:aft/src/repo.dart'; -import 'package:path/path.dart' as p; /// Command for bumping package versions across the repo. class VersionBumpCommand extends AmplifyCommand with GitRefOptions, GlobOptions { VersionBumpCommand() { argParser - ..addFlag( - 'preview', - help: 'Preview version changes without applying', - defaultsTo: false, - negatable: false, - ) ..addFlag( 'yes', abbr: 'y', @@ -56,8 +49,6 @@ class VersionBumpCommand extends AmplifyCommand late final bool yes = argResults!['yes'] as bool; - late final bool preview = argResults!['preview'] as bool; - late final VersionBumpType? forcedBumpType = () { final forceBreaking = argResults!['force-breaking'] as bool; if (forceBreaking) return VersionBumpType.breaking; @@ -85,40 +76,9 @@ class VersionBumpCommand extends AmplifyCommand changesForPackage: _changesForPackage, forcedBumpType: forcedBumpType, ); - final changelogUpdates = repo.changelogUpdates; - - final bumpedPackages = []; - for (final package in repo.publishablePackages()) { - final edits = package.pubspecInfo.pubspecYamlEditor.edits; - if (edits.isEmpty) { - continue; - } - bumpedPackages.add(package); - if (preview) { - logger.info('pubspec.yaml'); - for (final edit in edits) { - final originalText = package.pubspecInfo.pubspecYaml - .substring(edit.offset, edit.offset + edit.length); - logger.info('$originalText --> ${edit.replacement}'); - } - } else { - await File(p.join(package.path, 'pubspec.yaml')) - .writeAsString(package.pubspecInfo.pubspecYamlEditor.toString()); - } - final changelogUpdate = changelogUpdates[package]; - if (changelogUpdate != null && changelogUpdate.hasUpdate) { - if (preview) { - logger - ..info('CHANGELOG.md') - ..info(changelogUpdate.newText!); - } else { - await File(p.join(package.path, 'CHANGELOG.md')) - .writeAsString(changelogUpdate.toString()); - } - } - } - - return bumpedPackages; + return repo.writeChanges( + packages: repo.publishablePackages(), + ); } @override @@ -130,20 +90,18 @@ class VersionBumpCommand extends AmplifyCommand final bumpedPackages = await _updateVersions(); - if (!preview) { - for (final package in bumpedPackages) { - // Run build_runner for packages which generate their version number. - final needsBuildRunner = package.pubspecInfo.pubspec.devDependencies - .containsKey('build_version'); - if (!needsBuildRunner) { - continue; - } - await runBuildRunner( - package, - logger: logger, - verbose: verbose, - ); + for (final package in bumpedPackages) { + // Run build_runner for packages which generate their version number. + final needsBuildRunner = package.pubspecInfo.pubspec.devDependencies + .containsKey('build_version'); + if (!needsBuildRunner) { + continue; } + await runBuildRunner( + package, + logger: logger, + verbose: verbose, + ); } logger.info('Version successfully bumped'); diff --git a/packages/aft/lib/src/config/config_loader.dart b/packages/aft/lib/src/config/config_loader.dart index cdb5f318be..b939762582 100644 --- a/packages/aft/lib/src/config/config_loader.dart +++ b/packages/aft/lib/src/config/config_loader.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:aft/src/config/config.dart'; import 'package:aft/src/config/raw_config.dart'; +import 'package:aws_common/aws_common.dart'; import 'package:collection/collection.dart'; import 'package:path/path.dart' as p; import 'package:pubspec_parse/pubspec_parse.dart'; @@ -127,10 +128,30 @@ class AftConfigLoader { rawComponents.map((name, component) { final summaryPackage = switch (component.summary) { null => null, - final summary => repoPackages[summary]!, + final summary => switch (repoPackages[summary]) { + final summaryPackage? => summaryPackage, + // Allow missing summary package for testing + _ when zDebugMode => null, + _ => throw StateError( + 'Summary package "$summary" does not exist for component: ' + '${component.name}', + ), + }, }; - final packages = - component.packages.map((name) => repoPackages[name]!).toList(); + final packages = component.packages + .map( + (name) => switch (repoPackages[name]) { + final package? => package, + // Allow missing component package for testing + _ when zDebugMode => null, + _ => throw StateError( + 'Component package "$name" does not exist for component: ' + '${component.name}', + ), + }, + ) + .nonNulls + .toList(); final packageGraph = UnmodifiableMapView({ for (final package in packages) package.name: package.pubspecInfo.pubspec.dependencies.keys diff --git a/packages/aft/lib/src/repo.dart b/packages/aft/lib/src/repo.dart index 78f8201fe4..010234a5b0 100644 --- a/packages/aft/lib/src/repo.dart +++ b/packages/aft/lib/src/repo.dart @@ -51,6 +51,8 @@ class Repo { final GitDir git; + PackageInfo operator [](String packageName) => allPackages[packageName]!; + /// All packages which can be published to `pub.dev`. List publishablePackages([ Map? allPackages, @@ -252,6 +254,35 @@ class Repo { } } + /// Writes all changes made by, for example, [bumpAllVersions], to disk. + /// + /// If [packages] is passed, only changes for those packages are written. + /// Otherwise, all repo packages are affected. + /// + /// Returns the list of packages which both had changes and were written. + Future> writeChanges({ + List? packages, + }) async { + final affectedPackages = []; + for (final package in packages ?? allPackages.values.toList()) { + final edits = package.pubspecInfo.pubspecYamlEditor.edits; + // Don't write changelog updates for packages with no corresponding + // pubspec update. + if (edits.isEmpty) { + continue; + } + affectedPackages.add(package); + await File(p.join(package.path, 'pubspec.yaml')) + .writeAsString(package.pubspecInfo.pubspecYamlEditor.toString()); + final changelogUpdate = changelogUpdates[package]; + if (changelogUpdate != null && changelogUpdate.hasUpdate) { + await File(p.join(package.path, 'CHANGELOG.md')) + .writeAsString(changelogUpdate.toString()); + } + } + return affectedPackages; + } + /// Bumps the version and changelog in [package] and its component packages /// using [commit] and returns the new version. /// diff --git a/packages/aft/test/common.dart b/packages/aft/test/common.dart deleted file mode 100644 index d33b695faf..0000000000 --- a/packages/aft/test/common.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:aft/aft.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_parse/pubspec_parse.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -/// Creates a dummy package for testing repo operations. -MapEntry> dummyPackage( - String name, { - Version? version, - bool publishable = true, - VersionConstraint? sdkConstraint, - Map deps = const {}, - Map devDeps = const {}, -}) { - final path = 'packages/$name'; - sdkConstraint ??= VersionConstraint.compatibleWith(Version(3, 0, 0)); - - final pubspecEditor = YamlEditor(''' -name: $name - -environment: - sdk: $sdkConstraint - -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]); -} diff --git a/packages/aft/test/constraints_checker_test.dart b/packages/aft/test/constraints_checker_test.dart index 066a0ce37b..eb5e7864f4 100644 --- a/packages/aft/test/constraints_checker_test.dart +++ b/packages/aft/test/constraints_checker_test.dart @@ -3,30 +3,39 @@ import 'package:aft/aft.dart'; import 'package:aft/src/constraints_checker.dart'; +import 'package:aft/src/repo.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; import 'package:yaml_edit/yaml_edit.dart'; -import 'common.dart'; +import 'helpers/descriptors.dart' as d; + +extension on Repo { + PackageInfo get amplifyCore => this['amplify_core']; + PackageInfo get amplifyFlutter => this['amplify_flutter']; + PackageInfo get amplifyTest => this['amplify_test']; +} void main() { group('GlobalConstraintsChecker', () { for (final action in ConstraintsAction.values) { test( 'handles SDK constraints for preview Dart versions (${action.name})', - () { - final preReleaseConstraint = VersionConstraint.compatibleWith( - Version(3, 2, 0, pre: '0'), - ); - final actions = dummyPackage( - 'actions', - publishable: false, - sdkConstraint: preReleaseConstraint, - ); - final amplifyFlutter = dummyPackage( - 'amplify_flutter', - sdkConstraint: preReleaseConstraint, - ); + () async { + const preReleaseConstraint = '^3.2.0-0'; + final actions = await d + .package( + 'actions', + publishable: false, + sdkConstraint: preReleaseConstraint, + ) + .create(); + final amplifyFlutter = await d + .package( + 'amplify_flutter', + sdkConstraint: preReleaseConstraint, + ) + .create(); final checker = GlobalConstraintChecker( action, const {}, @@ -37,25 +46,25 @@ void main() { { expect( - checker.checkConstraints(actions.key), + checker.checkConstraints(actions), isTrue, reason: 'Package is not publishable and can take a prerelease constraint ' 'to leverage new Dart features', ); expect(checker.mismatchedDependencies, isEmpty); - expect(actions.key.pubspecInfo.pubspecYamlEditor.edits, isEmpty); + expect(actions.pubspecInfo.pubspecYamlEditor.edits, isEmpty); } { switch (action) { case ConstraintsAction.apply || ConstraintsAction.update: expect( - checker.checkConstraints(amplifyFlutter.key), + checker.checkConstraints(amplifyFlutter), isTrue, ); expect( - amplifyFlutter.key.pubspecInfo.pubspecYamlEditor.edits.single, + amplifyFlutter.pubspecInfo.pubspecYamlEditor.edits.single, isA().having( (edit) => edit.replacement.trim(), 'replacement', @@ -65,7 +74,7 @@ void main() { expect(checker.mismatchedDependencies, isEmpty); case ConstraintsAction.check: expect( - checker.checkConstraints(amplifyFlutter.key), + checker.checkConstraints(amplifyFlutter), isFalse, reason: 'Package is publishable and must match the global SDK constraint', @@ -85,7 +94,7 @@ void main() { ), ); expect( - amplifyFlutter.key.pubspecInfo.pubspecYamlEditor.edits, + amplifyFlutter.pubspecInfo.pubspecYamlEditor.edits, isEmpty, ); } @@ -105,49 +114,43 @@ void main() { 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, - ]); + () async { + final repo = await d.repo([ + d.package('amplify_core', version: '1.0.0'), + d.package( + 'amplify_test', + publishable: false, + dependencies: { + // An outdated constraint + 'amplify_core': '<1.0.0', + }, + ), + d.package( + 'amplify_flutter', + version: '1.0.0', + dependencies: { + 'amplify_core': '>=1.0.0 <1.1.0', + }, + devDependencies: { + 'amplify_test': 'any', + }, + ), + ]).create(); final constraintsChecker = PublishConstraintsChecker( action, - repoGraph, + repo.getPackageGraph(includeDevDependencies: true), ); { expect( - constraintsChecker.checkConstraints(amplifyCore.key), + constraintsChecker.checkConstraints(repo.amplifyCore), isTrue, ); } { expect( - constraintsChecker.checkConstraints(amplifyTest.key), + constraintsChecker.checkConstraints(repo.amplifyTest), isTrue, reason: "amplify_test's constraint on amplify_core is fine by itself", @@ -158,11 +161,11 @@ void main() { switch (action) { case ConstraintsAction.apply || ConstraintsAction.update: expect( - constraintsChecker.checkConstraints(amplifyFlutter.key), + constraintsChecker.checkConstraints(repo.amplifyFlutter), isTrue, ); expect( - amplifyTest.key.pubspecInfo.pubspecYamlEditor.edits.single, + repo.amplifyTest.pubspecInfo.pubspecYamlEditor.edits.single, isA().having( (edit) => edit.replacement, 'replacement', @@ -172,7 +175,7 @@ void main() { expect(constraintsChecker.mismatchedDependencies, isEmpty); case ConstraintsAction.check: expect( - constraintsChecker.checkConstraints(amplifyFlutter.key), + constraintsChecker.checkConstraints(repo.amplifyFlutter), isFalse, reason: 'The constraint amplify_test has on amplify_core would ' @@ -194,7 +197,7 @@ void main() { ), ); expect( - amplifyTest.key.pubspecInfo.pubspecYamlEditor.edits, + repo.amplifyTest.pubspecInfo.pubspecYamlEditor.edits, isEmpty, ); } diff --git a/packages/aft/test/e2e_test.dart b/packages/aft/test/e2e_test.dart index 6a0e689d5c..1440f7700a 100644 --- a/packages/aft/test/e2e_test.dart +++ b/packages/aft/test/e2e_test.dart @@ -16,72 +16,26 @@ import 'dart:io'; -import 'package:aft/aft.dart'; import 'package:aft/src/repo.dart'; import 'package:aws_common/aws_common.dart'; -import 'package:git/git.dart' as git; import 'package:path/path.dart' as p; -import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; +import 'helpers/descriptors.dart' as d; + void main() { - final logger = AWSLogger()..logLevel = LogLevel.verbose; + final logger = AWSLogger().createChild('E2E'); group('Repo', () { late Repo repo; - late Directory repoDir; late String baseRef; final packageBumps = {}; Future runGit(List args) async { - final result = await git.runGit( - args, - processWorkingDir: repoDir.path, - ); - return (result.stdout as String).trim(); - } - - PackageInfo createPackage( - String packageName, { - Map? dependencies, - Version? version, - }) { - version ??= Version(0, 1, 0); - final packagePath = p.join(repoDir.path, 'packages', packageName); - final pubspec = StringBuffer( - ''' -name: $packageName -version: $version - -environment: - sdk: '>=2.18.0 <4.0.0' -''', - ); - if (dependencies != null && dependencies.isNotEmpty) { - pubspec.writeln('dependencies:'); - for (final dependency in dependencies.entries) { - pubspec.writeln(' ${dependency.key}: "${dependency.value}"'); - } - } - final changelog = ''' -## $version - -Initial version. -'''; - final pubspecUri = Uri.file(p.join(packagePath, 'pubspec.yaml')); - File.fromUri(pubspecUri) - ..createSync(recursive: true) - ..writeAsStringSync(pubspec.toString()); - File(p.join(packagePath, 'CHANGELOG.md')) - ..createSync(recursive: true) - ..writeAsStringSync(changelog); - - return PackageInfo( - name: packageName, - path: packagePath, - pubspecInfo: PubspecInfo.fromUri(pubspecUri), - flavor: PackageFlavor.dart, - ); + final result = await repo.git.runCommand(args); + final stdout = result.stdout as String; + logger.info('git ${args.join(' ')}:\n$stdout'); + return stdout.trim(); } Future makeChange( @@ -90,8 +44,7 @@ Initial version. Map? trailers, }) async { for (final package in packages) { - final newDir = Directory(p.join(repoDir.path, 'packages', package)) - .createTempSync(); + final newDir = Directory(repo[package].path).createTempSync(); File(p.join(newDir.path, 'file.txt')).createSync(); } @@ -107,98 +60,50 @@ Initial version. return runGit(['rev-parse', 'HEAD']); } - setUp(() async { - repoDir = Directory.systemTemp.createTempSync('aft'); - await runGit(['init']); - await runGit( - ['commit', '--allow-empty', '-m', 'Initial commit'], - ); - await runGit(['rev-parse', 'HEAD']); - }); - group('E2E', () { - final nextVersion = Version(1, 0, 0, pre: 'next.0'); - final coreVersion = Version(0, 1, 0); - final nextConstraint = VersionRange( - min: nextVersion, - max: Version(1, 0, 0, pre: 'next.1'), - includeMin: true, - includeMax: false, - ); - final coreConstraint = VersionConstraint.compatibleWith(coreVersion); + const nextVersion = '1.0.0-next.0'; + const coreVersion = '0.1.0'; + const nextConstraint = '>=1.0.0-next.0 <1.0.0-next.1'; + const coreConstraint = '^0.1.0'; setUp(() async { - final amplifyAuthCognito = createPackage( - 'amplify_auth_cognito', - version: nextVersion, - dependencies: { - 'amplify_auth_cognito_ios': nextConstraint, - 'amplify_auth_cognito_dart': coreConstraint, - 'amplify_core': nextConstraint, - 'aws_common': coreConstraint, - }, - ); - final amplifyAuthCognitoIos = createPackage( - 'amplify_auth_cognito_ios', - version: nextVersion, - ); - final amplifyAuthCognitoDart = createPackage( - 'amplify_auth_cognito_dart', - version: coreVersion, - dependencies: { - 'amplify_core': coreConstraint, - 'aws_common': coreConstraint, - }, - ); - final amplifyCore = createPackage( - 'amplify_core', - version: nextVersion, - dependencies: { - 'aws_common': coreConstraint, - }, - ); - final awsCommon = createPackage('aws_common', version: coreVersion); - - final allPackages = [ - amplifyAuthCognito, - amplifyAuthCognitoIos, - amplifyAuthCognitoDart, - amplifyCore, - awsCommon, - ]; - repo = await Repo.open( - AftConfig( - rootDirectory: repoDir.uri, - workingDirectory: Directory.current.uri, - allPackages: { - for (final package in allPackages) package.name: package, + repo = await d.repo([ + d.package( + 'amplify_auth_cognito', + version: nextVersion, + dependencies: { + // Flutter packages in component + 'amplify_analytics_pinpoint': nextConstraint, + 'amplify_core': nextConstraint, + + // Related Dart package + 'amplify_auth_cognito_dart': coreConstraint, + + // Dart package not in component + 'aws_common': coreConstraint, }, - components: { - 'Amplify Flutter': AftComponent( - name: 'Amplify Flutter', - packages: [ - amplifyAuthCognito, - amplifyAuthCognitoIos, - ], - packageGraph: { - 'amplify_auth_cognito': [amplifyAuthCognitoIos], - 'amplify_auth_cognito_ios': [], - }, - propagate: VersionPropagation.minor, - ), + ), + d.package( + 'amplify_analytics_pinpoint', + version: nextVersion, + ), + d.package( + 'amplify_auth_cognito_dart', + version: coreVersion, + dependencies: { + 'amplify_core': coreConstraint, + 'aws_common': coreConstraint, }, - environment: Environment( - sdk: VersionConstraint.compatibleWith(Version(2, 17, 0)), - flutter: VersionConstraint.compatibleWith(Version(3, 0, 0)), - ), - platforms: PlatformEnvironment( - android: AndroidEnvironment(minSdkVersion: '24'), - ios: IosEnvironment(minOSVersion: '13.0'), - macOS: MacOSEnvironment(minOSVersion: '13.0'), - ), ), - logger: logger, - ); + d.package( + 'amplify_core', + version: nextVersion, + dependencies: { + 'aws_common': coreConstraint, + }, + ), + d.package('aws_common', version: coreVersion), + ]).create(); await runGit(['add', '.']); await runGit(['commit', '-m', 'Add packages']); @@ -210,12 +115,12 @@ Initial version. // - Only a root package await makeChange('fix(aws_common): Fix type', ['aws_common']); await makeChange( - 'chore(amplify_auth_cognito_ios): Update iOS dependency', - ['amplify_auth_cognito_ios'], + 'chore(amplify_analytics_pinpoint): Update dependency', + ['amplify_analytics_pinpoint'], ); await makeChange( - 'fix(amplify_auth_cognito_ios)!: Change iOS dependency', - ['amplify_auth_cognito_ios'], + 'fix(amplify_analytics_pinpoint)!: Change dependency', + ['amplify_analytics_pinpoint'], ); await makeChange( 'feat(amplify_core): New hub events', @@ -245,14 +150,15 @@ Initial version. 'chore(version): Release flutter', [ 'amplify_auth_cognito', - 'amplify_auth_cognito_ios', + 'amplify_analytics_pinpoint', ], trailers: { 'Updated-Components': 'Amplify Flutter', }, ); + packageBumps['amplify_core'] = flutterBump; packageBumps['amplify_auth_cognito'] = flutterBump; - packageBumps['amplify_auth_cognito_ios'] = flutterBump; + packageBumps['amplify_analytics_pinpoint'] = flutterBump; }); Future changesForPackage( @@ -269,7 +175,7 @@ Initial version. 'amplify_core': 3, 'amplify_auth_cognito_dart': 2, 'amplify_auth_cognito': 2, - 'amplify_auth_cognito_ios': 3, + 'amplify_analytics_pinpoint': 3, }; for (final entry in packages.entries) { test(entry.key, () async { @@ -293,7 +199,7 @@ Initial version. 'amplify_core': 0, 'amplify_auth_cognito_dart': 0, 'amplify_auth_cognito': 0, - 'amplify_auth_cognito_ios': 0, + 'amplify_analytics_pinpoint': 0, }; for (final entry in packages.entries) { test(entry.key, () async { @@ -321,7 +227,7 @@ Initial version. 'amplify_core': 3, 'amplify_auth_cognito_dart': 2, 'amplify_auth_cognito': 2, - 'amplify_auth_cognito_ios': 3, + 'amplify_analytics_pinpoint': 3, }; final changelogs = { 'aws_common': ''' @@ -349,11 +255,11 @@ Initial version. ### Features - feat(auth): New feature ''', - 'amplify_auth_cognito_ios': ''' + 'amplify_analytics_pinpoint': ''' ## NEXT ### Breaking Changes -- fix(amplify_auth_cognito_ios)!: Change iOS dependency +- fix(amplify_analytics_pinpoint)!: Change dependency ''', }; @@ -382,129 +288,149 @@ Initial version. } }); - group('bumps versions', () { - final finalVersions = { - 'aws_common': '0.1.0+1', - 'amplify_core': '1.0.0-next.0+1', - 'amplify_auth_cognito_dart': '0.1.1', - 'amplify_auth_cognito': '1.0.0-next.1', - 'amplify_auth_cognito_ios': '1.0.0-next.1', - }; - final updatedChangelogs = { - 'aws_common': ''' + test('bumps versions', () async { + final finalRepo = d.repo([ + d.package( + 'aws_common', + version: '0.1.0+1', + contents: [ + d.pubspec(''' +name: aws_common +version: 0.1.0+1 + +environment: + sdk: ^3.0.0 +'''), + d.file('CHANGELOG.md', ''' ## 0.1.0+1 ### Fixes - fix(aws_common): Fix type -''', - 'amplify_core': ''' -## 1.0.0-next.0+1 - -### Features -- feat(amplify_core): New hub events -- feat(auth): New feature -''', - 'amplify_auth_cognito_dart': ''' -## 0.1.1 - -### Features -- feat(auth): New feature -''', - 'amplify_auth_cognito': ''' -## 1.0.0-next.1 -### Features -- feat(auth): New feature -''', - 'amplify_auth_cognito_ios': ''' -## 1.0.0-next.1 - -### Breaking Changes -- fix(amplify_auth_cognito_ios)!: Change iOS dependency -''', - }; - final updatedPubspecs = { - 'aws_common': ''' -name: aws_common -version: 0.1.0+1 +## 0.1.0 -environment: - sdk: '>=2.18.0 <4.0.0' -''', - 'amplify_core': ''' +Initial version. +'''), + ], + ), + d.package( + 'amplify_core', + version: '1.0.0-next.1', + contents: [ + d.pubspec(''' name: amplify_core -version: 1.0.0-next.0+1 +version: 1.0.0-next.1 environment: - sdk: '>=2.18.0 <4.0.0' + sdk: ^3.0.0 dependencies: aws_common: "^0.1.0" -''', - 'amplify_auth_cognito_dart': ''' +'''), + d.file('CHANGELOG.md', ''' +## 1.0.0-next.1 + +### Features +- feat(amplify_core): New hub events +- feat(auth): New feature + +## 1.0.0-next.0 + +Initial version. +'''), + ], + ), + d.package( + 'amplify_auth_cognito_dart', + version: '0.1.1', + contents: [ + d.pubspec(''' name: amplify_auth_cognito_dart version: 0.1.1 environment: - sdk: '>=2.18.0 <4.0.0' + sdk: ^3.0.0 dependencies: - amplify_core: ">=1.0.0-next.0+1 <1.0.0-next.1" - aws_common: "^0.1.0" -''', - 'amplify_auth_cognito': ''' + amplify_core: ">=1.0.0-next.1 <1.0.0-next.2" + aws_common: ^0.1.0 +'''), + d.file('CHANGELOG.md', ''' +## 0.1.1 + +### Features +- feat(auth): New feature + +## 0.1.0 + +Initial version. +'''), + ], + ), + d.package( + 'amplify_auth_cognito', + version: '1.0.0-next.1', + contents: [ + d.pubspec(''' name: amplify_auth_cognito version: 1.0.0-next.1 environment: - sdk: '>=2.18.0 <4.0.0' + sdk: ^3.0.0 dependencies: - amplify_auth_cognito_ios: ">=1.0.0-next.1 <1.0.0-next.2" + amplify_analytics_pinpoint: ">=1.0.0-next.1 <1.0.0-next.2" amplify_auth_cognito_dart: ">=0.1.1 <0.2.0" - amplify_core: ">=1.0.0-next.0+1 <1.0.0-next.1" + amplify_core: ">=1.0.0-next.1 <1.0.0-next.2" aws_common: "^0.1.0" -''', - 'amplify_auth_cognito_ios': ''' -name: amplify_auth_cognito_ios +'''), + d.file('CHANGELOG.md', ''' +## 1.0.0-next.1 + +### Features +- feat(auth): New feature + +## 1.0.0-next.0 + +Initial version. +'''), + ], + ), + d.package( + 'amplify_analytics_pinpoint', + version: '1.0.0-next.1', + contents: [ + d.pubspec(''' +name: amplify_analytics_pinpoint version: 1.0.0-next.1 environment: - sdk: '>=2.18.0 <4.0.0' -''', - }; + sdk: ^3.0.0 +'''), + d.file('CHANGELOG.md', ''' +## 1.0.0-next.1 - setUp(() async { - await repo.bumpAllVersions( - repo.allPackages, - changesForPackage: (pkg) => changesForPackage( - pkg.name, - baseRef: baseRef, - ), - ); - }); +### Breaking Changes +- fix(amplify_analytics_pinpoint)!: Change dependency - for (final check in finalVersions.entries) { - final packageName = check.key; - test(packageName, () { - final package = repo.allPackages[packageName]!; - final newVersion = - repo.versionChanges.proposedVersion(package.name); - expect(newVersion.toString(), finalVersions[packageName]); +## 1.0.0-next.0 - final changelog = repo.changelogUpdates[package]!.newText; - expect( - changelog, - equalsIgnoringWhitespace(updatedChangelogs[packageName]!), - ); +Initial version. +'''), + ], + ), + ]); - final pubspec = package.pubspecInfo.pubspecYamlEditor.toString(); - expect( - pubspec, - equalsIgnoringWhitespace(updatedPubspecs[packageName]!), - ); - }); - } + await repo.bumpAllVersions( + repo.allPackages, + changesForPackage: (pkg) => changesForPackage( + pkg.name, + baseRef: baseRef, + ), + ); + await repo.writeChanges(); + + await finalRepo.validate(); }); }); }); diff --git a/packages/aft/test/helpers/descriptors.dart b/packages/aft/test/helpers/descriptors.dart new file mode 100644 index 0000000000..095d6b45a1 --- /dev/null +++ b/packages/aft/test/helpers/descriptors.dart @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/// Descriptors for working in the test sandbox. +/// +/// It's recommended that tests import this like: +/// ```dart +/// import 'helpers/descriptors.dart' as d; +/// ``` +library; + +export 'package:test_descriptor/test_descriptor.dart'; + +export 'package_descriptor.dart'; +export 'pubspec_descriptor.dart'; +export 'repo_descriptor.dart'; diff --git a/packages/aft/test/helpers/matchers.dart b/packages/aft/test/helpers/matchers.dart new file mode 100644 index 0000000000..5ab41e32d4 --- /dev/null +++ b/packages/aft/test/helpers/matchers.dart @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +/// Creates a [Matcher] for the [expected] contents of a pubspec. +/// +/// Unlike [equals], this compares by deep equality of the [Map] representation +/// of the pubspecs, meaning that the YAML strings can vary. +Matcher matchesPubspec(String expected) => _PubspecMatcher(expected); + +final class _PubspecMatcher extends Matcher { + _PubspecMatcher(this._expectedYaml); + + final String _expectedYaml; + late final YamlMap expected = loadYaml(_expectedYaml) as YamlMap; + late final _equalsMatcher = equals(expected); + + @override + Description describe(Description description) { + return _equalsMatcher.describe(description); + } + + @override + Description describeMismatch( + Object? item, + Description mismatchDescription, + Map matchState, + bool verbose, + ) { + return _equalsMatcher.describeMismatch( + item, + mismatchDescription, + matchState, + verbose, + ); + } + + @override + bool matches(Object? item, Map matchState) { + final pubspecMap = switch (item) { + String _ => loadYaml(item) as YamlMap, + Map _ => item, + _ => throw ArgumentError( + 'Invalid pubspec. Expected String or Map, got ${item.runtimeType}', + ), + }; + return _equalsMatcher.matches(pubspecMap, matchState); + } +} diff --git a/packages/aft/test/helpers/package_descriptor.dart b/packages/aft/test/helpers/package_descriptor.dart new file mode 100644 index 0000000000..5f6ef3f5fe --- /dev/null +++ b/packages/aft/test/helpers/package_descriptor.dart @@ -0,0 +1,161 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:aft/aft.dart'; +import 'package:collection/collection.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; +import 'package:yaml_edit/yaml_edit.dart'; + +/// {@macro aft.test.package_descriptor} +PackageDescriptor package( + String name, { + String? version, + bool publishable = true, + String? sdkConstraint, + Map dependencies = const {}, + Map devDependencies = const {}, + List contents = const [], +}) => + PackageDescriptor( + name, + version: version, + publishable: publishable, + sdkConstraint: sdkConstraint, + dependencies: dependencies, + devDependencies: devDependencies, + contents: contents, + ); + +/// {@template aft.test.package_descriptor} +/// Describes the desired state of a package. +/// +/// Call [create] to create a package with the given description in +/// the temporary sandbox. +/// {@endtemplate} +final class PackageDescriptor extends d.Descriptor { + /// {@macro aft.test.package_descriptor} + factory PackageDescriptor( + String name, { + String? version, + bool publishable = true, + String? sdkConstraint, + Map dependencies = const {}, + Map devDependencies = const {}, + List contents = const [], + }) { + return PackageDescriptor._( + name, + version: switch (version) { + final version? => Version.parse(version), + _ => null, + }, + publishable: publishable, + sdkConstraint: switch (sdkConstraint) { + final sdkConstraint? => VersionConstraint.parse(sdkConstraint), + _ => null, + }, + dependencies: dependencies.map( + (name, constraint) => MapEntry( + name, + VersionConstraint.parse(constraint), + ), + ), + devDependencies: devDependencies.map( + (name, constraint) => MapEntry( + name, + VersionConstraint.parse(constraint), + ), + ), + contents: contents, + ); + } + + PackageDescriptor._( + super.name, { + this.version, + this.publishable = true, + this.sdkConstraint, + this.dependencies = const {}, + this.devDependencies = const {}, + this.contents = const [], + }); + + final Version? version; + final bool publishable; + final VersionConstraint? sdkConstraint; + final Map dependencies; + final Map devDependencies; + + /// The contents of the package directory. + /// + /// By default, this creates a `pubspec.yaml` and initial `CHANGELOG.md`. + final List contents; + + /// The contents of the `pubspec.yaml`. + String get pubspec { + final sdkConstraint = this.sdkConstraint ?? + VersionConstraint.compatibleWith(Version(3, 0, 0)); + + final pubspecEditor = YamlEditor(''' +name: $name +${version == null ? '' : 'version: $version\n'} +environment: + sdk: $sdkConstraint +'''); + + void addConstraints( + Map constraints, + DependencyType type, + ) { + if (constraints.isNotEmpty) { + pubspecEditor.update([type.key], {}); + } + for (final MapEntry(key: dep, value: constraint) in constraints.entries) { + final path = [type.key, dep]; + pubspecEditor.update(path, constraint.toString()); + } + } + + addConstraints(dependencies, DependencyType.dependency); + addConstraints(devDependencies, DependencyType.devDependency); + + if (!publishable) { + pubspecEditor.update(['publish_to'], 'none'); + } + + return pubspecEditor.toString(); + } + + /// The directory descriptor for the package. + d.DirectoryDescriptor get dir => d.dir( + name, + [ + ...contents, + if (contents.none((d) => d.name == 'pubspec.yaml')) + d.file('pubspec.yaml', pubspec), + if (contents.none((d) => d.name == 'CHANGELOG.md') && version != null) + d.file('CHANGELOG.md', ''' +## $version + +Initial version. +'''), + ], + ); + + @override + Future create([String? parent]) async { + await dir.create(parent); + return PackageInfo.fromDirectory(dir.io)!; + } + + @override + String describe() => name; + + @override + Future validate([String? parent]) async { + await dir.validate(parent); + expect(PackageInfo.fromDirectory(dir.io), isNotNull); + } +} diff --git a/packages/aft/test/helpers/pubspec_descriptor.dart b/packages/aft/test/helpers/pubspec_descriptor.dart new file mode 100644 index 0000000000..4498f09fd3 --- /dev/null +++ b/packages/aft/test/helpers/pubspec_descriptor.dart @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; +import 'package:yaml/yaml.dart'; + +import 'matchers.dart'; + +/// {@macro aft.test.pubspec_descriptor} +PubspecDescriptor pubspec(String contents) => PubspecDescriptor(contents); + +/// {@template aft.test.pubspec_descriptor} +/// Creates a file descriptor for a `pubspec.yaml` with the given [contents]. +/// +/// Can be used to create or validate a pubspec. +/// {@endtemplate} +final class PubspecDescriptor extends d.Descriptor { + /// {@macro aft.test.pubspec_descriptor} + PubspecDescriptor(this.contents) : super('pubspec.yaml'); + + final String contents; + + @override + Future create([String? parent]) async { + await d.file('pubspec.yaml', contents).create(parent); + } + + @override + String describe() => name; + + @override + Future validate([String? parent]) async { + final path = p.join(parent ?? d.sandbox, 'pubspec.yaml'); + try { + expect( + loadYaml(await File(path).readAsString()), + matchesPubspec(contents), + ); + } on TestFailure catch (error) { + final prettyPath = p.relative(path, from: d.sandbox); + fail('Invalid contents for file "$prettyPath":\n${error.message}'); + } + } +} diff --git a/packages/aft/test/helpers/repo_descriptor.dart b/packages/aft/test/helpers/repo_descriptor.dart new file mode 100644 index 0000000000..dc16367ae4 --- /dev/null +++ b/packages/aft/test/helpers/repo_descriptor.dart @@ -0,0 +1,88 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:io'; + +import 'package:aft/aft.dart'; +import 'package:aft/src/config/config_loader.dart'; +import 'package:aft/src/repo.dart'; +import 'package:aws_common/aws_common.dart'; +import 'package:git/git.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +/// {@macro aft.test.repo_descriptor} +RepoDescriptor repo(Iterable contents) => + RepoDescriptor(contents); + +/// {@template aft.test.repo_descriptor} +/// Describes the desired state of a repo. +/// +/// Call [create] to create a repository with the given description in +/// the temporary sandbox. +/// {@endtemplate} +final class RepoDescriptor extends d.Descriptor { + /// {@macro aft.test.repo_descriptor} + RepoDescriptor(Iterable contents) + : contents = contents.toList(), + super('amplify_flutter'); + + final List contents; + + final _loader = AftConfigLoader( + workingDirectory: Directory(d.sandbox), + ); + + /// The root `pubspec.yaml`. + /// + /// Uses the real pubspec for the repo for improved testing. + String get pubspec { + var dir = Directory.current; + Directory? rootDirectory; + while (p.absolute(dir.parent.path) != p.absolute(dir.path)) { + if (dir.pubspec != null) { + rootDirectory = dir; + } + dir = dir.parent; + } + expect( + rootDirectory, + isNotNull, + reason: 'Should find root directory', + ); + return rootDirectory!.pubspec!.pubspecYaml; + } + + @override + Future create([String? parent]) async { + assert(parent == null, 'Not supported. Should use root sandbox'); + final gitDir = await GitDir.init(parent ?? d.sandbox); + await gitDir.runCommand( + ['commit', '--allow-empty', '-m', 'Initial commit'], + ); + await d.file('pubspec.yaml', pubspec).create(parent); + for (final item in contents) { + await item.create(parent); + } + final repo = await Repo.open( + _loader.load(), + logger: AWSLogger()..logLevel = LogLevel.verbose, + ); + return repo; + } + + @override + String describe() => name; + + @override + Future validate([String? parent]) async { + assert(parent == null, 'Not supported. Should use root sandbox'); + await GitDir.fromExisting(parent ?? d.sandbox); + await d.file('pubspec.yaml', pubspec).validate(parent); + for (final item in contents) { + await item.validate(parent); + } + expect(_loader.load, returnsNormally); + } +} diff --git a/packages/aft/test/model_test.dart b/packages/aft/test/model_test.dart index 7df04612bf..b5ff167be6 100644 --- a/packages/aft/test/model_test.dart +++ b/packages/aft/test/model_test.dart @@ -18,7 +18,7 @@ import 'package:aft/src/models.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; -import 'common.dart'; +import 'helpers/descriptors.dart' as d; void main() { group('AmplifyVersion', () { @@ -116,23 +116,23 @@ void main() { }); group('PackageInfo', () { - test('compatibleWithActiveSdk', () { + test('compatibleWithActiveSdk', () async { final currentDartVersion = Version.parse( Platform.version.split(RegExp(r'\s+')).first, ); - final stablePackage = dummyPackage('stable_pkg'); - final previewPackage = dummyPackage( - 'preview_pkg', - sdkConstraint: VersionConstraint.compatibleWith( - Version(3, 2, 0, pre: '0'), - ), - ); + final stablePackage = await d.package('stable_pkg').create(); + final previewPackage = await d + .package( + 'preview_pkg', + sdkConstraint: '^3.2.0-0', + ) + .create(); expect( - stablePackage.key.compatibleWithActiveSdk, + stablePackage.compatibleWithActiveSdk, isTrue, ); expect( - previewPackage.key.compatibleWithActiveSdk, + previewPackage.compatibleWithActiveSdk, currentDartVersion.isPreRelease, ); });