Skip to content

Commit c1f7b91

Browse files
committed
chore: refactor changelog script to not require annotated commits
1 parent 0d3cf8b commit c1f7b91

1 file changed

Lines changed: 71 additions & 90 deletions

File tree

scripts/changelog.js

Lines changed: 71 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict'
2+
23
/*
34
Usage:
45
@@ -13,107 +14,87 @@ the result to the changelog.
1314
*/
1415
const execSync = require('child_process').execSync
1516
const branch = process.argv[2] || 'origin/latest'
16-
const log = execSync(`git log --reverse --pretty='format:%h %H%d %s (%aN)%n%b%n---%n' ${branch}...`)
17+
const log = execSync(`git log --reverse --pretty='format:%h' ${branch}...`)
1718
.toString()
1819
.split(/\n/)
1920

20-
main()
21-
22-
function shortname (url) {
23-
const matched =
24-
url.match(/https:\/\/github\.com\/([^/]+\/[^/]+)\/(?:pull|issues)\/(\d+)/) ||
25-
url.match(/https:\/\/(npm\.community)\/t\/(?:[^/]+\/)(\d+)/)
26-
if (!matched) {
27-
return false
21+
function printCommit (c) {
22+
console.log(`* [\`${c.hash}\`](${c.url})`)
23+
for (const pr of c.prs) {
24+
console.log(` [#${pr.number}](${pr.url})`)
2825
}
29-
const repo = matched[1]
30-
const id = matched[2]
31-
if (repo !== 'npm/cli') {
32-
return `${repo}#${id}`
33-
} else {
34-
return `#${id}`
26+
console.log(` ${c.message}`)
27+
// no credit for deps commits
28+
if (!c.message.startsWith('deps')) {
29+
for (const user of c.credit) {
30+
console.log(` ([${user.name}](${user.url}))`)
31+
}
3532
}
3633
}
3734

38-
function printCommit (c) {
39-
console.log(`* [\`${c.shortid}\`](https://github.com/npm/cli/commit/${c.fullid})`)
40-
if (c.fixes.length) {
41-
for (const fix of c.fixes) {
42-
const label = shortname(fix)
43-
if (label) {
44-
console.log(` [${label}](${fix})`)
35+
const main = async () => {
36+
const query = `
37+
fragment commitCredit on GitObject {
38+
... on Commit {
39+
messageHeadline
40+
url
41+
authors (first:10) {
42+
nodes {
43+
user {
44+
login
45+
url
46+
}
47+
email
48+
name
49+
}
50+
}
51+
associatedPullRequests (first:10) {
52+
nodes {
53+
number
54+
url
55+
merged
56+
}
57+
}
4558
}
4659
}
47-
} else if (c.prurl) {
48-
const label = shortname(c.prurl)
49-
if (label) {
50-
console.log(` [${label}](${c.prurl})`)
51-
} else {
52-
console.log(` [#](${c.prurl})`)
60+
61+
query {
62+
repository (owner:"npm", name:"cli") {
63+
${log.map((sha) => `_${sha}: object (expression: "${sha}") {
64+
...commitCredit
65+
}`).join('\n')}
66+
}
5367
}
54-
}
55-
const msg = c.message
56-
.replace(/^\s+/gm, '')
57-
.replace(/^[-a-z]+: /, '')
58-
.replace(/^/gm, ' ')
59-
.replace(/^ {2}Reviewed-by: @.*/gm, '')
60-
.replace(/\n$/, '')
61-
// backtickify package@version
62-
.replace(/^(\s*@?[^@\s]+@\d+[.]\d+[.]\d+)\b(\s*\S)/g, '$1:$2')
63-
.replace(/((?:\b|@)[^@\s]+@\d+[.]\d+[.]\d+)\b/g, '`$1`')
64-
// linkify commitids
65-
.replace(/\b([a-f0-9]{7,8})\b/g, '[`$1`](https://github.com/npm/cli/commit/$1)')
66-
console.log(msg)
67-
// don't assign credit for dep updates
68-
if (!/^ {2}`[^`]+@\d+\.\d+\.\d+[^`]*`:?$/m.test(msg)) {
69-
if (c.credit) {
70-
c.credit.forEach(function (credit) {
71-
console.log(` ([@${credit}](https://github.com/${credit}))`)
72-
})
73-
} else {
74-
console.log(` ([@${c.author}](https://github.com/${c.author}))`)
68+
`
69+
70+
const response = execSync(`gh api graphql -f query='${query}'`).toString()
71+
const body = JSON.parse(response)
72+
73+
for (const [hash, data] of Object.entries(body.data.repository)) {
74+
const commit = {
75+
hash: hash.slice(1), // remove leading _
76+
url: data.url,
77+
message: data.messageHeadline.replace(/\(#\d+\)$/, ''),
78+
prs: data.associatedPullRequests.nodes.filter((pull) => pull.merged),
79+
credit: data.authors.nodes.map((author) => {
80+
if (author.user && author.user.login) {
81+
return {
82+
name: `@${author.user.login}`,
83+
url: author.user.url,
84+
}
85+
}
86+
// if the commit used an email that's not associated with a github account
87+
// then the user field will be empty, so we fall back to using the committer's
88+
// name and email as specified by git
89+
return {
90+
name: author.name,
91+
url: `mailto:${author.email}`,
92+
}
93+
}),
7594
}
95+
96+
printCommit(commit)
7697
}
7798
}
7899

79-
function main () {
80-
let commit
81-
log.forEach(function (line) {
82-
line = line.replace(/\r/g, '')
83-
let m
84-
/* eslint no-cond-assign:0 */
85-
if (/^---$/.test(line)) {
86-
printCommit(commit)
87-
} else if (
88-
(m = line.match(/^([a-f0-9]{7,10}) ([a-f0-9]+) (?:[(]([^)]+)[)] )?(.*?) [(](.*?)[)]/))
89-
) {
90-
commit = {
91-
shortid: m[1],
92-
fullid: m[2],
93-
branch: m[3],
94-
message: m[4],
95-
author: m[5],
96-
prurl: null,
97-
fixes: [],
98-
credit: null,
99-
}
100-
} else if ((m = line.match(/^PR-URL: (.*)/))) {
101-
commit.prurl = m[1]
102-
} else if ((m = line.match(/^Credit: @(.*)/))) {
103-
if (!commit.credit) {
104-
commit.credit = []
105-
}
106-
commit.credit.push(m[1])
107-
} else if ((m = line.match(/^(?:Fix(?:es)|Closes?): #?([0-9]+)/))) {
108-
commit.fixes.push(`https://github.com/npm/cli/issues/${m[1]}`)
109-
} else if ((m = line.match(/^(?:Fix(?:es)|Closes?): ([^#]+)#([0-9]*)/))) {
110-
commit.fixes.push(`https://github.com/${m[1]}/issues/${m[2]}`)
111-
} else if ((m = line.match(/^(?:Fix(?:es)|Closes?): (https?:\/\/.*)/))) {
112-
commit.fixes.push(m[1])
113-
} else if ((m = line.match(/^Reviewed-By: @(.*)/))) {
114-
commit.reviewed = m[1]
115-
} else if (/\S/.test(line)) {
116-
commit.message += `\n${line}`
117-
}
118-
})
119-
}
100+
main()

0 commit comments

Comments
 (0)