Skip to content

Commit

Permalink
Second, I guess
Browse files Browse the repository at this point in the history
  • Loading branch information
joshaber committed Aug 9, 2017
1 parent 2cb3bf2 commit 70f3e78
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
out
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!out
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
"name": "what-the-changelog",
"version": "0.0.1",
"description": "",
"main": "index.js",
"main": "out/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^8.0.20",
"typescript": "^2.4.2"
}
}
60 changes: 60 additions & 0 deletions src/api.ts
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();
});
}
4 changes: 4 additions & 0 deletions src/index.ts
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);
102 changes: 102 additions & 0 deletions src/run.ts
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));
}
31 changes: 31 additions & 0 deletions src/spawn.ts
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}`
)
);
}
});
});
}
13 changes: 13 additions & 0 deletions tsconfig.json
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
}

0 comments on commit 70f3e78

Please sign in to comment.