forked from desktop/what-the-changelog
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
216 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules | ||
out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
!out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import * as HTTPS from "https"; | ||
|
||
export interface IAPIPR { | ||
readonly title: string; | ||
readonly body: string; | ||
} | ||
|
||
type GraphQLResponse = { | ||
readonly data: { | ||
readonly repository: { | ||
readonly pullRequest: IAPIPR; | ||
}; | ||
}; | ||
}; | ||
|
||
export function fetchPR(id: number): Promise<IAPIPR | null> { | ||
return new Promise((resolve, reject) => { | ||
const options: HTTPS.RequestOptions = { | ||
host: "api.github.com", | ||
protocol: "https:", | ||
path: "/graphql", | ||
method: "POST", | ||
headers: { | ||
Authorization: `bearer ${process.env.GITHUB_ACCESS_TOKEN}`, | ||
"User-Agent": "what-the-changelog" | ||
} | ||
}; | ||
|
||
const request = HTTPS.request(options, response => { | ||
let received = ""; | ||
response.on("data", chunk => { | ||
received += chunk; | ||
}); | ||
|
||
response.on("end", () => { | ||
try { | ||
const json: GraphQLResponse = JSON.parse(received); | ||
const pr = json.data.repository.pullRequest; | ||
resolve(pr); | ||
} catch (e) { | ||
resolve(null); | ||
} | ||
}); | ||
}); | ||
|
||
const graphql = ` | ||
{ | ||
repository(owner: "desktop", name: "desktop") { | ||
pullRequest(number: ${id}) { | ||
title | ||
body | ||
} | ||
} | ||
} | ||
`; | ||
request.write(JSON.stringify({ query: graphql })); | ||
|
||
request.end(); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { run } from "./run"; | ||
|
||
const args = process.argv.splice(2); | ||
run(args); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { spawn } from "./spawn"; | ||
import { fetchPR, IAPIPR } from "./api"; | ||
|
||
async function getLogLines( | ||
previousVersion: string | ||
): Promise<ReadonlyArray<string>> { | ||
const log = await spawn("git", [ | ||
"log", | ||
`...${previousVersion}`, | ||
"--merges", | ||
"--grep='Merge pull request'", | ||
"--format=format:%s", | ||
"-z", | ||
"--" | ||
]); | ||
|
||
return log.split("\0"); | ||
} | ||
|
||
interface IParsedCommit { | ||
readonly id: number; | ||
readonly remote: string; | ||
} | ||
|
||
function parseCommitTitle(line: string): IParsedCommit { | ||
// E.g.: Merge pull request #2424 from desktop/fix-shrinkwrap-file | ||
const re = /^Merge pull request #(\d+) from (.+)\/.*$/; | ||
const matches = line.match(re); | ||
if (!matches || matches.length !== 3) { | ||
throw new Error(`Unable to parse '${line}'`); | ||
} | ||
|
||
const id = parseInt(matches[1], 10); | ||
if (isNaN(id)) { | ||
throw new Error(`Unable to parse PR number from '${line}': ${matches[1]}`); | ||
} | ||
|
||
return { | ||
id, | ||
remote: matches[2] | ||
}; | ||
} | ||
|
||
function capitalized(str: string): string { | ||
return str.charAt(0).toUpperCase() + str.slice(1); | ||
} | ||
|
||
const PlaceholderChangeType = "???"; | ||
|
||
function getChangelogEntry(prID: number, pr: IAPIPR): string { | ||
let issueRef = ""; | ||
let type = PlaceholderChangeType; | ||
const description = capitalized(pr.title); | ||
|
||
const re = /Fixes #(\d+)/gi; | ||
let match; | ||
do { | ||
match = re.exec(pr.body); | ||
if (match && match.length > 1) { | ||
issueRef += ` #${match[1]}`; | ||
} | ||
} while (match); | ||
|
||
if (issueRef.length) { | ||
type = "Fixed"; | ||
} else { | ||
issueRef = ` #${prID}`; | ||
} | ||
|
||
return `[${type}] ${description} -${issueRef}`; | ||
} | ||
|
||
async function getChangelogEntries( | ||
lines: ReadonlyArray<string> | ||
): Promise<ReadonlyArray<string>> { | ||
const entries = []; | ||
for (const line of lines) { | ||
try { | ||
const commit = parseCommitTitle(line); | ||
const pr = await fetchPR(commit.id); | ||
if (!pr) { | ||
throw new Error(`Unable to get PR from API: ${commit.id}`); | ||
} | ||
|
||
const entry = getChangelogEntry(commit.id, pr); | ||
entries.push(entry); | ||
} catch (e) { | ||
console.warn("Unable to parse line, using the full message.", e); | ||
|
||
entries.push(`[${PlaceholderChangeType}] ${line}`); | ||
} | ||
} | ||
|
||
return entries; | ||
} | ||
|
||
export async function run(args: ReadonlyArray<string>): Promise<void> { | ||
const previousVersion = args[0]; | ||
const lines = await getLogLines(previousVersion); | ||
const changelogEntries = await getChangelogEntries(lines); | ||
console.log(JSON.stringify(changelogEntries)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import * as ChildProcess from "child_process"; | ||
|
||
export function spawn( | ||
cmd: string, | ||
args: ReadonlyArray<string> | ||
): Promise<string> { | ||
return new Promise((resolve, reject) => { | ||
const child = ChildProcess.spawn(cmd, args as string[], { shell: true }); | ||
let receivedData = ""; | ||
|
||
child.on("error", reject); | ||
|
||
child.stdout.on("data", data => { | ||
receivedData += data; | ||
}); | ||
|
||
child.on("close", (code, signal) => { | ||
if (code === 0) { | ||
resolve(receivedData); | ||
} else { | ||
reject( | ||
new Error( | ||
`'${cmd} ${args.join( | ||
" " | ||
)}' exited with code ${code}, signal ${signal}` | ||
) | ||
); | ||
} | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"target": "es6", | ||
"noImplicitAny": true, | ||
"sourceMap": false, | ||
"strict": true, | ||
"outDir": "./out" | ||
}, | ||
"exclude": ["node_modules", "out"], | ||
"compileOnSave": false | ||
} |