UNPKG

@kazupon/lerna-changelog

Version:

Generate a changelog for a lerna monorepo

196 lines (195 loc) 7.87 kB
"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; }