Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: checkbox based releases #77

Merged
merged 2 commits into from
May 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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