Skip to content

Commit

Permalink
feat: write conventional commit scopes in changelog (#341)
Browse files Browse the repository at this point in the history
Fixes #318
  • Loading branch information
blaugold authored Jul 5, 2022
1 parent dd42003 commit 0c64d61
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 53 deletions.
10 changes: 10 additions & 0 deletions docs/configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
7 changes: 7 additions & 0 deletions packages/melos/lib/src/common/changelog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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!));
}
Expand Down
65 changes: 39 additions & 26 deletions packages/melos/lib/src/workspace_configs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object?, Object?> yaml) {
final branch = assertKeyIsA<String?>(
key: 'branch',
map: yaml,
path: 'command/version',
);
final message = assertKeyIsA<String?>(
key: 'message',
map: yaml,
path: 'command/version',
);
final linkToCommits = assertKeyIsA<bool?>(
key: 'linkToCommits',
final includeScopes = assertKeyIsA<bool?>(
key: 'includeScopes',
map: yaml,
path: 'command/version',
);
Expand All @@ -293,18 +299,16 @@ class VersionCommandConfigs {
map: yaml,
path: 'command/version',
);
final branch = assertKeyIsA<String?>(
key: 'branch',
final linkToCommits = assertKeyIsA<bool?>(
key: 'linkToCommits',
map: yaml,
path: 'command/version',
);

final workspaceChangelog = assertKeyIsA<bool?>(
key: 'workspaceChangelog',
map: yaml,
path: 'command/version',
);

final updateGitTagRefs = assertKeyIsA<bool?>(
key: 'updateGitTagRefs',
map: yaml,
Expand All @@ -313,41 +317,47 @@ 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,
);
}

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<String, Object?> 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,
};
Expand All @@ -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,
)''';
}
}
Expand Down
87 changes: 65 additions & 22 deletions packages/melos/test/changelog_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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))'),
);
});
});
Expand All @@ -42,56 +79,62 @@ 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(
'''
repository: https://github.com/a/b
command:
version:
linkToCommits: $linkToCommits
includeScopes: $includeScopes
includeCommitId: $includeCommitId
linkToCommits: $linkToCommits
''',
)..addPackage(
'''
Expand Down
28 changes: 23 additions & 5 deletions packages/melos/test/workspace_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand All @@ -99,16 +102,23 @@ 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}),
throwsMelosConfigException(),
);
});

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(),
);
});
Expand All @@ -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,
),
);
});
Expand Down

0 comments on commit 0c64d61

Please sign in to comment.