Skip to content

Commit

Permalink
feat: introduce detect-checked command, a quick check for whether a r…
Browse files Browse the repository at this point in the history
…elease has been requested (#93)
  • Loading branch information
bcoe authored May 16, 2019
1 parent fe4cd4f commit d835335
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 44 deletions.
16 changes: 15 additions & 1 deletion .github/main.workflow
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
workflow "Candidate Issue" {
on = "schedule(*/5 * * * *)"
on = "schedule(*/8 * * * *)"
resolves = ["candidate-issue"]
}

Expand All @@ -11,3 +11,17 @@ action "candidate-issue" {
}
secrets = ["GITHUB_TOKEN"]
}

workflow "Detect Checked" {
on = "schedule(*/4 * * * *)"
resolves = ["detect-checked"]
}

action "detect-checked" {
uses = "googleapis/release-please/.github/action/release-please@master"
env = {
PACKAGE_NAME = "release-please"
RELEASE_PLEASE_COMMAND = "detect-checked"
}
secrets = ["GITHUB_TOKEN"]
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 67 additions & 10 deletions src/bin/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const yargs = require('yargs');

interface YargsOptions {
describe: string;
demand: boolean;
demand?: boolean;
default?: string;
}

interface YargsOptionsBuilder {
Expand All @@ -47,11 +48,47 @@ yargs
.option('repo-url', {
describe: 'GitHub URL to generate release for',
demand: true
})
.option('label', {
default: 'autorelease: pending',
describe:
'label that will be added to PR created from candidate issue'
})
.option('issue-label', {
default: 'release-candidate,type: process',
describe: 'label(s) to add to candidate issue'
});
},
async (argv: ReleasePROptions) => {
const ci = new CandidateIssue(argv);
await ci.updateOrCreateIssue();
})
.command(
'detect-checked',
'has the release checkbox been checked on candidate issue? if so create a PR',
(yargs: YargsOptionsBuilder) => {
yargs
.option('package-name', {
describe: 'name of package release is being minted for',
demand: true
})
.option('repo-url', {
describe: 'GitHub URL to generate release for',
demand: true
})
.option('label', {
default: 'autorelease: pending',
describe:
'label that will be added to PR created from candidate issue'
})
.option('issue-label', {
default: 'release-candidate,type: process',
describe: 'label(s) to add to candidate issue'
});
},
async (argv: ReleasePROptions) => {
const ci = new CandidateIssue(argv);
await ci.run();
await ci.detectChecked();
})
.command(
'release-pr', 'create a new release PR from a candidate issue',
Expand All @@ -64,6 +101,10 @@ yargs
.option('repo-url', {
describe: 'GitHub URL to generate release for',
demand: true
})
.option('label', {
default: 'autorelease: pending',
describe: 'label(s) to add to generated PR'
});
},
async (argv: ReleasePROptions) => {
Expand All @@ -73,9 +114,15 @@ yargs
.command(
'github-release', 'create a GitHub release from am release PR',
(yargs: YargsOptionsBuilder) => {
yargs.option(
'repo-url',
{describe: 'GitHub URL to generate release for', demand: true});
yargs
.option('repo-url', {
describe: 'GitHub URL to generate release for',
demand: true
})
.option('label', {
default: 'autorelease: pending',
describe: 'label to remove from release PR'
});
},
async (argv: GitHubReleaseOptions) => {
const gr = new GitHubRelease(argv);
Expand All @@ -94,7 +141,7 @@ yargs
console.info(chalk.green(
'----- put the content below in .github/main.workflow -----'));
console.info(`workflow "Candidate Issue" {
on = "schedule(*/5 * * * *)"
on = "schedule(*/8 * * * *)"
resolves = ["candidate-issue"]
}
Expand All @@ -107,6 +154,20 @@ action "candidate-issue" {
secrets = ["GITHUB_TOKEN"]
}
workflow "Detect Checked" {
on = "schedule(*/4 * * * *)"
resolves = ["detect-checked"]
}
action "detect-checked" {
uses = "googleapis/release-please/.github/action/release-please@master"
env = {
PACKAGE_NAME = "${argv.packageName}"
RELEASE_PLEASE_COMMAND = "detect-checked"
}
secrets = ["GITHUB_TOKEN"]
}
workflow "GitHub Release" {
on = "push"
resolves = ["github-release"]
Expand Down Expand Up @@ -139,10 +200,6 @@ action "github-release" {
default: false,
type: 'boolean'
})
.option('label', {
default: 'autorelease: pending',
describe: 'label to add to generated PR'
})
.demandCommand(1)
.strict(true)
.parse();
46 changes: 37 additions & 9 deletions src/candidate-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,25 @@ const CHECKBOX = '* [ ] **Should I create this release for you :robot:?**';
const CHECK_REGEX = /\[x]/;

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

constructor(options: ReleasePROptions) {
this.bumpMinorPreMajor = options.bumpMinorPreMajor || false;
this.label = options.label;
this.releaseLabel = options.label;
// labels to apply to the candidate issue being
// created or updated.
if (options.issueLabel) {
this.issueLabels = options.issueLabel.split(',');
} else {
this.issueLabels = [];
}
this.repoUrl = options.repoUrl;
this.token = options.token;
this.packageName = options.packageName;
Expand All @@ -50,17 +58,36 @@ export class CandidateIssue {
this.gh = this.gitHubInstance();
}

async run() {
async detectChecked() {
const issue: IssuesListResponseItem|undefined =
await this.gh.findExistingReleaseIssue(
ISSUE_TITLE, this.issueLabels.join(','));
if (issue) {
checkpoint(
`release candidate #${issue.number} found`, CheckpointType.Success);
if (CHECK_REGEX.test(issue.body)) {
checkpoint('release checkbox was checked', CheckpointType.Success);
await this.updateOrCreateIssue(issue);
} else {
checkpoint(
`candidate #${issue.number} not checked`, CheckpointType.Failure);
}
} else {
checkpoint(`no release candidate found`, CheckpointType.Failure);
}
}

async updateOrCreateIssue(issue?: IssuesListResponseItem) {
switch (this.releaseType) {
case ReleaseType.Node:
await this.nodeReleaseCandidate();
await this.nodeReleaseCandidate(issue);
break;
default:
throw Error('unknown release type');
}
}

private async nodeReleaseCandidate() {
private async nodeReleaseCandidate(issue?: IssuesListResponseItem) {
const latestTag: GitHubTag|undefined = await this.gh.latestTag();
const commits: string[] =
await this.commits(latestTag ? latestTag.sha : undefined);
Expand All @@ -78,8 +105,9 @@ export class CandidateIssue {
previousTag: candidate.previousTag
});

const issue: IssuesListResponseItem|undefined =
await this.gh.findExistingReleaseIssue(ISSUE_TITLE);
issue = issue ||
await this.gh.findExistingReleaseIssue(
ISSUE_TITLE, this.issueLabels.join(','));
let body: string =
CandidateIssue.bodyTemplate(changelogEntry, this.packageName);

Expand All @@ -92,7 +120,7 @@ export class CandidateIssue {
CheckpointType.Success);
const rp = new ReleasePR({
bumpMinorPreMajor: this.bumpMinorPreMajor,
label: this.label,
label: this.releaseLabel,
token: this.token,
repoUrl: this.repoUrl,
packageName: this.packageName,
Expand All @@ -111,7 +139,7 @@ export class CandidateIssue {
}
}

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

private async coerceReleaseCandidate(
Expand Down
26 changes: 13 additions & 13 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,42 +152,42 @@ export class GitHub {
return tags;
}

async addLabel(pr: number, label: string) {
async addLabels(pr: number, labels: string[]) {
checkpoint(
`adding label ${chalk.green(label)} to https://github.com/${
`adding label ${chalk.green(labels.join(','))} to https://github.com/${
this.owner}/${this.repo}/pull/${pr}`,
CheckpointType.Success);
await this.octokit.issues.addLabels({
owner: this.owner,
repo: this.repo,
issue_number: pr,
labels: [label]
});
await this.octokit.issues.addLabels(
{owner: this.owner, repo: this.repo, issue_number: pr, labels});
}

async openIssue(title: string, body: string, issue?: IssuesListResponseItem) {
async openIssue(
title: string, body: string, labels: string[],
issue?: IssuesListResponseItem) {
if (issue) {
checkpoint(`updating issue #${issue.number}`, CheckpointType.Success);
this.octokit.issues.update({
owner: this.owner,
repo: this.repo,
body,
issue_number: issue.number
issue_number: issue.number,
labels
});
} else {
checkpoint(`creating new release proposal issue`, CheckpointType.Success);
this.octokit.issues.create(
{owner: this.owner, repo: this.repo, title, body});
{owner: this.owner, repo: this.repo, title, body, labels});
}
}

async findExistingReleaseIssue(title: string, perPage = 100):
async findExistingReleaseIssue(title: string, label: string, perPage = 100):
Promise<IssuesListResponseItem|undefined> {
const paged = 0;
try {
for await (const response of this.octokit.paginate.iterator({
method: 'GET',
url: `/repos/${this.owner}/${this.repo}/issues?per_page=${perPage}`
url: `/repos/${this.owner}/${this.repo}/issues?per_page=${
perPage}&labels=${label}`
})) {
for (let i = 0, issue; response.data[i] !== undefined; i++) {
const issue: IssuesListResponseItem = response.data[i];
Expand Down
3 changes: 2 additions & 1 deletion src/release-pr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum ReleaseType {
export interface ReleasePROptions {
bumpMinorPreMajor?: boolean;
label: string;
issueLabel?: string;
token?: string;
repoUrl: string;
packageName: string;
Expand Down Expand Up @@ -128,7 +129,7 @@ export class ReleasePR {
title,
body
});
await this.gh.addLabel(pr, this.label);
await this.gh.addLabels(pr, [this.label]);
return pr;
}
private async coerceReleaseCandidate(
Expand Down
20 changes: 11 additions & 9 deletions system-test/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,17 @@ describe('GitHub', () => {
describe('findExistingReleaseIssue', () => {
it('returns an open issue matching the title provided', async () => {
const gh = new GitHub({owner: 'bcoe', repo: 'node-25650-bug'});
const issue =
await nockBack('find-matching-issue.json')
.then((nbr: NockBackResponse) => {
return gh.findExistingReleaseIssue('this issue is a fixture')
.then((res) => {
nbr.nockDone();
return res;
});
});
const issue = await nockBack('find-matching-issue.json')
.then((nbr: NockBackResponse) => {
return gh
.findExistingReleaseIssue(
'this issue is a fixture',
'type: process,release-candidate')
.then((res) => {
nbr.nockDone();
return res;
});
});
issue.number.should.be.gt(0);
});
});
Expand Down

0 comments on commit d835335

Please sign in to comment.