lerna-changelog
Advanced tools
Comparing version 0.2.3 to 0.3.0
@@ -31,4 +31,4 @@ "use strict"; | ||
function ApiDataCache(host, _ref) { | ||
var rootPath = _ref.rootPath; | ||
var cacheDir = _ref.cacheDir; | ||
var rootPath = _ref.rootPath, | ||
cacheDir = _ref.cacheDir; | ||
@@ -35,0 +35,0 @@ _classCallCheck(this, ApiDataCache); |
@@ -7,2 +7,4 @@ "use strict"; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
@@ -32,6 +34,13 @@ | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var UNRELEASED_TAG = "___unreleased___"; | ||
var COMMIT_FIX_REGEX = /(fix|close|resolve)(e?s|e?d)? [T#](\d+)/i; | ||
var Changelog = function () { | ||
function Changelog(config) { | ||
function Changelog() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
_classCallCheck(this, Changelog); | ||
@@ -41,2 +50,6 @@ | ||
this.remote = new _RemoteRepo2.default(this.config); | ||
// CLI options | ||
this.tagFrom = options["tag-from"]; | ||
this.tagTo = options["tag-to"]; | ||
} | ||
@@ -64,84 +77,98 @@ | ||
var commitInfo = this.getCommitInfo(); | ||
var committers = this.getCommitters(commitInfo); | ||
var commitsByCategory = this.getCommitsByCategory(commitInfo); | ||
var fixesRegex = /(fix|close|resolve)(e?s|e?d)? [T#](\d+)/i; | ||
var date = new Date().toISOString(); | ||
date = date.slice(0, date.indexOf("T")); | ||
var markdown = "\n"; | ||
markdown += "## Unreleased (" + date + ")"; | ||
// Get all info about commits in a certain tags range | ||
var commitsInfo = this.getCommitsInfo(); | ||
var commitsByTag = this.getCommitsByTag(commitsInfo); | ||
_progressBar2.default.init(commitsByCategory.length); | ||
Object.keys(commitsByTag).forEach(function (tag) { | ||
var commitsForTag = commitsByTag[tag].commits; | ||
var commitsByCategory = _this.getCommitsByCategory(commitsForTag); | ||
var committers = _this.getCommitters(commitsForTag); | ||
commitsByCategory.filter(function (category) { | ||
return category.commits.length > 0; | ||
}).forEach(function (category) { | ||
_progressBar2.default.tick(category.heading); | ||
// Skip this iteration if there are no commits available for the tag | ||
var hasCommitsForCurrentTag = commitsByCategory.some(function (category) { | ||
return category.commits.length > 0; | ||
}); | ||
if (!hasCommitsForCurrentTag) return; | ||
var commitsByPackage = {}; | ||
var releaseTitle = tag === UNRELEASED_TAG ? "Unreleased" : tag; | ||
markdown += "## " + releaseTitle + " (" + commitsByTag[tag].date + ")"; | ||
category.commits.forEach(function (commit) { | ||
_progressBar2.default.init(commitsByCategory.length); | ||
// Array of unique packages. | ||
var changedPackages = Object.keys((0, _execSync2.default)("git show -m --name-only --pretty='format:' --first-parent " + commit.commitSHA) | ||
// turn into an array | ||
.split("\n") | ||
// extract base package name, and stuff into an object for deduping. | ||
.reduce(function (obj, files) { | ||
if (files.indexOf("packages/") === 0) { | ||
obj[files.slice(9).split("/", 1)[0]] = true; | ||
} | ||
return obj; | ||
}, {})); | ||
commitsByCategory.filter(function (category) { | ||
return category.commits.length > 0; | ||
}).forEach(function (category) { | ||
_progressBar2.default.tick(category.heading); | ||
var heading = changedPackages.length > 0 ? "* " + changedPackages.map(function (pkg) { | ||
return "`" + pkg + "`"; | ||
}).join(", ") : "* Other"; // No changes to packages, but still relevant. | ||
var commitsByPackage = category.commits.reduce(function (acc, commit) { | ||
// Array of unique packages. | ||
var changedPackages = _this.getListOfUniquePackages(commit.commitSHA); | ||
if (!commitsByPackage[heading]) { | ||
commitsByPackage[heading] = []; | ||
} | ||
var heading = changedPackages.length > 0 ? "* " + changedPackages.map(function (pkg) { | ||
return "`" + pkg + "`"; | ||
}).join(", ") : "* Other"; | ||
// No changes to packages, but still relevant. | ||
var existingCommitsForHeading = acc[heading] || []; | ||
return _extends({}, acc, _defineProperty({}, heading, existingCommitsForHeading.concat(commit))); | ||
}, {}); | ||
commitsByPackage[heading].push(commit); | ||
}); | ||
markdown += "\n"; | ||
markdown += "\n"; | ||
markdown += "#### " + category.heading; | ||
markdown += "\n"; | ||
markdown += "\n"; | ||
markdown += "#### " + category.heading; | ||
Object.keys(commitsByPackage).forEach(function (heading) { | ||
markdown += "\n" + heading; | ||
Object.keys(commitsByPackage).forEach(function (heading) { | ||
markdown += "\n" + heading; | ||
commitsByPackage[heading].forEach(function (commit) { | ||
markdown += "\n * "; | ||
commitsByPackage[heading].forEach(function (commit) { | ||
if (commit.number) { | ||
var prUrl = _this.remote.getBasePullRequestUrl() + commit.number; | ||
markdown += "[#" + commit.number + "](" + prUrl + ") "; | ||
} | ||
markdown += "\n * "; | ||
if (commit.title.match(COMMIT_FIX_REGEX)) { | ||
commit.title = commit.title.replace(COMMIT_FIX_REGEX, "Closes [#$3](" + _this.remote.getBaseIssueUrl() + "$3)"); | ||
} | ||
if (commit.number) { | ||
var prUrl = _this.remote.getBasePullRequestUrl() + commit.number; | ||
markdown += "[#" + commit.number + "](" + prUrl + ") "; | ||
} | ||
if (commit.title.match(fixesRegex)) { | ||
commit.title = commit.title.replace(fixesRegex, "Closes [#$3](" + _this.remote.getBaseIssueUrl() + "$3)"); | ||
} | ||
markdown += commit.title + "." + " ([@" + commit.user.login + "](" + commit.user.html_url + "))"; | ||
markdown += commit.title + "." + " ([@" + commit.user.login + "](" + commit.user.html_url + "))"; | ||
}); | ||
}); | ||
}); | ||
}); | ||
_progressBar2.default.terminate(); | ||
_progressBar2.default.terminate(); | ||
markdown += "\n\n#### Committers: " + committers.length + "\n"; | ||
markdown += committers.map(function (commiter) { | ||
return "- " + commiter; | ||
}).join("\n"); | ||
markdown += "\n\n#### Committers: " + committers.length + "\n"; | ||
markdown += committers.map(function (commiter) { | ||
return "- " + commiter; | ||
}).join("\n"); | ||
markdown += "\n\n\n"; | ||
}); | ||
return markdown; | ||
return markdown.substring(0, markdown.length - 3); | ||
} | ||
}, { | ||
key: "getListOfUniquePackages", | ||
value: function getListOfUniquePackages(sha) { | ||
return Object.keys( | ||
// turn into an array | ||
(0, _execSync2.default)("git show -m --name-only --pretty='format:' --first-parent " + sha).split("\n").reduce(function (acc, files) { | ||
if (files.indexOf("packages/") === 0) { | ||
acc[files.slice(9).split("/", 1)[0]] = true; | ||
} | ||
return acc; | ||
}, {})); | ||
} | ||
}, { | ||
key: "getListOfTags", | ||
value: function getListOfTags() { | ||
var tags = (0, _execSync2.default)("git tag"); | ||
if (tags) { | ||
return tags.split("\n"); | ||
} | ||
return []; | ||
} | ||
}, { | ||
key: "getLastTag", | ||
@@ -154,5 +181,16 @@ value: function getLastTag() { | ||
value: function getListOfCommits() { | ||
var lastTag = this.getLastTag(); | ||
var commits = (0, _execSync2.default)("git log --oneline " + lastTag + "..").split("\n"); | ||
return commits; | ||
// Determine the tags range to get the commits for. Custom from/to can be | ||
// provided via command-line options. | ||
// Default is "from last tag". | ||
var tagFrom = this.tagFrom || this.getLastTag(); | ||
var tagTo = this.tagTo || ""; | ||
var tagsRange = tagFrom + ".." + tagTo; | ||
var commits = (0, _execSync2.default)( | ||
// Prints "<short-hash>;<ref-name>;<summary>;<date>" | ||
// This format is used in `getCommitsInfo` for easily analize the commit. | ||
"git log --oneline --pretty=\"%h;%D;%s;%cd\" --date=short " + tagsRange); | ||
if (commits) { | ||
return commits.split("\n"); | ||
} | ||
return []; | ||
} | ||
@@ -168,3 +206,8 @@ }, { | ||
var login = (commit.user || {}).login; | ||
if (login && !committers[login]) { | ||
// If a list of `ignoreCommitters` is provided in the lerna.json config | ||
// check if the current committer should be kept or not. | ||
var shouldKeepCommiter = login && (!_this2.config.ignoreCommitters || !_this2.config.ignoreCommitters.some(function (c) { | ||
return c === login || login.indexOf(c) > -1; | ||
})); | ||
if (login && shouldKeepCommiter && !committers[login]) { | ||
var user = _this2.remote.getUserData(login); | ||
@@ -185,15 +228,29 @@ var userNameAndLink = "[" + login + "](" + user.html_url + ")"; | ||
}, { | ||
key: "getCommitInfo", | ||
value: function getCommitInfo() { | ||
key: "getCommitsInfo", | ||
value: function getCommitsInfo() { | ||
var _this3 = this; | ||
var commits = this.getListOfCommits(); | ||
var allTags = this.getListOfTags(); | ||
_progressBar2.default.init(commits.length); | ||
var logs = commits.map(function (commit) { | ||
var commitsInfo = commits.map(function (commit) { | ||
// commit is formatted as following: | ||
// <short-hash>;<ref-name>;<summary>;<date> | ||
var parts = commit.split(";"); | ||
var sha = parts[0]; | ||
var _refs = parts[1]; | ||
var tagsInCommit = void 0; | ||
if (_refs.length > 1) { | ||
// Since there might be multiple tags referenced by the same commit, | ||
// we need to treat all of them as a list. | ||
tagsInCommit = allTags.reduce(function (acc, tag) { | ||
if (_refs.indexOf(tag) < 0) return acc; | ||
return acc.concat(tag); | ||
}, []); | ||
} | ||
var message = parts[2]; | ||
var date = parts[3]; | ||
var sha = commit.slice(0, 7); | ||
var message = commit.slice(8); | ||
var response; | ||
_progressBar2.default.tick(sha); | ||
@@ -203,55 +260,96 @@ | ||
if (message.indexOf("Merge pull request ") === 0) { | ||
var start = message.indexOf("#") + 1; | ||
var end = message.slice(start).indexOf(" "); | ||
var issueNumber = message.slice(start, start + end); | ||
var commitInfo = { | ||
commitSHA: sha, | ||
message: message, | ||
// Note: Only merge commits or commits referencing an issue / PR | ||
// will be kept in the changelog. | ||
labels: [], | ||
tags: tagsInCommit, | ||
date: date | ||
}; | ||
response = _this3.remote.getIssueData(issueNumber); | ||
if (message.indexOf("Merge pull request ") === 0 || mergeCommit) { | ||
var issueNumber = void 0; | ||
if (message.indexOf("Merge pull request ") === 0) { | ||
var start = message.indexOf("#") + 1; | ||
var end = message.slice(start).indexOf(" "); | ||
issueNumber = message.slice(start, start + end); | ||
} else issueNumber = mergeCommit[1]; | ||
var response = _this3.remote.getIssueData(issueNumber); | ||
response.commitSHA = sha; | ||
response.mergeMessage = message; | ||
return response; | ||
} else if (mergeCommit) { | ||
var issueNumber = mergeCommit[1]; | ||
response = _this3.remote.getIssueData(issueNumber); | ||
response.commitSHA = sha; | ||
response.mergeMessage = message; | ||
return response; | ||
Object.assign(commitInfo, response); | ||
} | ||
return { | ||
commitSHA: sha, | ||
message: message, | ||
labels: [] | ||
}; | ||
return commitInfo; | ||
}); | ||
_progressBar2.default.terminate(); | ||
return logs; | ||
return commitsInfo; | ||
} | ||
}, { | ||
key: "getCommitsByCategory", | ||
value: function getCommitsByCategory(logs) { | ||
key: "getCommitsByTag", | ||
value: function getCommitsByTag(commits) { | ||
var _this4 = this; | ||
var categories = this.remote.getLabels().map(function (label) { | ||
var commits = []; | ||
// Analyze the commits and group them by tag. | ||
// This is useful to generate multiple release logs in case there are | ||
// multiple release tags. | ||
var currentTags = [UNRELEASED_TAG]; | ||
return commits.reduce(function (acc, commit) { | ||
if (commit.tags && commit.tags.length > 0) { | ||
currentTags = commit.tags; | ||
} | ||
logs.forEach(function (log) { | ||
var labels = log.labels.map(function (label) { | ||
return label.name.toLowerCase(); | ||
}); | ||
// Tags referenced by commits are treated as a list. When grouping them, | ||
// we split the commits referenced by multiple tags in their own group. | ||
// This results in having one group of commits for each tag, even if | ||
// the same commits are "duplicated" across the different tags | ||
// referencing them. | ||
var commitsForTags = currentTags.reduce(function (acc2, currentTag) { | ||
var existingCommitsForTag = []; | ||
if ({}.hasOwnProperty.call(acc, currentTag)) { | ||
existingCommitsForTag = acc[currentTag].commits; | ||
} | ||
if (labels.indexOf(label.toLowerCase()) >= 0) { | ||
commits.push(log); | ||
var releaseDate = _this4.getToday(); | ||
if (currentTag !== UNRELEASED_TAG) { | ||
releaseDate = acc[currentTag] ? acc[currentTag].date : commit.date; | ||
} | ||
}); | ||
return _extends({}, acc2, _defineProperty({}, currentTag, { | ||
date: releaseDate, | ||
commits: existingCommitsForTag.concat(commit) | ||
})); | ||
}, {}); | ||
return _extends({}, acc, commitsForTags); | ||
}, {}); | ||
} | ||
}, { | ||
key: "getCommitsByCategory", | ||
value: function getCommitsByCategory(commits) { | ||
var _this5 = this; | ||
return this.remote.getLabels().map(function (label) { | ||
return { | ||
heading: _this4.remote.getHeadingForLabel(label), | ||
commits: commits | ||
heading: _this5.remote.getHeadingForLabel(label), | ||
// Keep only the commits that have a matching label with the one | ||
// provided in the lerna.json config. | ||
commits: commits.reduce(function (acc, commit) { | ||
if (commit.labels.some(function (l) { | ||
return l.name.toLowerCase() === label.toLowerCase(); | ||
})) return acc.concat(commit); | ||
return acc; | ||
}, []) | ||
}; | ||
}); | ||
return categories; | ||
} | ||
}, { | ||
key: "getToday", | ||
value: function getToday() { | ||
var date = new Date().toISOString(); | ||
return date.slice(0, date.indexOf("T")); | ||
} | ||
}]); | ||
@@ -262,9 +360,2 @@ | ||
exports.default = Changelog; | ||
function toTitleCase(str) { | ||
return str.replace(/\w\S*/g, function (text) { | ||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase(); | ||
}); | ||
} | ||
exports.default = Changelog; |
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
"use strict"; | ||
@@ -14,3 +14,3 @@ Object.defineProperty(exports, "__esModule", { | ||
function ConfigurationError(message) { | ||
this.name = 'ConfigurationError'; | ||
this.name = "ConfigurationError"; | ||
this.message = message; | ||
@@ -17,0 +17,0 @@ this.stack = new Error().stack; |
@@ -32,4 +32,4 @@ "use strict"; | ||
this.repo = repo; | ||
this.cache = new _ApiDataCache2.default('github', config); | ||
this.auth = process.env.GITHUB_AUTH; | ||
this.cache = new _ApiDataCache2.default("github", config); | ||
this.auth = this.getAuthToken(); | ||
if (!this.auth) { | ||
@@ -41,5 +41,10 @@ throw new _ConfigurationError2.default("Must provide GITHUB_AUTH"); | ||
_createClass(GithubAPI, [{ | ||
key: "getAuthToken", | ||
value: function getAuthToken() { | ||
return process.env.GITHUB_AUTH; | ||
} | ||
}, { | ||
key: "getIssueData", | ||
value: function getIssueData(issue) { | ||
return this._get('issue', issue); | ||
return this._get("issue", issue); | ||
} | ||
@@ -49,3 +54,3 @@ }, { | ||
value: function getUserData(login) { | ||
return this._get('user', login); | ||
return this._get("user", login); | ||
} | ||
@@ -70,3 +75,3 @@ }, { | ||
var url = "https://api.github.com" + path; | ||
return (0, _execSync2.default)("curl -H 'Authorization: token " + process.env.GITHUB_AUTH + "' --silent " + url); | ||
return (0, _execSync2.default)("curl -H 'Authorization: token " + process.env.GITHUB_AUTH + "' --silent --globoff " + url); | ||
} | ||
@@ -73,0 +78,0 @@ }]); |
@@ -21,4 +21,4 @@ "use strict"; | ||
var repo = config.repo; | ||
var labels = config.labels; | ||
var repo = config.repo, | ||
labels = config.labels; | ||
@@ -25,0 +25,0 @@ this.repo = repo; |
{ | ||
"name": "lerna-changelog", | ||
"version": "0.2.3", | ||
"version": "0.3.0", | ||
"description": "Generate a changelog for a lerna monorepo", | ||
"main": "index.js", | ||
"bin": { | ||
"lerna-changelog": "cli.js" | ||
"lerna-changelog": "./bin/cli.js" | ||
}, | ||
"scripts": { | ||
"build": "babel src -d lib", | ||
"build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__", | ||
"watch": "npm run build -- --watch", | ||
"clean": "rimraf lib", | ||
"test": "eslint index.js cli.js src/", | ||
"prepublish": "npm run build" | ||
"lint": "eslint index.js cli.js src", | ||
"fix": "npm run lint -- --fix", | ||
"test": "jest", | ||
"test-ci": "npm run build && jest", | ||
"prepublish": "npm run build", | ||
"changelog": "node ./bin/cli.js" | ||
}, | ||
@@ -30,6 +35,13 @@ "repository": { | ||
"devDependencies": { | ||
"babel-cli": "^6.9.0", | ||
"babel-preset-es2015": "^6.9.0", | ||
"eslint": "^2.10.2", | ||
"rimraf": "^2.5.2" | ||
"babel-cli": "^6.18.0", | ||
"babel-eslint": "^7.1.1", | ||
"babel-jest": "^18.0.0", | ||
"babel-plugin-transform-object-rest-spread": "6.20.2", | ||
"babel-preset-es2015": "^6.18.0", | ||
"eslint": "^3.13.1", | ||
"eslint-config-babel": "^6.0.0", | ||
"eslint-plugin-flowtype": "^2.30.0", | ||
"jest": "^18.1.0", | ||
"lerna": "^2.0.0-beta.32", | ||
"rimraf": "^2.5.4" | ||
}, | ||
@@ -41,4 +53,5 @@ "peerDependencies": { | ||
"chalk": "^1.1.3", | ||
"mkdirp": "^0.5.1" | ||
"mkdirp": "^0.5.1", | ||
"yargs": "^6.6.0" | ||
} | ||
} |
@@ -74,5 +74,27 @@ # Lerna Changelog | ||
- `labels`: GitHub issue/PR labels mapped to changelog section headers | ||
- `ignoreCommitters` [optional]: list of commiters to ignore (exact or partial match). Useful for example to ignore commits from bot agents | ||
## CLI | ||
```bash | ||
$ lerna-changelog | ||
Usage: lerna-changelog [options] | ||
Options: | ||
--tag-from A git tag that determines the lower bound of the range of commits | ||
(defaults to last available) [string] | ||
--tag-to A git tag that determines the upper bound of the range of commits | ||
[string] | ||
--version Show version number [boolean] | ||
--help Show help [boolean] | ||
Examples: | ||
lerna-changelog create a changelog for the changes | ||
after the latest available tag | ||
lerna-changelog --tag-from 0.1.0 create a changelog for the changes | ||
--tag-to 0.3.0 in all tags within the given range | ||
``` | ||
[lerna-homepage]: https://lernajs.io | ||
[hzoo-profile]: https://github.com/hzoo | ||
[original-pr]: https://github.com/lerna/lerna/pull/29 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
212970
37
986
100
4
11
2
+ Addedyargs@^6.6.0
+ Addedcamelcase@3.0.0(transitive)
+ Addedos-locale@1.4.0(transitive)
+ Addedwhich-module@1.0.0(transitive)
+ Addedyargs@6.6.0(transitive)
+ Addedyargs-parser@4.2.1(transitive)