From dae82ca4db41cfbdbfa91e46f300feda9089f144 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 24 Feb 2017 14:25:59 -0500 Subject: [PATCH] Write script to generate CHANGELOG.md entries (#2196) Write script to generate CHANGELOG.md entries Update PR/Issue templates to make them easier to fill out (less boilerplate to delete) --- .github/ISSUE_TEMPLATE.md | 15 ++-- .github/PULL_REQUEST_TEMPLATE.md | 10 ++- package.json | 2 + scripts/generate-changelog.ts | 149 +++++++++++++++++++++++++++++++ yarn.lock | 56 +++++++++++- 5 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 scripts/generate-changelog.ts diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 7a7d668984c..e7d5a66abca 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,26 +1,21 @@ ### Bug Report -- __TSLint version__: `` -- __TypeScript version__: `` -- __Running TSLint via__: (please choose one) CLI / Node.js API / grunt-tslint / Atom / Visual Studio / etc +- __TSLint version__: +- __TypeScript version__: +- __Running TSLint via__: (pick one) CLI / Node.js API / VSCode / grunt-tslint / Atom / Visual Studio / etc #### TypeScript code being linted ```ts -(include if relevant) +// code snippet ``` with `tslint.json` configuration: ```json -(include if relevant) + ``` #### Actual behavior -(fill this out) - #### Expected behavior - -(fill this out) - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d21f050c4e8..2547d888d82 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,10 +5,14 @@ - [ ] Includes tests - [ ] Documentation update -#### What changes did you make? +#### Overview of change: -(give an overview) #### Is there anything you'd like reviewers to focus on? -(optional) + + +#### CHANGELOG.md entry: + + + diff --git a/package.json b/package.json index 04354230821..f3d2159a643 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@types/colors": "^0.6.33", "@types/diff": "0.0.31", "@types/findup-sync": "^0.3.29", + "@types/github": "^0.0.0", "@types/glob": "^5.0.30", "@types/js-yaml": "^3.5.29", "@types/mocha": "^2.2.35", @@ -62,6 +63,7 @@ "@types/resolve": "0.0.4", "@types/update-notifier": "^1.0.0", "chai": "^3.5.0", + "github": "^8.1.1", "js-yaml": "^3.7.0", "mocha": "^3.2.0", "npm-run-all": "^3.1.0", diff --git a/scripts/generate-changelog.ts b/scripts/generate-changelog.ts new file mode 100644 index 00000000000..049f3ef9d24 --- /dev/null +++ b/scripts/generate-changelog.ts @@ -0,0 +1,149 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. + * + * 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. + */ + +/** + * Generates entries for CHANGELOG.md from pull requests + * + * Reads changelog entries from pull requests merged since the last release tag. + * Changelog entries are lines within the first PR comment that matches /^\[[a-z\-]+\]/ like `[new-rule]` and `[bugfix]` + */ + +// tslint:disable:no-console + +import GitHubApi = require("github"); +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; + +const github = new GitHubApi({ + host: "api.github.com", + protocol: "https", + timeout: 5000, +}); + +const repoInfo = { + owner: "palantir", + repo: "tslint", +}; + +const tokenFile = path.join(os.homedir(), "github_token.txt"); + +// authenticate +const auth: GitHubApi.Auth = { + token: fs.readFileSync(tokenFile, "utf8").toString().trim(), + type: "oauth", +}; +console.log("Using OAuth token " + auth.token + "\n"); + +github.authenticate(auth); + +const commits: ICommit[] = []; +github.repos.getLatestRelease(repoInfo).then((value) => { + console.log("Getting commits " + value.tag_name + "..master"); + // get the commits between the most recent release and the head of master + return github.repos.compareCommits({ + base: value.tag_name, + head: "master", + ...repoInfo, + }); +}).then((value) => { + // for each commit, get the PR, and extract changelog entries + const promises: Array> = []; + for (const commitInfo of value.commits) { + const commit: ICommit = { + fields: [], + sha: commitInfo.sha, + submitter: commitInfo.commit.author.name != null ? commitInfo.commit.author.name : commitInfo.author.login, + title: commitInfo.commit.message, + }; + commits.push(commit); + + // check for a pull request number in the commit title + const match = (commitInfo.commit.message as string).match(/\(#(\d+)\)/); + if (match && match.length > 1) { + commit.pushRequestNum = Number.parseInt(match[1], 10); + + // get the PR text + promises.push(github.issues.get({ + number: commit.pushRequestNum, + ...repoInfo, + }).then((comment) => { + // extract the changelog entries + const lines = (comment.body as string).split("\r\n"); + for (const line of lines) { + const fieldMatch = line.match(/^(\[[a-z\-]+\])/); + if (fieldMatch) { + commit.fields.push({ + tag: fieldMatch[1], + text: line + " (#" + commit.pushRequestNum + ")", + }); + } + } + })); + } + + } + return Promise.all(promises); +}).then(() => { + const entries: IField[] = []; + const noFields: string[] = []; + const contributors = new Set(); + for (const commit of commits) { + if (commit.fields.length > 0) { + for (const field of commit.fields) { + entries.push(field); + } + } else { + noFields.push(commit.title); + } + contributors.add(commit.submitter); + } + entries.sort((a, b) => { + return a.tag.localeCompare(b.tag); + }); + + console.log("\n---- formatted changelog entries: ----"); + for (const entry of entries) { + console.log("- " + entry.text); + } + + console.log("\n---- PRs with missing changelog entries: ----"); + for (const missing of noFields) { + console.log("- " + missing.replace(/[\r\n]+/, "\r\n ")); + } + + console.log("\n---- thanks ----"); + console.log("Thanks to our contributors!"); + contributors.forEach((contributor) => { + console.log("- " + contributor); + }); +}).catch((error) => { + console.log("Error:" + error); +}); + +interface IField { + tag: string; + text: string; +} + +interface ICommit { + pushRequestBody?: string; + pushRequestNum?: number; + submitter: string; + sha: string; + title: string; + fields: IField[]; +} diff --git a/yarn.lock b/yarn.lock index fb1c7e06704..05c81b8ff31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,10 @@ dependencies: "@types/minimatch" "*" +"@types/github@^0.0.0": + version "0.0.0" + resolved "https://registry.yarnpkg.com/@types/github/-/github-0.0.0.tgz#f32202bcdb15ac916cd079caba9e9f45bb304e76" + "@types/glob@^5.0.30": version "5.0.30" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.30.tgz#1026409c5625a8689074602808d082b2867b8a51" @@ -61,6 +65,13 @@ version "1.0.0" resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-1.0.0.tgz#3ae6206a6d67c01ffddb9a1eac4cd9b518d534ee" +agent-base@2: + version "2.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.0.1.tgz#bd8f9e86a8eb221fffa07bd14befd55df142815e" + dependencies: + extend "~3.0.0" + semver "~5.0.1" + ansi-align@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" @@ -259,7 +270,7 @@ crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" -debug@2.2.0: +debug@2, debug@2.2.0, debug@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -363,6 +374,9 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" +extend@3, extend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" execa@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.4.0.tgz#4eb6467a36a095fabb2970ff9d5e3fb7bce6ebc3" @@ -391,6 +405,13 @@ findup-sync@~0.3.0: dependencies: glob "~5.0.0" +follow-redirects@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-0.0.7.tgz#34b90bab2a911aa347571da90f22bd36ecd8a919" + dependencies: + debug "^2.2.0" + stream-consume "^0.1.0" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -407,6 +428,15 @@ function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" +github@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/github/-/github-8.1.1.tgz#a078c61669b4d4b588bf1b2e2a591eb7c49feb36" + dependencies: + follow-redirects "0.0.7" + https-proxy-agent "^1.0.0" + mime "^1.2.11" + netrc "^0.1.4" + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -505,6 +535,14 @@ hosted-git-info@^2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" +https-proxy-agent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6" + dependencies: + agent-base "2" + debug "2" + extend "3" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -709,6 +747,10 @@ map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" +mime@^1.2.11: + version "1.3.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" + "minimatch@2 || 3", minimatch@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" @@ -753,6 +795,10 @@ ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" +netrc@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/netrc/-/netrc-0.1.4.tgz#6be94fcaca8d77ade0a9670dc460914c94472444" + node-status-codes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" @@ -996,6 +1042,10 @@ semver-diff@^2.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" +semver@~5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" + shell-quote@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" @@ -1039,6 +1089,10 @@ stream-combiner@~0.0.4: dependencies: duplexer "~0.1.1" +stream-consume@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"