diff --git a/__snapshots__/default-changelog-notes.js b/__snapshots__/default-changelog-notes.js index 0ebca5644..dea5dd0d6 100644 --- a/__snapshots__/default-changelog-notes.js +++ b/__snapshots__/default-changelog-notes.js @@ -218,6 +218,33 @@ exports['DefaultChangelogNotes buildNotes with commit parsing should handle mult * upgrade to Node 7 ([8916be7](https://github.com/googleapis/java-asset/commit/8916be74596394c27516696b957fd0d7)) ` +exports['DefaultChangelogNotes buildNotes with commit parsing should include commit authors when enabled with username 1'] = ` +## [1.2.3](https://github.com/googleapis/java-asset/compare/v1.2.2...v1.2.3) (1983-10-10) + + +### Features + +* some feature ([@testuser](https://github.com/testuser)) ([sha1](https://github.com/googleapis/java-asset/commit/sha1)) +` + +exports['DefaultChangelogNotes buildNotes with commit parsing should include commit authors when enabled without username 1'] = ` +## [1.2.3](https://github.com/googleapis/java-asset/compare/v1.2.2...v1.2.3) (1983-10-10) + + +### Features + +* some feature (Test User) ([sha1](https://github.com/googleapis/java-asset/commit/sha1)) +` + +exports['DefaultChangelogNotes buildNotes with commit parsing should not include commit authors when disabled 1'] = ` +## [1.2.3](https://github.com/googleapis/java-asset/compare/v1.2.2...v1.2.3) (1983-10-10) + + +### Features + +* some feature ([sha1](https://github.com/googleapis/java-asset/commit/sha1)) +` + exports['DefaultChangelogNotes buildNotes with commit parsing should not include content two newlines after BREAKING CHANGE 1'] = ` ## [1.2.3](https://github.com/googleapis/java-asset/compare/v1.2.2...v1.2.3) (1983-10-10) diff --git a/docs/cli.md b/docs/cli.md index f688d41db..733c18e9a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -61,6 +61,7 @@ Extra options: | `--changelog-type` | [`ChangelogType`](/docs/customizing.md#changelog-types) | Strategy for building the changelog contents. Defaults to `default` | | `--changelog-sections` | `string` | Comma-separated list of commit scopes to show in changelog headings | | `--changelog-host` | `string` | Host for commit hyperlinks in the changelog. Defaults to `https://github.com` | +| `--include-commit-authors` | `boolean` | Include commit authors in changelog entries (e.g., `(@username)`). Defaults to `false` | | `--pull-request-title-pattern` | `string` | Override the pull request title pattern. Defaults to `chore${scope}: release${component} ${version}` | | `--pull-request-header` | `string` | Override the pull request header. Defaults to `:robot: I have created a release *beep* *boop*` | | `--pull-request-footer` | `string` | Override the pull request footer. Defaults to `This PR was generated with Release Please. See documentation.` | @@ -111,6 +112,7 @@ need to specify your release options: | `--changelog-type` | [`ChangelogType`](/docs/customizing.md#changelog-types) | Strategy for building the changelog contents. Defaults to `default` | | `--changelog-sections` | `string` | Comma-separated list of commit scopes to show in changelog headings | | `--changelog-host` | `string` | Host for commit hyperlinks in the changelog. Defaults to `https://github.com` | +| `--include-commit-authors` | `boolean` | Include commit authors in changelog entries (e.g., `(@username)`). Defaults to `false` | | `--monorepo-tags` | boolean | Add prefix to tags and branches, allowing multiple libraries to be released from the same repository | | `--pull-request-title-pattern` | `string` | Override the pull request title pattern. Defaults to `chore${scope}: release${component} ${version}` | | `--pull-request-header` | `string` | Override the pull request header. Defaults to `:robot: I have created a release *beep* *boop*` | diff --git a/docs/manifest-releaser.md b/docs/manifest-releaser.md index 9e19e1d33..5dbdd178a 100644 --- a/docs/manifest-releaser.md +++ b/docs/manifest-releaser.md @@ -189,6 +189,11 @@ defaults (those are documented in comments) // absence defaults to https://github.com "changelog-host": "https://example.com", + // include commit authors in changelog entries + // when true, appends (@username) or author name to each entry + // absence defaults to false + "include-commit-authors": true, + // when `manifest-release` creates GitHub Releases per package, create // those as "Draft" releases (which can later be manually published). // absence defaults to false and Releases are created as already Published. diff --git a/src/changelog-notes.ts b/src/changelog-notes.ts index 5d688ad76..7fefe85ed 100644 --- a/src/changelog-notes.ts +++ b/src/changelog-notes.ts @@ -24,6 +24,7 @@ export interface BuildNotesOptions { targetBranch: string; changelogSections?: ChangelogSection[]; commits?: Commit[]; + includeCommitAuthors?: boolean; } export interface ChangelogNotes { diff --git a/src/changelog-notes/default.ts b/src/changelog-notes/default.ts index e3e47f6b6..28789004d 100644 --- a/src/changelog-notes/default.ts +++ b/src/changelog-notes/default.ts @@ -84,9 +84,17 @@ export class DefaultChangelogNotes implements ChangelogNotes { context.repository ) ); + let subject = htmlEscape(commit.bareMessage); + // Append author info if enabled and author is available + if (options.includeCommitAuthors && commit.author) { + const authorDisplay = commit.author.username + ? `@${commit.author.username}` + : commit.author.name; + subject = `${subject} (${authorDisplay})`; + } return { body: '', // commit.body, - subject: htmlEscape(commit.bareMessage), + subject, type: commit.type, scope: commit.scope, notes, diff --git a/src/commit.ts b/src/commit.ts index 904b24c0f..bd0d5a800 100644 --- a/src/commit.ts +++ b/src/commit.ts @@ -25,11 +25,18 @@ import * as parser from '@conventional-commits/parser'; // eslint-disable-next-line @typescript-eslint/no-var-requires const conventionalCommitsFilter = require('conventional-commits-filter'); +export interface CommitAuthor { + name: string; + email?: string; + username?: string; +} + export interface Commit { sha: string; message: string; files?: string[]; pullRequest?: PullRequest; + author?: CommitAuthor; } export interface ConventionalCommit extends Commit { diff --git a/src/github.ts b/src/github.ts index c0bbf3668..946d84e19 100644 --- a/src/github.ts +++ b/src/github.ts @@ -88,9 +88,18 @@ interface GitHubCreateOptions { type CommitFilter = (commit: Commit) => boolean; +interface GraphQLCommitAuthor { + name?: string; + email?: string; + user?: { + login: string; + } | null; +} + interface GraphQLCommit { sha: string; message: string; + author?: GraphQLCommitAuthor; associatedPullRequests: { nodes: GraphQLPullRequest[]; }; @@ -433,6 +442,13 @@ export class GitHub { } sha: oid message + author { + name + email + user { + login + } + } } pageInfo { hasNextPage @@ -491,6 +507,13 @@ export class GitHub { const commit: Commit = { sha: graphCommit.sha, message: graphCommit.message, + author: graphCommit.author + ? { + name: graphCommit.author.name || 'Unknown', + email: graphCommit.author.email, + username: graphCommit.author.user?.login, + } + : undefined, }; const mergePullRequest = graphCommit.associatedPullRequests.nodes.find( pr => { diff --git a/src/index.ts b/src/index.ts index 31eed9f8b..150324b19 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ export { } from './manifest'; export {ReleasePullRequest} from './release-pull-request'; export {PullRequest} from './pull-request'; -export {Commit, ConventionalCommit} from './commit'; +export {Commit, CommitAuthor, ConventionalCommit} from './commit'; export {Strategy} from './strategy'; export {BaseStrategyOptions, BuildUpdatesOptions} from './strategies/base'; export { diff --git a/src/manifest.ts b/src/manifest.ts index d4d453a07..d5ac9018a 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -131,6 +131,7 @@ export interface ReleaserConfig { changelogPath?: string; changelogType?: ChangelogNotesType; changelogHost?: string; + includeCommitAuthors?: boolean; // Ruby-only versionFile?: string; @@ -176,6 +177,7 @@ interface ReleaserConfigJson { 'include-v-in-release-name'?: boolean; 'changelog-type'?: ChangelogNotesType; 'changelog-host'?: string; + 'include-commit-authors'?: boolean; 'pull-request-title-pattern'?: string; 'pull-request-header'?: string; 'pull-request-footer'?: string; @@ -1383,6 +1385,7 @@ function extractReleaserConfig( changelogSections: config['changelog-sections'], changelogPath: config['changelog-path'], changelogHost: config['changelog-host'], + includeCommitAuthors: config['include-commit-authors'], releaseAs: config['release-as'], skipGithubRelease: config['skip-github-release'], skipChangelog: config['skip-changelog'], @@ -1738,6 +1741,8 @@ function mergeReleaserConfig( changelogPath: pathConfig.changelogPath ?? defaultConfig.changelogPath, changelogHost: pathConfig.changelogHost ?? defaultConfig.changelogHost, changelogType: pathConfig.changelogType ?? defaultConfig.changelogType, + includeCommitAuthors: + pathConfig.includeCommitAuthors ?? defaultConfig.includeCommitAuthors, releaseAs: pathConfig.releaseAs ?? defaultConfig.releaseAs, skipGithubRelease: pathConfig.skipGithubRelease ?? defaultConfig.skipGithubRelease, diff --git a/src/strategies/base.ts b/src/strategies/base.ts index 952c666cc..49ef38857 100644 --- a/src/strategies/base.ts +++ b/src/strategies/base.ts @@ -88,6 +88,7 @@ export interface BaseStrategyOptions { initialVersion?: string; extraLabels?: string[]; dateFormat?: string; + includeCommitAuthors?: boolean; } /** @@ -120,6 +121,7 @@ export abstract class BaseStrategy implements Strategy { readonly extraFiles: ExtraFile[]; readonly extraLabels: string[]; protected dateFormat: string; + protected includeCommitAuthors?: boolean; readonly changelogNotes: ChangelogNotes; @@ -158,6 +160,7 @@ export abstract class BaseStrategy implements Strategy { this.initialVersion = options.initialVersion; this.extraLabels = options.extraLabels || []; this.dateFormat = options.dateFormat || DEFAULT_DATE_FORMAT; + this.includeCommitAuthors = options.includeCommitAuthors; } /** @@ -232,6 +235,7 @@ export abstract class BaseStrategy implements Strategy { targetBranch: this.targetBranch, changelogSections: this.changelogSections, commits: commits, + includeCommitAuthors: this.includeCommitAuthors, }); } diff --git a/test/changelog-notes/default-changelog-notes.ts b/test/changelog-notes/default-changelog-notes.ts index ffb9baa9d..db10273b3 100644 --- a/test/changelog-notes/default-changelog-notes.ts +++ b/test/changelog-notes/default-changelog-notes.ts @@ -290,6 +290,90 @@ describe('DefaultChangelogNotes', () => { expect(notes).to.is.string; safeSnapshot(notes); }); + it('should include commit authors when enabled with username', async () => { + const commits = [ + { + sha: 'sha1', + message: 'feat: some feature', + files: ['path1/file1.txt'], + type: 'feat', + scope: null, + bareMessage: 'some feature', + notes: [], + references: [], + breaking: false, + author: { + name: 'Test User', + email: 'test@example.com', + username: 'testuser', + }, + }, + ]; + const changelogNotes = new DefaultChangelogNotes(); + const notes = await changelogNotes.buildNotes(commits, { + ...notesOptions, + includeCommitAuthors: true, + }); + expect(notes).to.is.string; + expect(notes).to.include('@testuser'); + safeSnapshot(notes); + }); + it('should include commit authors when enabled without username', async () => { + const commits = [ + { + sha: 'sha1', + message: 'feat: some feature', + files: ['path1/file1.txt'], + type: 'feat', + scope: null, + bareMessage: 'some feature', + notes: [], + references: [], + breaking: false, + author: { + name: 'Test User', + email: 'test@example.com', + }, + }, + ]; + const changelogNotes = new DefaultChangelogNotes(); + const notes = await changelogNotes.buildNotes(commits, { + ...notesOptions, + includeCommitAuthors: true, + }); + expect(notes).to.is.string; + expect(notes).to.include('Test User'); + safeSnapshot(notes); + }); + it('should not include commit authors when disabled', async () => { + const commits = [ + { + sha: 'sha1', + message: 'feat: some feature', + files: ['path1/file1.txt'], + type: 'feat', + scope: null, + bareMessage: 'some feature', + notes: [], + references: [], + breaking: false, + author: { + name: 'Test User', + email: 'test@example.com', + username: 'testuser', + }, + }, + ]; + const changelogNotes = new DefaultChangelogNotes(); + const notes = await changelogNotes.buildNotes(commits, { + ...notesOptions, + includeCommitAuthors: false, + }); + expect(notes).to.is.string; + expect(notes).to.not.include('@testuser'); + expect(notes).to.not.include('Test User'); + safeSnapshot(notes); + }); // it('ignores reverted commits', async () => { // const commits = [buildCommitFromFixture('multiple-messages')]; // const changelogNotes = new DefaultChangelogNotes();