@kazupon/lerna-changelog
Version:
Generate a changelog for a lerna monorepo
196 lines (195 loc) • 7.87 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const pMap = require('p-map');
const progress_bar_1 = __importDefault(require("./progress-bar"));
const find_pull_request_id_1 = __importDefault(require("./find-pull-request-id"));
const Git = __importStar(require("./git"));
const github_api_1 = __importDefault(require("./github-api"));
const markdown_renderer_1 = __importDefault(require("./markdown-renderer"));
const UNRELEASED_TAG = '___unreleased___';
class Changelog {
constructor(config) {
this.config = config;
this.github = new github_api_1.default(this.config);
this.renderer = new markdown_renderer_1.default({
categories: Object.keys(this.config.labels).map(key => this.config.labels[key]),
baseIssueUrl: this.github.getBaseIssueUrl(this.config.repo),
unreleasedName: this.config.nextVersion || 'Unreleased',
packageMode: !!config.package
});
}
async createMarkdown(options = {}) {
const from = options.tagFrom || (await Git.lastTag());
const to = options.tagTo || 'HEAD';
const releases = await this.listReleases(from, to);
return this.renderer.renderMarkdown(releases);
}
async getCommitInfos(from, to) {
const commits = this.getListOfCommits(from, to);
const commitInfos = this.toCommitInfos(commits);
await this.downloadIssueData(commitInfos);
this.fillInCategories(commitInfos);
await this.fillInPackages(commitInfos);
return commitInfos;
}
async listReleases(from, to) {
const commits = await this.getCommitInfos(from, to);
const releases = this.groupByRelease(commits);
await this.fillInContributors(releases);
return releases;
}
async getListOfUniquePackages(sha) {
const packages = (await Git.changedPaths(sha))
.map(path => this.packageFromPath(path))
.filter(Boolean)
.filter(onlyUnique);
return !this.config.package
? packages
: packages.filter(pkg => pkg === this.config.package);
}
packageFromPath(path) {
const parts = path.split('/');
if (parts[0] !== 'packages' || parts.length < 3) {
return '';
}
if (parts.length >= 4 && parts[1][0] === '@') {
return `${parts[1]}/${parts[2]}`;
}
return parts[1];
}
getListOfCommits(from, to) {
return Git.listCommits(from, to);
}
async getCommitters(commits) {
const committers = {};
for (const commit of commits) {
const issue = commit.githubIssue;
const login = issue && issue.user && issue.user.login;
const shouldKeepCommiter = login && !this.ignoreCommitter(login);
if (login && shouldKeepCommiter && !committers[login]) {
committers[login] = await this.github.getUserData(login);
}
}
return Object.keys(committers).map(k => committers[k]);
}
ignoreCommitter(login) {
return this.config.ignoreCommitters.some((c) => c === login || login.indexOf(c) > -1);
}
toCommitInfos(commits) {
return commits.map(commit => {
const { sha, refName, summary: message, date } = commit;
let tagsInCommit;
if (refName.length > 1) {
const TAG_PREFIX = 'tag: ';
tagsInCommit = refName
.split(', ')
.filter(ref => ref.startsWith(TAG_PREFIX))
.map(ref => ref.substr(TAG_PREFIX.length));
}
const issueNumber = find_pull_request_id_1.default(message);
return {
commitSHA: sha,
message,
tags: tagsInCommit,
issueNumber,
date
};
});
}
async downloadIssueData(commitInfos) {
progress_bar_1.default.init('Downloading issue information…', commitInfos.length);
await pMap(commitInfos, async (commitInfo) => {
if (commitInfo.issueNumber) {
commitInfo.githubIssue = await this.github.getIssueData(this.config.repo, commitInfo.issueNumber);
}
progress_bar_1.default.tick();
}, { concurrency: 5 });
progress_bar_1.default.terminate();
}
groupByRelease(commits) {
const releaseMap = {};
let currentTags = [UNRELEASED_TAG];
for (const commit of commits) {
if (commit.tags && commit.tags.length > 0) {
currentTags = commit.tags;
}
if (!this.config.package) {
this._groupByRelease(currentTags, commit, releaseMap);
}
else {
for (const pkg of commit.packages || []) {
if (pkg === this.config.package) {
this._groupByRelease(currentTags, commit, releaseMap);
}
}
}
}
return Object.keys(releaseMap).map(tag => releaseMap[tag]);
}
_groupByRelease(tags, commit, releaseMap) {
for (const currentTag of tags) {
if (!releaseMap[currentTag]) {
const date = currentTag === UNRELEASED_TAG ? this.getToday() : commit.date;
releaseMap[currentTag] = { name: currentTag, date, commits: [] };
}
releaseMap[currentTag].commits.push(commit);
}
}
getToday() {
const date = new Date().toISOString();
return date.slice(0, date.indexOf('T'));
}
fillInCategories(commits) {
for (const commit of commits) {
if (!commit.githubIssue || !commit.githubIssue.labels)
continue;
const labels = commit.githubIssue.labels.map(label => label.name.toLowerCase());
commit.categories = Object.keys(this.config.labels)
.filter(label => labels.indexOf(label.toLowerCase()) !== -1)
.map(label => this.config.labels[label]);
}
}
async fillInPackages(commits) {
progress_bar_1.default.init('Mapping commits to packages…', commits.length);
try {
await pMap(commits, async (commit) => {
commit.packages = await this.getListOfUniquePackages(commit.commitSHA);
progress_bar_1.default.tick();
}, { concurrency: 5 });
}
finally {
progress_bar_1.default.terminate();
}
}
async fillInContributors(releases) {
for (const release of releases) {
release.contributors = await this.getCommitters(release.commits);
}
}
}
exports.default = Changelog;
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}