diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index fcf50da..ddcfb57 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -32,6 +32,40 @@ 'use strict'; +/** + * @typedef PR + * @property {number} pullId + * @property {{name: string, href: string}} author + * @property {string} href + * @property {string} title + * @property {string[]} shas + * @property {Commit[]} commits + */ + +/** + * @typedef Commit + * @extends PR + * @property {string} type + * @property {string} header + * @property {string} subject + * @property {string} sha + * @property {{title: string, text:string}[]} notes + * @property {{prefix: string, issue: number, href: string}[]} references + */ + +/** + * @typedef RepoInfo + * @property {string} htmlBase + * @property {string} username + * @property {string} repository + */ + +/** + * @typedef NlmOptions + * @property {{skip?: boolean, set: {[key: string]: string}}} emoji + * @property {{verbose?: boolean}} changelog + */ + const Github = require('../github/client'); const parseRepository = require('../github/parse-repository'); @@ -73,11 +107,16 @@ const emojiMaps = new Map([ ], ]); +/** + * @param {string} type + * @param {{nlmOptions?: NlmOptions}} options + * @return {string} + */ function getTypeCategory(type, options) { const { nlmOptions = {} } = options; const { emoji: emojiOpts = {} } = nlmOptions; - let descr = ''; + let descr; const headlineLevel = '####'; switch (type) { @@ -129,6 +168,10 @@ function getTypeCategory(type, options) { ); } +/** + * @param {*[]} data + * @return {*[]} + */ function sortByType(data) { if (!data.length) { return []; @@ -159,6 +202,10 @@ function sortByType(data) { return sorted.concat(data); } +/** + * @param {string} title + * @return {string} + */ function getType(title) { const match = title.match(/^(\w+):/); let type = match && match[1]; @@ -168,13 +215,22 @@ function getType(title) { return type || 'internal'; } +/** + * @param {String} str + * @return {RegExpMatchArray|null} + */ function findDependency(str) { return str.match(DEP_REGEXP); } -function addPullRequestCommits(pkg, commits, pr) { - const github = Github.forRepository(pkg.repository); - +/** + * + * @param {Object} github + * @param {Commit[]} commits + * @param {PR} pr + * @return {Promise} + */ +function addPullRequestCommits(github, commits, pr) { pr.commits = null; pr.shas = null; @@ -197,10 +253,19 @@ function addPullRequestCommits(pkg, commits, pr) { }); } +/** + * @param {*[]} arrs + * @return {*[]} + */ function flatten(arrs) { return [].concat.apply([], arrs); } +/** + * @param {PR[]} prs + * @param {Commit[]} commits + * @return {*[]|Commit[]} + */ function removePRCommits(prs, commits) { const prShas = flatten(prs.map(pr => pr.shas)); return commits.filter( @@ -208,8 +273,12 @@ function removePRCommits(prs, commits) { ); } +/** + * @param {Commit} commit + * @return {*[]|{commit: Commit, text: string}[]} + */ function extractBreakingChanges(commit) { - if (!commit.notes || !commit.notes.length) { + if (!(commit.notes && commit.notes.length)) { return []; } @@ -223,6 +292,9 @@ function extractBreakingChanges(commit) { }); } +/** + * @param {PR[]} prs + */ function removeInvalidPRs(prs) { // Warning: We're doing something evil here and mutate the input array. const filtered = prs.filter(pr => { @@ -232,18 +304,27 @@ function removeInvalidPRs(prs) { Object.assign(prs, filtered); } +/** + * @param {{prefix: string, issue: number, href: string}[]} refs + * @return {string} + */ function formatReferences(refs) { if (!refs || refs.length === 0) { return ''; } - refs = refs.map(ref => { + const references = refs.map(ref => { return `[${ref.prefix}${ref.issue}](${ref.href})`; }); - return ` - see: ${refs.join(', ')}`; + return ` - see: ${references.join(', ')}`; } +/** + * @param {Commit} commit + * @param {RepoInfo} repoInfo + * @return {string} + */ function getCommitLink(commit, repoInfo) { const abbr = commit.sha.substr(0, 7); const href = [ @@ -256,10 +337,21 @@ function getCommitLink(commit, repoInfo) { return `[\`${abbr}\`](${href})`; } +/** + * @param {{text: string, commit: Commit}} change + * @param {RepoInfo} repoInfo + * @return {string} + */ function formatBreakingChange(change, repoInfo) { return `${change.text}\n\n*See: ${getCommitLink(change.commit, repoInfo)}*`; } +/** + * @param {Commit[]} commits + * @param {{nlmOptions?: NlmOptions}} options}} options + * @param {RepoInfo} repoInfo + * @return {*[]|(string)[][]} + */ function getBreakingChanges(commits, options, repoInfo) { const breaking = flatten(commits.map(extractBreakingChanges)); if (!breaking.length) { @@ -273,6 +365,11 @@ function getBreakingChanges(commits, options, repoInfo) { return [['breaking', breakingChanges]]; } +/** + * @param {Commit} commit + * @param {RepoInfo} repoInfo + * @return {string} + */ function formatCommit(commit, repoInfo) { let subject; @@ -287,6 +384,12 @@ function formatCommit(commit, repoInfo) { )}`; } +/** + * @param {PR} pr + * @param {{nlmOptions?: NlmOptions}} options + * @param {RepoInfo} repoInfo + * @return {string} + */ function formatPR(pr, options, repoInfo) { const { nlmOptions = {} } = options; const changes = @@ -301,6 +404,13 @@ function formatPR(pr, options, repoInfo) { return [titleLine].concat(changes).join('\n'); } +/** + * @param {PR[]} rawPRs + * @param {Commit[]} rawCommits + * @param {{nlmOptions?: NlmOptions}} options + * @param {RepoInfo} repoInfo + * @return {*[]|[string, string][]} + */ function format(rawPRs, rawCommits, options, repoInfo) { const orphansCommits = rawCommits.map(commit => [ getType(`${commit.type}: ${commit.subject}`), @@ -313,6 +423,11 @@ function format(rawPRs, rawCommits, options, repoInfo) { .map(([type, line]) => [type, `* ${line}`]); } +/** + * @param {[string, string][]|*[]} data + * @param {{nlmOptions?: NlmOptions}} options + * @return {string} + */ function mergeChangelog(data, options) { const changelog = new Map(); const sorted = sortByType(data); @@ -330,15 +445,23 @@ function mergeChangelog(data, options) { .join('\n\n'); } +/** + * @param {*} cwd + * @param {{repository: string|{url: string}}} pkg + * @param {{commits: Commit[], nlmOptions?: NlmOptions, changelog?: string}} options + * @return {Promise} + */ async function generateChangeLog(cwd, pkg, options) { const { commits } = options; const repoInfo = parseRepository(pkg.repository); + const github = Github.forRepository(pkg.repository); + /** @type {PR[]} */ const prs = commits.filter(c => c.type === 'pr'); // step1: fetch PR commits data from GH & match with commits for (const pr of prs) { - await addPullRequestCommits(pkg, commits, pr); + await addPullRequestCommits(github, commits, pr); } // step2: remove PRs without commits