Skip to content

Commit

Permalink
build: refactor release-note-generator
Browse files Browse the repository at this point in the history
  • Loading branch information
reed-jones committed Aug 14, 2020
1 parent 7c229e6 commit 23cf2e9
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 91 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GITHUB_TOKEN=
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ mix-manifest.json
vendor
.php_cs.cache
.phpunit.result.cache

.env
51 changes: 51 additions & 0 deletions build/github-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import fetch from 'node-fetch'
import semver from 'semver'

let latestRelease = null;
export async function getLatestRelease() {
if (latestRelease) return latestRelease
const releaseData = await fetch("https://api.github.com/repos/reed-jones/phase/releases/latest")
return latestRelease = await releaseData.json()
}

let releases = []
export async function getReleases() {
if (releases.length) return releases
const releasesData = await fetch("https://api.github.com/repos/reed-jones/phase/releases")
return releases = await releasesData.json();
}

export async function createRelease(version, body) {
const data = {
tag_name: version,
name: version,
draft: false,
prerelease: false,
body
}

const response = await fetch('https://api.github.com/repos/reed-jones/phase/releases', {
method: 'post',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Accept: 'application/vnd.github.v3+json',
Authorization: `token ${process.env.GITHUB_TOKEN}`
}
});
return await response.json();
}

let tags = []
export async function getTags() {
if (tags.length) return tags
const tagsData = await fetch("https://api.github.com/repos/reed-jones/phase/tags")
const rawTags = await tagsData.json()
return tags = rawTags.sort((a,b) => semver.compare(b.name, a.name))
}

export async function getLatestTag() {
if (tags.length) return tags[0]
await getTags()
return tags[0]
}
135 changes: 46 additions & 89 deletions build/release-notes.js
Original file line number Diff line number Diff line change
@@ -1,99 +1,56 @@
import { join } from "path";
import { existsSync } from "fs";
import { execSync } from "child_process";
import fetch from "node-fetch";
import semver from "semver";

process.chdir(join(__dirname, ".."));

const parseChangelog = (line) => {
const regex = /^\[(?<hash>.{7,7})\] (?<type>[\w\s\d]*)(\((?<scope>.*)\))?: (?<change>.*) \((?<author>.*)\)$/gm;
let m;

while ((m = regex.exec(line)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
return m.groups;
}
};
import { getLatestRelease, getLatestTag, createRelease } from "./github-api";
import {
git,
parseChangelogLine,
acceptableLines,
formatLine,
formatDiff,
} from "./utils";

async function generateChangelog() {
const sections = [];

const releaseData = await fetch(
"https://api.github.com/repos/reed-jones/phase/releases/latest"
);
const { tag_name } = await releaseData.json();

const tagData = await fetch(
"https://api.github.com/repos/reed-jones/phase/tags"
);
const tags = await tagData.json();
tags.sort((a, b) => semver.compare(b.name, a.name));
const { tag_name } = await getLatestRelease();
const tag = await getLatestTag();

const log =
execSync(`git log --pretty=format:"[%h] %s (%an <%ae>)" ${tag_name}...HEAD`)
.toString()
.trim()
.split("\n")
.map(parseChangelog)
.filter(
(line) =>
line &&
!line.change.startsWith("wip") &&
(!line.scope || !line.scope.includes("release"))
)
.sort((a, b) => a.type.localeCompare(b.type))
.map(
(line) =>
`[${line.hash}]: **${line.type}${
line.scope ? `(**_${line.scope}_**)` : ""
}**: ${line.change} - (${line.author})`
)
git(`log --pretty=format:"[%h] %s (%an <%ae>)" ${tag_name}...HEAD`)
.map(parseChangelogLine)
.filter(acceptableLines)
.map(formatLine)
.join("\n") || "No notable changes tracked";

sections.push(
`## Notable changes since the last stable release (${tag_name}):`
);
sections.push(`\n${log}\n`);

const pkgs = Array.from(
new Set(
execSync(
`git diff --name-only ${tag_name}...HEAD packages ':(exclude)*/package.json' ':(exclude)*/composer.json' ':(exclude)*/__tests__/*'`
const pkgs = [
...new Set(
git(
[
`diff --name-only ${tag_name}...HEAD packages`,
`':(exclude)*/package.json'`,
`':(exclude)*/composer.json'`,
`':(exclude)*/__tests__/*'`,
].join(" ")
)
.toString()
.trim()
.split("\n")
.map((line) => {
const [_, _base, _package] = line.split('/')
const file = `./packages/${_base}/${_package}/package.json`;
const isForNpm = existsSync(file);
const version = isForNpm
? require(join("..", file)).version
: tags[0].name.replace(/^v/, "");

const pkg = {
base: _base,
package: _package,
version,
manager: isForNpm ? "npm" : "composer",
};

return `[${`_${pkg.manager}_`.padEnd(10, " ")}] **${pkg.base}/${
pkg.package
}**@_${pkg.version}_`.toLowerCase()
})
.filter((s) => Boolean(s))
)
);

sections.push("## Packages updated in this release");
sections.push(`\n${pkgs.join("\n")}\n`);

console.log(sections.join("\n"));
.map(formatDiff(tag))
.filter(Boolean)
),
];

const date = new Date
const formattedDate = `${date.getFullYear()}-${`${date.getMonth() + 1}`.padStart(2, '0')}-${`${date.getDate()}`.padStart(2, '0')}`;

const body = [
`## [${tag.name}](https://github.com/reed-jones/phase/compare/${tag_name}...${tag.name}) - ${formattedDate}`,
'\n',
`## Notable commits since the last release (${tag_name}...${tag.name}):`,
`\n${log}`,
`\n`,
"## Packages updated in this release",
`\n${pkgs.join("\n")}\n`,
].join("\n");

return createRelease(tag.name, body)
// return body
}

generateChangelog().catch(console.error);
generateChangelog()
.then(console.log)
.catch(console.error);
59 changes: 58 additions & 1 deletion build/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,65 @@
import { execSync } from "child_process";
import { existsSync } from "fs";
import { join } from 'path'

export const packages = [
"packages/@phased/types",
"packages/@phased/state",
"packages/@phased/routing",
"packages/@phased/webpack-plugin",
"packages/@phased/laravel-mix",
"packages/@phased/phase"
"packages/@phased/phase",
];

export function git(cmd) {
return execSync(`git ${cmd}`).toString().trim().split("\n");
}

// release/changelog parseing
export function parseChangelogLine(line) {
const regex = /^\[(?<hash>.{7,7})\] (?<type>[\w\s\d]*)(\((?<scope>.*)\))?: (?<change>.*) \((?<author>.*)\)$/gm;
let m;

while ((m = regex.exec(line)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
return m.groups;
}
}

export function acceptableLines(line) {
if (!line) return false;
if (line.change.startsWith("wip")) return false;
if (!line.scope || !line.scope.includes("release")) return true;
return false;
}

export function formatLine(line) {
return `[${line.hash}]: **${line.type}${
line.scope ? `(**_${line.scope}_**)` : ""
}**: ${line.change} - (${line.author})`;
}

export function formatDiff(tag) {
return function(line) {
const [_, _base, _package] = line.split("/")
const file = `./packages/${_base}/${_package}/package.json`
const isForNpm = existsSync(file)
const version = isForNpm
? require(join("..", file)).version
: tag.name.replace(/^v/, "")

const pkg = {
base: _base,
package: _package,
version,
manager: isForNpm ? "npm" : "composer",
}

return `[${`_${pkg.manager}_`.padEnd(10, " ")}] **${pkg.base}/${
pkg.package
}**@_${pkg.version}_`.toLowerCase();
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@sucrase/jest-plugin": "^2.0.0",
"brotli-size": "^4.0.0",
"chalk": "^4.0.0",
"dotenv": "^8.2.0",
"esm": "^3.2.25",
"execa": "^4.0.0",
"gzip-size": "^5.1.1",
Expand All @@ -35,7 +36,7 @@
"test:php": "vendor/bin/pest",
"test": "yarn test:js && yarn test:php",
"bump": "node -r esm build/version.js",
"notes": "node -r esm build/release-notes.js",
"notes": "node -r esm -r dotenv/config build/release-notes.js",
"build": "node -r esm build/build-js.js",
"pretest:js": "yarn build"
},
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4772,6 +4772,11 @@ dotenv@^6.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==

dotenv@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==

[email protected], duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
Expand Down

0 comments on commit 23cf2e9

Please sign in to comment.