Skip to content

Commit

Permalink
feat: checkbox based releases (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed May 10, 2019
1 parent 51b60f5 commit 1e4193c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/bin/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'use strict';

import {MintRelease, MintReleaseOptions} from '../mint-release';
import {CandidateIssue, CandidateIssueOptions} from '../candidate-issue';
import {CandidateIssue} from '../candidate-issue';

const yargs = require('yargs');

Expand All @@ -33,7 +33,7 @@ yargs
.command(
'candidate-issue',
'create an issue that\'s an example of the next release', () => {},
async (argv: CandidateIssueOptions) => {
async (argv: MintReleaseOptions) => {
const ci = new CandidateIssue(argv);
await ci.run();
})
Expand Down
63 changes: 46 additions & 17 deletions src/candidate-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,41 @@
* limitations under the License.
*/

import {IssuesListResponseItem} from '@octokit/rest';
import * as semver from 'semver';

import {checkpoint, CheckpointType} from './checkpoint';
import {ConventionalCommits} from './conventional-commits';
import {GitHub, GitHubTag} from './github';
import {ReleaseCandidate, ReleaseType} from './mint-release';
import {MintRelease, MintReleaseOptions} from './mint-release';

const parseGithubRepoUrl = require('parse-github-repo-url');

export interface CandidateIssueOptions {
bumpMinorPreMajor?: boolean;
token?: string;
repoUrl: string;
packageName: string;
releaseType: ReleaseType;
}

const ISSUE_TITLE = 'chore(release): proposal for next release';
const CHECKBOX = '* [ ] **Should I create this release for you :robot:?**';
const CHECK_REGEX = /\[x]/;

export class CandidateIssue {
bumpMinorPreMajor?: boolean;
label: string;
gh: GitHub;
bumpMinorPreMajor?: boolean;
repoUrl: string;
token: string|undefined;
packageName: string;
releaseType: ReleaseType;

constructor(options: CandidateIssueOptions) {
constructor(options: MintReleaseOptions) {
this.bumpMinorPreMajor = options.bumpMinorPreMajor || false;
this.label = options.label;
this.repoUrl = options.repoUrl;
this.token = options.token;
this.packageName = options.packageName;
this.releaseType = options.releaseType;

this.gh = this.gitHubInstance();
}

async run() {
switch (this.releaseType) {
case ReleaseType.Node:
Expand Down Expand Up @@ -78,7 +77,37 @@ export class CandidateIssue {
previousTag: candidate.previousTag
});

await this.gh.openIssue(ISSUE_TITLE, this.bodyTemplate(changelogEntry));
const issue: IssuesListResponseItem|undefined =
await this.gh.findExistingReleaseIssue(ISSUE_TITLE);
let body: string = this.bodyTemplate(changelogEntry);

if (issue) {
if (CHECK_REGEX.test(issue.body)) {
// if the checkox has been clicked for a release
// mint the release.
checkpoint(
`release checkbox was checked, creating release`,
CheckpointType.Success);
const mr = new MintRelease({
bumpMinorPreMajor: this.bumpMinorPreMajor,
label: this.label,
token: this.token,
repoUrl: this.repoUrl,
packageName: this.packageName,
releaseType: this.releaseType
});
const prNumber = await mr.run();
body = body.replace(CHECKBOX, `**release created at #${prNumber}**`);
} else if (issue.body === body) {
// don't update the issue if the content is the same for the release.
checkpoint(
`skipping update to #${issue.number}, no change to body`,
CheckpointType.Failure);
return;
}
}

await this.gh.openIssue(ISSUE_TITLE, body, issue);
}

private async coerceReleaseCandidate(
Expand Down Expand Up @@ -115,16 +144,16 @@ export class CandidateIssue {
}

private bodyTemplate(changelogEntry: string): string {
return `_:robot: This issue was created by robots! :robot:._
Its purpose is to show you what the next release of **${
this.packageName}** would look like... _If we published it right now._
If you're a maintainer, and would like create a PR for this release, simply comment on this issue.
return `_:robot: Here's what the next release of **${
this.packageName}** would look like._
---
${changelogEntry}
---
${CHECKBOX}
`;
}
}
33 changes: 13 additions & 20 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface GitHubTag {
interface GitHubPR {
branch: string;
version: string;
title: string;
body: string;
sha: string;
updates: Update[];
}
Expand Down Expand Up @@ -125,17 +127,8 @@ export class GitHub {
});
}

async openIssue(title: string, body: string) {
const issue: IssuesListResponseItem|undefined =
await this.findExistingReleaseIssue(title);
async openIssue(title: string, body: string, issue?: IssuesListResponseItem) {
if (issue) {
// don't update the issue if the content is the same for the release.
if (issue.body === body) {
checkpoint(
`skipping update to #${issue.number}, no change to body`,
CheckpointType.Failure);
return;
}
checkpoint(`updating issue #${issue.number}`, CheckpointType.Success);
this.octokit.issues.update({
owner: this.owner,
Expand All @@ -150,20 +143,19 @@ export class GitHub {
}
}

async findExistingReleaseIssue(title: string):
async findExistingReleaseIssue(title: string, perPage = 100):
Promise<IssuesListResponseItem|undefined> {
let paged = 0;
const MAX_PAGED_ISSUES =
256; // why 256? seemed like it won't be the first page.
const paged = 0;
try {
for await (const response of this.octokit.paginate.iterator(
{method: 'GET', url: `/repos/${this.owner}/${this.repo}/issues`})) {
for await (const response of this.octokit.paginate.iterator({
method: 'GET',
url: `/repos/${this.owner}/${this.repo}/issues?per_page=${perPage}`
})) {
for (let i = 0, issue; response.data[i] !== undefined; i++) {
const issue: IssuesListResponseItem = response.data[i];
if (issue.title.indexOf(title) !== -1 && issue.state === 'open') {
return issue;
}
if ((paged++) > MAX_PAGED_ISSUES) return undefined;
}
}
} catch (err) {
Expand Down Expand Up @@ -230,14 +222,15 @@ export class GitHub {

await this.updateFiles(options.updates, options.branch, refName);

const title = `chore: release ${options.version}`;
checkpoint(
`open pull-request: ${chalk.yellow(title)}`, CheckpointType.Success);
`open pull-request: ${chalk.yellow(options.title)}`,
CheckpointType.Success);
const resp: Response<PullsCreateResponse> =
await this.octokit.pulls.create({
owner: this.owner,
repo: this.repo,
title,
title: options.title,
body: options.body,
head: options.branch,
base: 'master'
});
Expand Down
16 changes: 11 additions & 5 deletions src/mint-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,15 @@ export class MintRelease {

this.gh = this.gitHubInstance();
}
async run() {
async run(): Promise<number> {
switch (this.releaseType) {
case ReleaseType.Node:
await this.nodeRelease();
break;
return await this.nodeRelease();
default:
throw Error('unknown release type');
}
}
private async nodeRelease() {
private async nodeRelease(): Promise<number> {
const latestTag: GitHubTag|undefined = await this.gh.latestTag();
const commits: string[] =
await this.commits(latestTag ? latestTag.sha : undefined);
Expand Down Expand Up @@ -117,13 +116,20 @@ export class MintRelease {
}));

const sha = this.shaFromCommits(commits);
const title = `chore: release ${candidate.version}`;
const body =
`:robot: I have created a release \\*beep\\* \\*boop\\* \n---\n${
changelogEntry}`;
const pr: number = await this.gh.openPR({
branch: `release-v${candidate.version}`,
version: candidate.version,
sha,
updates
updates,
title,
body
});
await this.gh.addLabel(pr, this.label);
return pr;
}
private async coerceReleaseCandidate(
cc: ConventionalCommits,
Expand Down
2 changes: 2 additions & 0 deletions system-test/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ describe('GitHub', () => {
branch: 'greenkeeper/@types/node-10.10.0',
sha: 'abc123',
version: '1.3.0',
title: 'version 1.3.0',
body: 'my PR body',
updates: []
})
.catch(err => {
Expand Down

0 comments on commit 1e4193c

Please sign in to comment.