From 0c64d61eb9fa0f65b85a21e0843e112d0b717733 Mon Sep 17 00:00:00 2001 From: Gabriel Terwesten Date: Tue, 5 Jul 2022 13:03:27 +0200 Subject: [PATCH] feat: write conventional commit scopes in changelog (#341) Fixes #318 --- docs/configuration/overview.mdx | 10 +++ packages/melos/lib/src/common/changelog.dart | 7 ++ packages/melos/lib/src/workspace_configs.dart | 65 ++++++++------ packages/melos/test/changelog_test.dart | 87 ++++++++++++++----- .../melos/test/workspace_config_test.dart | 28 ++++-- 5 files changed, 144 insertions(+), 53 deletions(-) diff --git a/docs/configuration/overview.mdx b/docs/configuration/overview.mdx index d94a71989..66979b030 100644 --- a/docs/configuration/overview.mdx +++ b/docs/configuration/overview.mdx @@ -172,6 +172,16 @@ command: branch: main ``` +### `command/version/includeScopes` + +Whether to include conventional commit scopes in the generated CHANGELOG.md. + +```yaml +command: + version: + includeScopes: true +``` + ### `command/version/includeCommitId` Whether to add short commit id (no links) in the CHANGELOG.md, that is generated by `melos version`. diff --git a/packages/melos/lib/src/common/changelog.dart b/packages/melos/lib/src/common/changelog.dart index 1d83219ca..46f20f9a7 100644 --- a/packages/melos/lib/src/common/changelog.dart +++ b/packages/melos/lib/src/common/changelog.dart @@ -173,6 +173,13 @@ extension ChangelogStringBufferExtension on StringBuffer { writePunctuated(processCommitHeader(parsedMessage.header)); } else { writeBold(parsedMessage.type!.toUpperCase()); + if (config.commands.version.includeScopes) { + if (parsedMessage.scopes.isNotEmpty) { + write('('); + write(parsedMessage.scopes.join(',')); + write(')'); + } + } write(': '); writePunctuated(processCommitHeader(parsedMessage.description!)); } diff --git a/packages/melos/lib/src/workspace_configs.dart b/packages/melos/lib/src/workspace_configs.dart index 95a9116c6..e6426f367 100644 --- a/packages/melos/lib/src/workspace_configs.dart +++ b/packages/melos/lib/src/workspace_configs.dart @@ -269,22 +269,28 @@ BootstrapCommandConfigs( @immutable class VersionCommandConfigs { const VersionCommandConfigs({ + this.branch, this.message, + this.includeScopes = false, this.linkToCommits, this.includeCommitId, - this.branch, this.workspaceChangelog = false, this.updateGitTagRefs = false, }); factory VersionCommandConfigs.fromYaml(Map yaml) { + final branch = assertKeyIsA( + key: 'branch', + map: yaml, + path: 'command/version', + ); final message = assertKeyIsA( key: 'message', map: yaml, path: 'command/version', ); - final linkToCommits = assertKeyIsA( - key: 'linkToCommits', + final includeScopes = assertKeyIsA( + key: 'includeScopes', map: yaml, path: 'command/version', ); @@ -293,18 +299,16 @@ class VersionCommandConfigs { map: yaml, path: 'command/version', ); - final branch = assertKeyIsA( - key: 'branch', + final linkToCommits = assertKeyIsA( + key: 'linkToCommits', map: yaml, path: 'command/version', ); - final workspaceChangelog = assertKeyIsA( key: 'workspaceChangelog', map: yaml, path: 'command/version', ); - final updateGitTagRefs = assertKeyIsA( key: 'updateGitTagRefs', map: yaml, @@ -313,9 +317,10 @@ class VersionCommandConfigs { return VersionCommandConfigs( branch: branch, - linkToCommits: linkToCommits, - includeCommitId: includeCommitId, message: message, + includeScopes: includeScopes ?? false, + includeCommitId: includeCommitId, + linkToCommits: linkToCommits, workspaceChangelog: workspaceChangelog ?? false, updateGitTagRefs: updateGitTagRefs ?? false, ); @@ -323,31 +328,36 @@ class VersionCommandConfigs { static const VersionCommandConfigs empty = VersionCommandConfigs(); + /// If specified, prevents `melos version` from being used inside branches + /// other than the one specified. + final String? branch; + /// A custom header for the generated CHANGELOG.md. final String? message; - /// Whether to add links to commits in the generated CHANGELOG.md. - final bool? linkToCommits; + /// Whether to include conventional commit scopes in the generated + /// CHANGELOG.md. + final bool includeScopes; /// Whether to add commits ids in the generated CHANGELOG.md. final bool? includeCommitId; + /// Whether to add links to commits in the generated CHANGELOG.md. + final bool? linkToCommits; + /// Whether to also generate a CHANGELOG.md for the entire workspace at the root. final bool workspaceChangelog; /// Whether to also update pubspec with git referenced packages. final bool updateGitTagRefs; - /// If specified, prevents `melos version` from being used inside branches - /// other than the one specified. - final String? branch; - Map toJson() { return { + if (branch != null) 'branch': branch, if (message != null) 'message': message, - if (linkToCommits != null) 'linkToCommits': linkToCommits, + 'includeScopes': includeScopes, if (includeCommitId != null) 'includeCommitId': includeCommitId, - if (branch != null) 'branch': branch, + if (linkToCommits != null) 'linkToCommits': linkToCommits, 'workspaceChangelog': workspaceChangelog, 'updateGitTagRefs': updateGitTagRefs, }; @@ -356,34 +366,37 @@ class VersionCommandConfigs { @override bool operator ==(Object other) => other is VersionCommandConfigs && - runtimeType == other.runtimeType && + other.runtimeType == runtimeType && + other.branch == branch && other.message == message && - other.linkToCommits == linkToCommits && + other.includeScopes == includeScopes && other.includeCommitId == includeCommitId && + other.linkToCommits == linkToCommits && other.workspaceChangelog == workspaceChangelog && - other.updateGitTagRefs == updateGitTagRefs && - other.branch == branch; + other.updateGitTagRefs == updateGitTagRefs; @override int get hashCode => runtimeType.hashCode ^ + branch.hashCode ^ message.hashCode ^ - linkToCommits.hashCode ^ + includeScopes.hashCode ^ includeCommitId.hashCode ^ + linkToCommits.hashCode ^ workspaceChangelog.hashCode ^ - updateGitTagRefs.hashCode ^ - branch.hashCode; + updateGitTagRefs.hashCode; @override String toString() { return ''' VersionCommandConfigs( + branch: $branch, message: $message, - linkToCommits: $linkToCommits, + includeScopes: $includeScopes, includeCommitId: $includeCommitId, + linkToCommits: $linkToCommits, workspaceChangelog: $workspaceChangelog, updateGitTagRefs: $updateGitTagRefs, - branch: $branch, )'''; } } diff --git a/packages/melos/test/changelog_test.dart b/packages/melos/test/changelog_test.dart index c4839816f..79a77d28d 100644 --- a/packages/melos/test/changelog_test.dart +++ b/packages/melos/test/changelog_test.dart @@ -24,16 +24,53 @@ import 'package:test/test.dart'; import 'utils.dart'; void main() { + group('conventional commit', () { + test('write scopes', () { + final workspace = buildWorkspaceWithRepository(includeScopes: true); + final package = workspace.allPackages['test_pkg']!; + + // No scope + expect( + renderCommitPackageUpdate( + workspace, + package, + testCommit(message: 'feat: a'), + ), + contains('**FEAT**: a.'), + ); + + // One scope + expect( + renderCommitPackageUpdate( + workspace, + package, + testCommit(message: 'feat(a): b'), + ), + contains('**FEAT**(a): b.'), + ); + + // Multiple scopes + expect( + renderCommitPackageUpdate( + workspace, + package, + testCommit(message: 'feat(a,b): c'), + ), + contains('**FEAT**(a,b): c.'), + ); + }); + }); + group('linkToCommits', () { test('when enabled, adds link to commit behind each one', () { - final workspace = buildWorkspaceWithRepository(); + final workspace = buildWorkspaceWithRepository(linkToCommits: true); final package = workspace.allPackages['test_pkg']!; - final commit = testCommit(message: 'feat(a): b'); + final commit = testCommit(message: 'feat: a'); final commitUrl = workspace.config.repository!.commitUrl(commit.id); expect( renderCommitPackageUpdate(workspace, package, commit), - contains('**FEAT**: b. ([${commit.id.substring(0, 8)}]($commitUrl))'), + contains('**FEAT**: a. ([${commit.id.substring(0, 8)}]($commitUrl))'), ); }); }); @@ -42,47 +79,52 @@ void main() { test('when enabled, adds commit id behind each one', () { final workspace = buildWorkspaceWithRepository( includeCommitId: true, - linkToCommits: false, ); final package = workspace.allPackages['test_pkg']!; - final commit = testCommit(message: 'feat(a): b'); + final commit = testCommit(message: 'feat: a'); expect( renderCommitPackageUpdate(workspace, package, commit), - contains('**FEAT**: b. (${commit.id.substring(0, 8)})'), + contains('**FEAT**: a. (${commit.id.substring(0, 8)})'), ); }); test( - 'when enabled, and linkToCommits is also enabled adds link to commit behind each one', - () { - final workspace = buildWorkspaceWithRepository(includeCommitId: true); - final package = workspace.allPackages['test_pkg']!; - final commit = testCommit(message: 'feat(a): b'); - final commitUrl = workspace.config.repository!.commitUrl(commit.id); + 'when enabled, and linkToCommits is also enabled adds link to commit' + ' behind each one', + () { + final workspace = buildWorkspaceWithRepository( + includeCommitId: true, + linkToCommits: true, + ); + final package = workspace.allPackages['test_pkg']!; + final commit = testCommit(message: 'feat: a'); + final commitUrl = workspace.config.repository!.commitUrl(commit.id); - expect( - renderCommitPackageUpdate(workspace, package, commit), - contains('**FEAT**: b. ([${commit.id.substring(0, 8)}]($commitUrl))'), - ); - }); + expect( + renderCommitPackageUpdate(workspace, package, commit), + contains('**FEAT**: a. ([${commit.id.substring(0, 8)}]($commitUrl))'), + ); + }, + ); }); test('when repository is specified, adds links to referenced issues/PRs', () { - final workspace = buildWorkspaceWithRepository(linkToCommits: false); + final workspace = buildWorkspaceWithRepository(); final package = workspace.allPackages['test_pkg']!; - final commit = testCommit(message: 'feat(a): b (#123)'); + final commit = testCommit(message: 'feat: a (#123)'); final issueUrl = workspace.config.repository!.issueUrl('123'); expect( renderCommitPackageUpdate(workspace, package, commit), - contains('**FEAT**: b ([#123]($issueUrl)).'), + contains('**FEAT**: a ([#123]($issueUrl)).'), ); }); } MelosWorkspace buildWorkspaceWithRepository({ - bool linkToCommits = true, + bool includeScopes = false, + bool linkToCommits = false, bool includeCommitId = false, }) { final workspaceBuilder = VirtualWorkspaceBuilder( @@ -90,8 +132,9 @@ MelosWorkspace buildWorkspaceWithRepository({ repository: https://github.com/a/b command: version: - linkToCommits: $linkToCommits + includeScopes: $includeScopes includeCommitId: $includeCommitId + linkToCommits: $linkToCommits ''', )..addPackage( ''' diff --git a/packages/melos/test/workspace_config_test.dart b/packages/melos/test/workspace_config_test.dart index cb8c81e49..4ea12f603 100644 --- a/packages/melos/test/workspace_config_test.dart +++ b/packages/melos/test/workspace_config_test.dart @@ -82,13 +82,16 @@ void main() { }); group('VersionCommandConfigs', () { - test('message/repository/linkToCommits are optional', () { - // ignore: use_named_constants - const value = VersionCommandConfigs(); + test('defaults', () { + const value = VersionCommandConfigs.empty; expect(value.branch, null); expect(value.message, null); + expect(value.includeScopes, false); + expect(value.includeCommitId, null); expect(value.linkToCommits, null); + expect(value.updateGitTagRefs, false); + expect(value.workspaceChangelog, false); }); group('fromYaml', () { @@ -99,6 +102,13 @@ void main() { ); }); + test('throws if branch is not a string', () { + expect( + () => VersionCommandConfigs.fromYaml(const {'branch': 42}), + throwsMelosConfigException(), + ); + }); + test('throws if message is not a string', () { expect( () => VersionCommandConfigs.fromYaml(const {'message': 42}), @@ -106,9 +116,9 @@ void main() { ); }); - test('throws if branch is not a string', () { + test('throws if includeScopes is not a bool', () { expect( - () => VersionCommandConfigs.fromYaml(const {'branch': 42}), + () => VersionCommandConfigs.fromYaml(const {'includeScopes': 42}), throwsMelosConfigException(), ); }); @@ -126,13 +136,21 @@ void main() { const { 'branch': 'branch', 'message': 'message', + 'includeScopes': true, + 'includeCommitId': true, 'linkToCommits': true, + 'updateGitTagRefs': true, + 'workspaceChangelog': true, }, ), const VersionCommandConfigs( branch: 'branch', message: 'message', + includeScopes: true, + includeCommitId: true, linkToCommits: true, + updateGitTagRefs: true, + workspaceChangelog: true, ), ); });