Skip to content

Commit

Permalink
feat: add GitHub changelog notes generator (#1120)
Browse files Browse the repository at this point in the history
Fixes #1106 

Allows configuration in the manifest config as `changelog-type: 'github'` or via the CLI `--changelog-type=github`.
  • Loading branch information
chingor13 committed Nov 29, 2021
1 parent 8f6e52b commit 1470661
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 13 deletions.
2 changes: 2 additions & 0 deletions __snapshots__/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ Options:
[choices: "default", "always-bump-patch", "service-pack"] [default: "default"]
--changelog-path where can the CHANGELOG be found in the
project? [string] [default: "CHANGELOG.md"]
--changelog-type type of changelog to build
[choices: "default", "github"]
--last-package-version last version # that package was released as
[deprecated: use --latest-tag-version instead] [string]
--latest-tag-version Override the detected latest tag version
Expand Down
11 changes: 11 additions & 0 deletions __snapshots__/github.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"@octokit/graphql": "^4.3.1",
"@octokit/request": "^5.6.0",
"@octokit/request-error": "^2.1.0",
"@octokit/rest": "^18.6.6",
"@octokit/rest": "^18.12.0",
"@types/npm-package-arg": "^6.1.0",
"chalk": "^4.0.0",
"code-suggester": "^2.0.0",
Expand Down
11 changes: 10 additions & 1 deletion src/bin/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
ReleaseType,
VersioningStrategyType,
getVersioningStrategyTypes,
ChangelogNotesType,
getChangelogTypes,
} from '../factory';
import {Bootstrapper} from '../bootstrapper';

Expand Down Expand Up @@ -113,7 +115,9 @@ interface CreatePullRequestArgs
VersioningArgs,
PullRequestArgs,
PullRequestStrategyArgs,
TaggingArgs {}
TaggingArgs {
changelogType?: ChangelogNotesType;
}
interface CreateReleaseArgs
extends GitHubArgs,
ManifestArgs,
Expand Down Expand Up @@ -273,6 +277,10 @@ function pullRequestStrategyOptions(yargs: yargs.Argv): yargs.Argv {
describe: 'where can the CHANGELOG be found in the project?',
type: 'string',
})
.option('changelog-type', {
describe: 'type of changelog to build',
choices: getChangelogTypes(),
})
.option('last-package-version', {
describe: 'last version # that package was released as',
type: 'string',
Expand Down Expand Up @@ -390,6 +398,7 @@ const createReleasePullRequestCommand: yargs.CommandModule<
bumpMinorPreMajor: argv.bumpMinorPreMajor,
bumpPatchForMinorPreMajor: argv.bumpPatchForMinorPreMajor,
changelogPath: argv.changelogPath,
changelogType: argv.changelogType,
changelogSections: argv.changelogSections,
releaseAs: argv.releaseAs,
versioning: argv.versioningStrategy,
Expand Down
2 changes: 2 additions & 0 deletions src/changelog-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface BuildNotesOptions {
version: string;
previousTag?: string;
currentTag: string;
targetBranch: string;
changelogSections?: ChangelogSection[];
}

export interface ChangelogNotes {
Expand Down
7 changes: 2 additions & 5 deletions src/changelog-notes/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,18 @@ const presetFactory = require('conventional-changelog-conventionalcommits');
const DEFAULT_HOST = 'https://github.com';

interface DefaultChangelogNotesOptions {
changelogSections?: ChangelogSection[];
commitPartial?: string;
headerPartial?: string;
mainTemplate?: string;
}

export class DefaultChangelogNotes implements ChangelogNotes {
// allow for customized commit template.
private changelogSections?: ChangelogSection[];
private commitPartial?: string;
private headerPartial?: string;
private mainTemplate?: string;

constructor(options: DefaultChangelogNotesOptions = {}) {
this.changelogSections = options.changelogSections;
this.commitPartial = options.commitPartial;
this.headerPartial = options.headerPartial;
this.mainTemplate = options.mainTemplate;
Expand All @@ -61,8 +58,8 @@ export class DefaultChangelogNotes implements ChangelogNotes {
};

const config: {[key: string]: ChangelogSection[]} = {};
if (this.changelogSections) {
config.types = this.changelogSections;
if (options.changelogSections) {
config.types = options.changelogSections;
}
const preset = await presetFactory(config);
preset.writerOpts.commitPartial =
Expand Down
34 changes: 34 additions & 0 deletions src/changelog-notes/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {ChangelogNotes, BuildNotesOptions} from '../changelog-notes';
import {ConventionalCommit} from '../commit';
import {GitHub} from '../github';

export class GitHubChangelogNotes implements ChangelogNotes {
private github: GitHub;
constructor(github: GitHub) {
this.github = github;
}
async buildNotes(
_commits: ConventionalCommit[],
options: BuildNotesOptions
): Promise<string> {
return await this.github.generateReleaseNotes(
options.currentTag,
options.targetBranch,
options.previousTag
);
}
}
39 changes: 38 additions & 1 deletion src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ import {DependencyManifest} from './versioning-strategies/dependency-manifest';
import {ManifestPlugin} from './plugin';
import {NodeWorkspace} from './plugins/node-workspace';
import {CargoWorkspace} from './plugins/cargo-workspace';
import {ChangelogNotes, ChangelogSection} from './changelog-notes';
import {GitHubChangelogNotes} from './changelog-notes/github';
import {DefaultChangelogNotes} from './changelog-notes/default';

// Factory shared by GitHub Action and CLI for creating Release PRs
// and GitHub Releases:
Expand Down Expand Up @@ -95,6 +98,10 @@ export function getVersioningStrategyTypes(): readonly VersioningStrategyType[]
return allVersioningTypes;
}

export function getChangelogTypes(): readonly ChangelogNotesType[] {
return allChangelogNotesTypes;
}

export interface StrategyFactoryOptions extends ReleaserConfig {
github: GitHub;
path?: string;
Expand All @@ -111,7 +118,12 @@ export async function buildStrategy(
bumpMinorPreMajor: options.bumpMinorPreMajor,
bumpPatchForMinorPreMajor: options.bumpPatchForMinorPreMajor,
});
const strategyOptions: StrategyOptions = {
const changelogNotes = buildChangelogNotes({
type: options.changelogType || 'default',
github: options.github,
changelogSections: options.changelogSections,
});
const strategyOptions = {
github: options.github,
targetBranch,
path: options.path,
Expand All @@ -125,6 +137,7 @@ export async function buildStrategy(
skipGitHubRelease: options.skipGithubRelease,
releaseAs: options.releaseAs,
includeComponentInTag: options.includeComponentInTag,
changelogNotes,
pullRequestTitlePattern: options.pullRequestTitlePattern,
};
switch (options.releaseType) {
Expand Down Expand Up @@ -237,3 +250,27 @@ export function buildPlugin(options: PluginFactoryOptions): ManifestPlugin {
throw new Error(`Unknown plugin type: ${options.type}`);
}
}

const allChangelogNotesTypes = ['default', 'github'] as const;
export type ChangelogNotesType = typeof allChangelogNotesTypes[number];
interface ChangelogNotesFactoryOptions {
type: ChangelogNotesType;
github: GitHub;
changelogSections?: ChangelogSection[];
commitPartial?: string;
headerPartial?: string;
mainTemplate?: string;
}

export function buildChangelogNotes(
options: ChangelogNotesFactoryOptions
): ChangelogNotes {
switch (options.type) {
case 'github':
return new GitHubChangelogNotes(options.github);
case 'default':
return new DefaultChangelogNotes(options);
default:
throw new Error(`Unknown changelog type: ${options.type}`);
}
}
21 changes: 21 additions & 0 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,27 @@ export class GitHub {
});
}
);

/**
* Generate release notes from GitHub at tag
* @param {string} tagName Name of new release tag
* @param {string} targetCommitish Target commitish for new tag
* @param {string} previousTag Optional. Name of previous tag to analyze commits since
*/
async generateReleaseNotes(
tagName: string,
targetCommitish: string,
previousTag?: string
): Promise<string> {
const resp = await this.octokit.repos.generateReleaseNotes({
owner: this.repository.owner,
repo: this.repository.repo,
tag_name: tagName,
previous_tag_name: previousTag,
target_commitish: targetCommitish,
});
return resp.data.body;
}
}

// Takes a potentially unqualified branch name, and turns it
Expand Down
10 changes: 8 additions & 2 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
ReleaseType,
VersioningStrategyType,
buildPlugin,
ChangelogNotesType,
} from './factory';
import {Release} from './release';
import {Strategy} from './strategy';
Expand All @@ -48,8 +49,6 @@ export interface ReleaserConfig {
bumpPatchForMinorPreMajor?: boolean;

// Strategy options
changelogSections?: ChangelogSection[];
changelogPath?: string;
releaseAs?: string;
skipGithubRelease?: boolean;
draft?: boolean;
Expand All @@ -59,6 +58,11 @@ export interface ReleaserConfig {
includeComponentInTag?: boolean;
pullRequestTitlePattern?: string;

// Changelog options
changelogSections?: ChangelogSection[];
changelogPath?: string;
changelogType?: ChangelogNotesType;

// Ruby-only
versionFile?: string;
// Java-only
Expand Down Expand Up @@ -89,6 +93,7 @@ interface ReleaserConfigJson {
label?: string;
'release-label'?: string;
'include-component-in-tag'?: boolean;
'changelog-type'?: ChangelogNotesType;
'pull-request-title-pattern'?: string;

// Ruby-only
Expand Down Expand Up @@ -828,6 +833,7 @@ function extractReleaserConfig(config: ReleaserPackageConfig): ReleaserConfig {
versionFile: config['version-file'],
extraFiles: config['extra-files'],
includeComponentInTag: config['include-component-in-tag'],
changelogType: config['changelog-type'],
pullRequestTitlePattern: config['pull-request-title-pattern'],
};
}
Expand Down
1 change: 1 addition & 0 deletions src/strategies/php-yoshi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export class PHPYoshi extends Strategy {
version: newVersion.toString(),
previousTag: latestRelease?.tag?.toString(),
currentTag: newVersionTag.toString(),
targetBranch: this.targetBranch,
}
);
releaseNotesBody = updatePHPChangelogEntry(
Expand Down
4 changes: 3 additions & 1 deletion src/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export abstract class Strategy {
private includeComponentInTag: boolean;
private pullRequestTitlePattern?: string;

protected changelogNotes: ChangelogNotes;
readonly changelogNotes: ChangelogNotes;

// CHANGELOG configuration
protected changelogSections?: ChangelogSection[];
Expand Down Expand Up @@ -162,6 +162,8 @@ export abstract class Strategy {
version: newVersion.toString(),
previousTag: latestRelease?.tag?.toString(),
currentTag: newVersionTag.toString(),
targetBranch: this.targetBranch,
changelogSections: this.changelogSections,
});
}

Expand Down
6 changes: 4 additions & 2 deletions test/changelog-notes/default-changelog-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('DefaultChangelogNotes', () => {
version: '1.2.3',
previousTag: 'v1.2.2',
currentTag: 'v1.2.3',
targetBranch: 'main',
};
it('should build default release notes', async () => {
const changelogNotes = new DefaultChangelogNotes();
Expand All @@ -73,14 +74,15 @@ describe('DefaultChangelogNotes', () => {
safeSnapshot(notes);
});
it('should build with custom changelog sections', async () => {
const changelogNotes = new DefaultChangelogNotes({
const changelogNotes = new DefaultChangelogNotes();
const notes = await changelogNotes.buildNotes(commits, {
...notesOptions,
changelogSections: [
{type: 'feat', section: 'Features'},
{type: 'fix', section: 'Bug Fixes'},
{type: 'docs', section: 'Documentation'},
],
});
const notes = await changelogNotes.buildNotes(commits, notesOptions);
expect(notes).to.is.string;
safeSnapshot(notes);
});
Expand Down
24 changes: 24 additions & 0 deletions test/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,30 @@ describe('CLI', () => {
sinon.assert.calledOnce(createPullRequestsStub);
});

it('handles --changelog-type', async () => {
await parser.parseAsync(
'release-pr --repo-url=googleapis/release-please-cli --release-type=java-yoshi --changelog-type=github'
);

sinon.assert.calledOnceWithExactly(gitHubCreateStub, {
owner: 'googleapis',
repo: 'release-please-cli',
token: undefined,
});
sinon.assert.calledOnceWithExactly(
fromConfigStub,
fakeGitHub,
'main',
sinon.match({
releaseType: 'java-yoshi',
changelogType: 'github',
}),
sinon.match.any,
undefined
);
sinon.assert.calledOnce(createPullRequestsStub);
});

it('handles --draft-pull-request', async () => {
await parser.parseAsync(
'release-pr --repo-url=googleapis/release-please-cli --release-type=java-yoshi --draft-pull-request'
Expand Down
12 changes: 12 additions & 0 deletions test/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {JavaYoshi} from '../src/strategies/java-yoshi';
import {JavaSnapshot} from '../src/versioning-strategies/java-snapshot';
import {ServicePackVersioningStrategy} from '../src/versioning-strategies/service-pack';
import {DependencyManifest} from '../src/versioning-strategies/dependency-manifest';
import {GitHubChangelogNotes} from '../src/changelog-notes/github';
import {DefaultChangelogNotes} from '../src/changelog-notes/default';

describe('factory', () => {
let github: GitHub;
Expand All @@ -49,6 +51,7 @@ describe('factory', () => {
expect(versioningStrategy.bumpPatchForMinorPreMajor).to.be.false;
expect(strategy.path).to.eql('.');
expect(strategy.component).not.ok;
expect(strategy.changelogNotes).instanceof(DefaultChangelogNotes);
});
it('should build a with configuration', async () => {
const strategy = await buildStrategy({
Expand Down Expand Up @@ -84,6 +87,15 @@ describe('factory', () => {
ServicePackVersioningStrategy
);
});
it('should build with a configured changelog type', async () => {
const strategy = await buildStrategy({
github,
releaseType: 'simple',
changelogType: 'github',
});
expect(strategy).instanceof(Simple);
expect(strategy.changelogNotes).instanceof(GitHubChangelogNotes);
});
it('should build a ruby strategy', async () => {
const strategy = await buildStrategy({
github,
Expand Down

0 comments on commit 1470661

Please sign in to comment.