From 43255242efeb59661b33e2d883c045d827e4957f Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Sun, 15 Jan 2017 23:54:40 +0100 Subject: [PATCH 01/13] feat(changelog): allow to output changelog based on tags range This PR mainly introduces a way of querying commits for a custom tags range (at the moment only commits after the last available tag are taken into consideration). Now you can specify a custom range by passing --tagFrom and/or --tagTo options (--tagTo should be a tag after --tagFrom). Additionally, as also mentioned in #25, if multiple tags are present within the custom commits range, they will be grouped together a changelog entry for each tag will be created. Basic tests are also present now. Closes #17,#19,#25 --- .babelrc | 3 +- .eslintrc | 2 + .gitignore | 2 + .npmignore | 1 + README.md | 14 ++ cli.js | 21 ++- package.json | 28 ++- src/ApiDataCache.js | 16 +- src/Changelog.js | 355 +++++++++++++++++++++++-------------- src/ConfigurationError.js | 4 +- src/GithubAPI.js | 18 +- src/RemoteRepo.js | 2 +- src/__mocks__/Changelog.js | 55 ++++++ src/__mocks__/GithubAPI.js | 34 ++++ src/execSync.js | 4 +- test/Changelog.spec.js | 258 +++++++++++++++++++++++++++ 16 files changed, 652 insertions(+), 165 deletions(-) create mode 100644 src/__mocks__/Changelog.js create mode 100644 src/__mocks__/GithubAPI.js create mode 100644 test/Changelog.spec.js diff --git a/.babelrc b/.babelrc index c13c5f62..831f20a8 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,4 @@ { - "presets": ["es2015"] + "presets": ["es2015"], + "plugins": ["transform-object-rest-spread"] } diff --git a/.eslintrc b/.eslintrc index 94609be5..021e0bba 100644 --- a/.eslintrc +++ b/.eslintrc @@ -35,6 +35,8 @@ "indent": ["error", 2] }, "env": { + "es6": true, + "jest": true, "node": true } } diff --git a/.gitignore b/.gitignore index 19281fb7..0c672457 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ lib node_modules *.swp +coverage +.changelog diff --git a/.npmignore b/.npmignore index 85de9cf9..13e057ff 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1,2 @@ src +**/__mocks__/** diff --git a/README.md b/README.md index 69846c44..4d071152 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,20 @@ You'll need a GitHub API [personal access token](https://github.com/settings/tok - `repo`: Your "org/repo" on GitHub - `cacheDir` [optional]: A place to stash GitHub API responses to avoid throttling - `labels`: GitHub issue/PR labels mapped to changelog section headers +- `ignoreCommitters` [optional]: list of commiters to ignore (exact or partial match) + +## CLI + +```bash +$ lerna-changelog + + Usage: lerna-changelog [options] + + + Options: + --tagFrom define a custom tag to determine the lower bound of the range of commits (default: last available git tag) + --tagTo define a custom tag to determine the upper bound of the range of commits +``` [lerna-homepage]: https://lernajs.io [hzoo-profile]: https://github.com/hzoo diff --git a/cli.js b/cli.js index bbc19a56..694e4539 100755 --- a/cli.js +++ b/cli.js @@ -1,13 +1,26 @@ #!/usr/bin/env node var chalk = require("chalk"); -var lib = require("."); +var argv = require("yargs").argv; +var Changelog = require(".").Changelog; +var ConfigurationError = require(".").ConfigurationError; -var Changelog = lib.Changelog; -var ConfigurationError = lib.ConfigurationError; +if (argv.help) { + console.log( + "\n" + + " Usage: lerna-changelog [options]" + + "\n\n\n" + + " Options:" + + "\n" + + " --tagFrom define a custom tag to determine the lower bound of the range of commits (default: last available git tag)" + + "\n" + + " --tagTo define a custom tag to determine the upper bound of the range of commits" + ); + process.exit(0); +} try { - console.log((new Changelog()).createMarkdown()); + console.log((new Changelog(argv)).createMarkdown()); } catch (e) { if (e instanceof ConfigurationError) { console.log(chalk.red(e.message)); diff --git a/package.json b/package.json index 29311b21..b38fd4e1 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "lerna-changelog": "cli.js" }, "scripts": { - "build": "babel src -d lib", + "build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__/GithubAPI.js,src/__mocks__/Changelog.js", "clean": "rimraf lib", - "test": "eslint index.js cli.js src/", + "lint": "eslint index.js cli.js src/", + "test": "jest", "prepublish": "npm run build" }, "repository": { @@ -27,16 +28,29 @@ }, "homepage": "https://github.com/lerna/lerna-changelog#readme", "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", + "jest": "^18.1.0", + "lerna": "^2.0.0-beta.32", + "rimraf": "^2.5.4" }, "peerDependencies": { "lerna": "^2.0.0-beta.8" }, "dependencies": { "chalk": "^1.1.3", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.1", + "yargs": "^6.6.0" + }, + "jest": { + "globals": { + "process.env": { + "NODE_ENV": "test" + } + } } } diff --git a/src/ApiDataCache.js b/src/ApiDataCache.js index 0b1f3547..4f1f9674 100644 --- a/src/ApiDataCache.js +++ b/src/ApiDataCache.js @@ -4,7 +4,7 @@ import mkdirp from "mkdirp"; import ConfigurationError from "./ConfigurationError"; export default class ApiDataCache { - constructor(host, {rootPath, cacheDir}) { + constructor(host, { rootPath, cacheDir }) { this.host = host; const dir = this.dir = cacheDir && path.join(rootPath, cacheDir, host); @@ -12,22 +12,24 @@ export default class ApiDataCache { try { mkdirp.sync(dir); } catch (e) { - throw new ConfigurationError(`Can't use cacheDir "${cacheDir}" (${e.message})`); + throw new ConfigurationError( + `Can't use cacheDir "${cacheDir}" (${e.message})` + ); } } } get(type, key) { - if (!this.dir) return; + if (!this.dir) + return; try { return fs.readFileSync(this.fn(type, key), "utf-8"); - } catch (e) { - // Pass. - } + } catch (e) {} } set(type, key, data) { - if (!this.dir) return; + if (!this.dir) + return; return fs.writeFileSync(this.fn(type, key), data); } diff --git a/src/Changelog.js b/src/Changelog.js index 07085b3c..c177b93b 100644 --- a/src/Changelog.js +++ b/src/Changelog.js @@ -1,13 +1,20 @@ -import LernaRepo from "lerna/lib/Repository"; -import progressBar from "lerna/lib/progressBar"; -import RemoteRepo from "./RemoteRepo"; -import execSync from "./execSync"; +import LernaRepo from "lerna/lib/Repository"; +import progressBar from "lerna/lib/progressBar"; +import RemoteRepo from "./RemoteRepo"; +import execSync from "./execSync"; import ConfigurationError from "./ConfigurationError"; +const UNRELEASED_TAG = "___unreleased___"; +const COMMIT_FIX_REGEX = /(fix|close|resolve)(e?s|e?d)? [T#](\d+)/i; + export default class Changelog { - constructor(config) { + constructor(options = {}) { this.config = this.getConfig(); this.remote = new RemoteRepo(this.config); + + // CLI options + this.tagFrom = options.tagFrom; + this.tagTo = options.tagTo; } getConfig() { @@ -17,8 +24,8 @@ export default class Changelog { if (!config) { throw new ConfigurationError( - "Missing changelog config in `lerna.json`.\n"+ - "See docs for setup: https://github.com/lerna/lerna-changelog#readme" + "Missing changelog config in `lerna.json`.\n" + + "See docs for setup: https://github.com/lerna/lerna-changelog#readme" ); } @@ -28,89 +35,103 @@ export default class Changelog { } createMarkdown() { - const commitInfo = this.getCommitInfo(); - const committers = this.getCommitters(commitInfo); - const commitsByCategory = this.getCommitsByCategory(commitInfo); - const fixesRegex = /(fix|close|resolve)(e?s|e?d)? [T#](\d+)/i; - - let date = new Date().toISOString(); - - date = date.slice(0, date.indexOf("T")); - let markdown = "\n"; - markdown += "## Unreleased (" + date + ")"; - - progressBar.init(commitsByCategory.length); - - commitsByCategory.filter(category => { - return category.commits.length > 0; - }).forEach(category => { - progressBar.tick(category.heading); - - const commitsByPackage = {}; - - category.commits.forEach(commit => { - - // Array of unique packages. - var changedPackages = Object.keys( - execSync("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; - }, {}) - ); - - const heading = changedPackages.length > 0 - ?"* "+changedPackages.map(pkg => "`" + pkg + "`").join(", ") - :"* Other"; // No changes to packages, but still relevant. - - if (!commitsByPackage[heading]) { - commitsByPackage[heading] = []; - } - - commitsByPackage[heading].push(commit); - }); - - markdown += "\n"; - markdown += "\n"; - markdown += "#### " + category.heading; - - Object.keys(commitsByPackage).forEach(heading => { - markdown += "\n"+heading; - - commitsByPackage[heading].forEach(commit => { - - markdown += "\n * "; - - if (commit.number) { - var prUrl = this.remote.getBasePullRequestUrl() + commit.number; - markdown += "[#" + commit.number + "](" + prUrl + ") "; - } - + // Get all info about commits in a certain tags range + const commitsInfo = this.getCommitsInfo(); + const commitsByTag = this.getCommitsByTag(commitsInfo); + + Object.keys(commitsByTag).forEach(tag => { + const commitsForTag = commitsByTag[tag].commits; + + const releaseTitle = tag === UNRELEASED_TAG ? "Unreleased" : tag; + markdown += "## " + releaseTitle + " (" + commitsByTag[tag].date + ")"; + + const committers = this.getCommitters(commitsForTag); + const commitsByCategory = this.getCommitsByCategory(commitsForTag); + + progressBar.init(commitsByCategory.length); + + commitsByCategory + .filter(category => category.commits.length > 0) + .forEach(category => { + progressBar.tick(category.heading); + + const commitsByPackage = category.commits.reduce( + (acc, commit) => { + // Array of unique packages. + const changedPackages = this.getListOfUniquePackages(); + + const heading = changedPackages.length > 0 + ? "* " + changedPackages.map(pkg => "`" + pkg + "`").join(", ") + : "* Other"; + // No changes to packages, but still relevant. + const existingCommitsForHeading = acc[heading] || []; + return { + ...acc, + [heading]: existingCommitsForHeading.concat(commit) + }; + }, + {} + ); + + markdown += "\n"; + markdown += "\n"; + markdown += "#### " + category.heading; + + Object.keys(commitsByPackage).forEach(heading => { + markdown += "\n" + heading; + + commitsByPackage[heading].forEach(commit => { + markdown += "\n * "; + + if (commit.number) { + const prUrl = this.remote.getBasePullRequestUrl() + + commit.number; + markdown += "[#" + commit.number + "](" + prUrl + ") "; + } + + if (commit.title.match(COMMIT_FIX_REGEX)) { + commit.title = commit.title.replace( + COMMIT_FIX_REGEX, + "Closes [#$3](" + this.remote.getBaseIssueUrl() + "$3)" + ); + } + + markdown += commit.title + "." + " ([@" + commit.user.login + + "](" + + commit.user.html_url + + "))"; + }); + }); + }); - if (commit.title.match(fixesRegex)) { - commit.title = commit.title.replace(fixesRegex, "Closes [#$3](" + this.remote.getBaseIssueUrl() + "$3)"); - } + progressBar.terminate(); - markdown += commit.title + "." + " ([@" + commit.user.login + "](" + commit.user.html_url + "))"; - }); - }); + markdown += "\n\n#### Committers: " + committers.length + "\n"; + markdown += committers.map(function(commiter) { + return "- " + commiter; + }).join("\n"); + markdown += "\n\n\n"; }); - progressBar.terminate(); - - markdown += "\n\n#### Committers: " + committers.length + "\n"; - markdown += committers.map(function(commiter) { - return "- " + commiter; - }).join("\n"); + return markdown.substring(0, markdown.length - 3); + } - return markdown; + getListOfUniquePackages(sha) { + return Object.keys( + // turn into an array + execSync( + "git show -m --name-only --pretty='format:' --first-parent " + sha + ) + .split("\n") + .reduce((acc, files) => { + if (files.indexOf("packages/") === 0) { + acc[files.slice(9).split("/", 1)[0]] = true; + } + return acc; + }, {}) + ); } getLastTag() { @@ -118,17 +139,37 @@ export default class Changelog { } getListOfCommits() { - var lastTag = this.getLastTag(); - var commits = execSync("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". + const tagFrom = this.tagFrom || this.getLastTag(); + const tagTo = this.tagTo || ""; + const tagsRange = tagFrom + ".." + tagTo; + const commits = execSync( + // Prints ";;;" + // 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 []; } getCommitters(commits) { - var committers = {} + const committers = {}; commits.forEach(commit => { - const login = (commit.user||{}).login; - if (login && !committers[login]){ + const login = (commit.user || {}).login; + // If a list of `ignoreCommitters` is provided in the lerna.json config + // check if the current committer should be kept or not. + const shouldKeepCommiter = login && ( + !this.config.ignoreCommitters || + !this.config.ignoreCommitters.some( + c => c === login || login.indexOf(c) > -1 + ) + ); + if (login && shouldKeepCommiter && !committers[login]) { const user = this.remote.getUserData(login); const userNameAndLink = `[${login}](${user.html_url})`; if (user.name) { @@ -142,74 +183,122 @@ export default class Changelog { return Object.keys(committers).map(k => committers[k]).sort(); } - getCommitInfo() { + getCommitsInfo() { const commits = this.getListOfCommits(); progressBar.init(commits.length); - var logs = commits.map(commit => { + const commitsInfo = commits.map(commit => { + // commit is formatted as following: + // ;;; + const parts = commit.split(";"); + const sha = parts[0]; + const _refs = parts[1].split(","); + let tag; + if (_refs.length === 1) { + // "tag: + tag = _refs[0].replace(/tag:\s(.*?)$/, "$1").trim(); + } else if (_refs.length === 4) { + // "HEAD -> master, tag: , origin/master, origin/HEAD" + tag = _refs[1].replace(/tag:\s(.*?)$/, "$1").trim(); + } + const message = parts[2]; + const date = parts[3]; - var sha = commit.slice(0, 7); - var message = commit.slice(8); - var response; progressBar.tick(sha); - var mergeCommit = message.match(/\(#(\d+)\)$/); + const mergeCommit = message.match(/\(#(\d+)\)$/); - 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); + const commitInfo = { + commitSHA: sha, + message: message, + // Note: Only merge commits or commits referencing an issue / PR + // will be kept in the changelog. + labels: [], + tag, + date + }; - response = this.remote.getIssueData(issueNumber); - response.commitSHA = sha; - response.mergeMessage = message; - return response; - } else if (mergeCommit) { - var issueNumber = mergeCommit[1]; - response = this.remote.getIssueData(issueNumber); + if (message.indexOf("Merge pull request ") === 0 || mergeCommit) { + let issueNumber; + if (message.indexOf("Merge pull request ") === 0) { + const start = message.indexOf("#") + 1; + const end = message.slice(start).indexOf(" "); + issueNumber = message.slice(start, start + end); + } else + issueNumber = mergeCommit[1]; + + const response = this.remote.getIssueData(issueNumber); response.commitSHA = sha; response.mergeMessage = message; - return response; + Object.assign(commitInfo, response); } - return { - commitSHA: sha, - message: message, - labels: [] - }; + return commitInfo; }); progressBar.terminate(); - return logs; + return commitsInfo; } - getCommitsByCategory(logs) { - var categories = this.remote.getLabels().map(label => { - var commits = []; + getCommitsByTag(commits) { + // Analyze the commits and group them by tag. + // This is useful to generate multiple release logs in case there are + // multiple release tags. + let currentTag = UNRELEASED_TAG; + return commits.reduce( + (acc, commit) => { + const tag = commit.tag; + if (tag) { + currentTag = tag; + } - logs.forEach(function(log) { - var labels = log.labels.map(function(label) { - return label.name.toLowerCase(); - }); + let existingCommitsForTag = []; + if ({}.hasOwnProperty.call(acc, currentTag)) { + existingCommitsForTag = acc[currentTag].commits; + } - if (labels.indexOf(label.toLowerCase()) >= 0) { - commits.push(log); + let releaseDate = today(); + if (currentTag !== UNRELEASED_TAG) { + releaseDate = acc[currentTag] ? acc[currentTag].date : commit.date; } - }); - return { - heading: this.remote.getHeadingForLabel(label), - commits: commits - }; - }); + return { + ...acc, + [currentTag]: { + date: releaseDate, + commits: existingCommitsForTag.concat(commit) + } + }; + }, + {} + ); + } - return categories; + getCommitsByCategory(commits) { + return this.remote.getLabels().map( + label => ({ + heading: this.remote.getHeadingForLabel(label), + // Keep only the commits that have a matching label with the one + // provided in the lerna.json config. + commits: commits.reduce( + (acc, commit) => { + if ( + commit.labels.some( + l => l.name.toLowerCase() === label.toLowerCase() + ) + ) + return acc.concat(commit); + return acc; + }, + [] + ) + }) + ); } } -function toTitleCase(str) { - return str.replace(/\w\S*/g, function(text){ - return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase(); - }); +export function today() { + const date = new Date().toISOString(); + return date.slice(0, date.indexOf("T")); } diff --git a/src/ConfigurationError.js b/src/ConfigurationError.js index d24c4880..eb3bb3fe 100644 --- a/src/ConfigurationError.js +++ b/src/ConfigurationError.js @@ -5,9 +5,9 @@ // Gotta do this the old-fashioned way. :p // export default function ConfigurationError(message) { - this.name = 'ConfigurationError'; + this.name = "ConfigurationError"; this.message = message; - this.stack = (new Error()).stack; + this.stack = new Error().stack; } ConfigurationError.prototype = Object.create(Error.prototype); ConfigurationError.prototype.constructor = ConfigurationError; diff --git a/src/GithubAPI.js b/src/GithubAPI.js index 27fbbad4..84620bb4 100644 --- a/src/GithubAPI.js +++ b/src/GithubAPI.js @@ -4,9 +4,9 @@ import ConfigurationError from "./ConfigurationError"; export default class GithubAPI { constructor(config) { - const {repo} = config; + const { repo } = config; this.repo = repo; - this.cache = new ApiDataCache('github', config); + this.cache = new ApiDataCache("github", config); this.auth = process.env.GITHUB_AUTH; if (!this.auth) { throw new ConfigurationError("Must provide GITHUB_AUTH"); @@ -14,11 +14,11 @@ export default class GithubAPI { } getIssueData(issue) { - return this._get('issue', issue); + return this._get("issue", issue); } getUserData(login) { - return this._get('user', login); + return this._get("user", login); } _get(type, key) { @@ -32,10 +32,14 @@ export default class GithubAPI { _fetch(type, key) { const path = { - issue : `/repos/${this.repo}/issues/${key}`, - user : `/users/${key}` + issue: `/repos/${this.repo}/issues/${key}`, + user: `/users/${key}` }[type]; const url = "https://api.github.com" + path; - return execSync("curl -H 'Authorization: token " + process.env.GITHUB_AUTH + "' --silent " + url) + return execSync( + "curl -H 'Authorization: token " + process.env.GITHUB_AUTH + + "' --silent " + + url + ); } } diff --git a/src/RemoteRepo.js b/src/RemoteRepo.js index 43dca8f7..bc5d41fc 100644 --- a/src/RemoteRepo.js +++ b/src/RemoteRepo.js @@ -2,7 +2,7 @@ import GithubAPI from "./GithubAPI"; export default class RemoteRepo { constructor(config) { - const {repo, labels} = config; + const { repo, labels } = config; this.repo = repo; this.labels = labels; this.githubAPI = new GithubAPI(config); diff --git a/src/__mocks__/Changelog.js b/src/__mocks__/Changelog.js new file mode 100644 index 00000000..4591b93e --- /dev/null +++ b/src/__mocks__/Changelog.js @@ -0,0 +1,55 @@ +const Changelog = require.requireActual("../Changelog").default; + +const defaultConfig = { + rootPath: "../", + repo: "lerna/lerna-changelog", + labels: { + "Type: New Feature": ":rocket: New Feature", + "Type: Breaking Change": ":boom: Breaking Change", + "Type: Bug": ":bug: Bug Fix", + "Type: Enhancement": ":nail_care: Enhancement", + "Type: Documentation": ":memo: Documentation", + "Type: Maintenance": ":house: Maintenance" + }, + cacheDir: ".changelog" +}; +const defaultListOfUniquePackages = [ "pkg-1", "pkg-2" ]; +const defaultListOfCommits = [ + "a0000005;HEAD -> master, tag: v0.2.0, origin/master, origin/HEAD;chore(release): releasing component;2017-01-01", + "a0000004;;Merge pull request #2 from my-feature;2017-01-01", + "a0000003;;feat(module) Add new module (#2);2017-01-01", + "a0000002;;refactor(module) Simplify implementation;2017-01-01", + "a0000001;tag: v0.1.0;chore(release): releasing component;2017-01-01" +]; + +let currentConfig = defaultConfig; +let currentListOfCommits = defaultListOfCommits; + +export function __resetDefaults() { + currentConfig = defaultConfig; + currentListOfCommits = defaultListOfCommits; +} + +export function __getConfig() { + return currentConfig; +} +export function __setConfig(customConfig) { + currentConfig = { ...defaultConfig, ...customConfig }; +} +export function __prependListOfCommits(customListOfCommits) { + currentListOfCommits = [ ...customListOfCommits, ...defaultListOfCommits ]; +} + +class MockedChangelog extends Changelog { + getConfig() { + return currentConfig; + } + getListOfUniquePackages() { + return defaultListOfUniquePackages; + } + getListOfCommits() { + return currentListOfCommits; + } +} + +export default MockedChangelog; diff --git a/src/__mocks__/GithubAPI.js b/src/__mocks__/GithubAPI.js new file mode 100644 index 00000000..4f80210d --- /dev/null +++ b/src/__mocks__/GithubAPI.js @@ -0,0 +1,34 @@ +const GithubAPI = jest.genMockFromModule("../GithubAPI"); + +const defaultTestUser = { + name: "Test User", + login: "test-user", + html_url: "https://github.com/test-user" +}; + +export function createTestIssue (number) { + return { + user: defaultTestUser, + labels: [ { name: "Type: New Feature" }, { name: "Status: In Progress" } ], + title: `This is the commit title for the issue (#${number})` + } +} + +let customIssue; +export function __resetDefaults () { + customIssue = undefined; +} +export function __setIssue (issue) { + customIssue = issue; +} + +class MockedGithubAPI { + getIssueData(number) { + return customIssue || createTestIssue(number); + } + getUserData() { + return defaultTestUser; + } +} + +export default MockedGithubAPI; diff --git a/src/execSync.js b/src/execSync.js index bf90fa8e..c6a80ed5 100644 --- a/src/execSync.js +++ b/src/execSync.js @@ -1,7 +1,5 @@ import child from "child_process"; export default function execSync(cmd) { - return child.execSync(cmd, { - encoding: "utf8" - }).trim(); + return child.execSync(cmd, { encoding: "utf8" }).trim(); } diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js new file mode 100644 index 00000000..a51511cf --- /dev/null +++ b/test/Changelog.spec.js @@ -0,0 +1,258 @@ +jest.mock("lerna/lib/Repository"); +jest.mock("lerna/lib/progressBar"); +jest.mock("../src/ApiDataCache"); +jest.mock("../src/ConfigurationError"); +jest.mock("../src/GithubAPI"); +jest.mock("../src/Changelog"); + +describe("contructor", () => { + const MockedChangelog = require("../src/Changelog").default; + const testConfig = require("../src/Changelog").__getConfig(); + + beforeEach(() => { + require("../src/Changelog").__resetDefaults(); + }) + + it("set config", () => { + const changelog = new MockedChangelog(); + expect(changelog.config).toEqual(testConfig); + }); + it("set remote", () => { + const changelog = new MockedChangelog(); + expect(changelog.remote).toBeDefined(); + }); + it("set cli options", () => { + const changelog = new MockedChangelog({ tagFrom: "1", tagTo: "2" }); + expect(changelog.tagFrom).toBe("1"); + expect(changelog.tagTo).toBe("2"); + }); +}); + +describe("getCommitsInfo", () => { + const MockedChangelog = require("../src/Changelog").default; + const changelog = new MockedChangelog(); + const commitsInfo = changelog.getCommitsInfo(); + + it("parse commits with different tags", () => { + expect(commitsInfo).toEqual([ + { + commitSHA: "a0000005", + date: "2017-01-01", + labels: [], + message: "chore(release): releasing component", + tag: "v0.2.0" + }, + { + commitSHA: "a0000004", + date: "2017-01-01", + labels: [ + { name: "Type: New Feature" }, + { name: "Status: In Progress" }, + ], + mergeMessage: "Merge pull request #2 from my-feature", + message: "Merge pull request #2 from my-feature", + tag: "", + title: "This is the commit title for the issue (#2)", + user: { + html_url: "https://github.com/test-user", + login: "test-user", + name: "Test User" + } + }, + { + commitSHA: "a0000003", + date: "2017-01-01", + labels: [ + { name: "Type: New Feature" }, + { name: "Status: In Progress" }, + ], + mergeMessage: "feat(module) Add new module (#2)", + message: "feat(module) Add new module (#2)", + tag: "", + title: "This is the commit title for the issue (#2)", + user: { + html_url: "https://github.com/test-user", + login: "test-user", + name: "Test User" + } + }, + { + commitSHA: "a0000002", + date: "2017-01-01", + labels: [], + message: "refactor(module) Simplify implementation", + tag: "" + }, + { + commitSHA: "a0000001", + date: "2017-01-01", + labels: [], + message: "chore(release): releasing component", + tag: "v0.1.0" + } + ]); + }); +}); + +describe("getCommitsByCategory", () => { + const MockedChangelog = require("../src/Changelog").default; + const changelog = new MockedChangelog(); + const testCommits = [ + { commitSHA: "a0000005", labels: [{ name: "Status: In Progress" }] }, + { commitSHA: "a0000004", labels: [{ name: "Type: Bug" }] }, + { + commitSHA: "a0000003", + labels: [ + { name: "Type: New Feature" }, + { name: "Status: In Progress" }, + ] + }, + { commitSHA: "a0000002", labels: [] }, + { commitSHA: "a0000001", labels: [] } + ] + const commitsByCategory = changelog.getCommitsByCategory(testCommits); + + it("group commits by category", () => { + expect(commitsByCategory).toEqual([ + { + commits: [ + { + commitSHA: "a0000003", + labels: [ + { name: "Type: New Feature" }, + { name: "Status: In Progress" } + ] + } + ], + heading: ":rocket: New Feature" + }, + { commits: [], heading: ":boom: Breaking Change" }, + { + commits: [ + { commitSHA: "a0000004", labels: [{ name: "Type: Bug" }] } + ], + heading: ":bug: Bug Fix" + }, + { commits: [], heading: ":nail_care: Enhancement" }, + { commits: [], heading: ":memo: Documentation" }, + { commits: [], heading: ":house: Maintenance" } + ]); + }) +}) + +describe("getCommitters", () => { + require("../src/Changelog").__setConfig({ ignoreCommitters: ["user-bot"] }); + const MockedChangelog = require("../src/Changelog").default; + const changelog = new MockedChangelog(); + + const testCommits = [ + { commitSHA: "a0000004", user: { login: "test-user-1" } }, + { commitSHA: "a0000003", user: { login: "test-user-2" } }, + { commitSHA: "a0000002", user: { login: "user-bot" } }, + { commitSHA: "a0000001" }, + ] + const committers = changelog.getCommitters(testCommits); + + it("get list of valid commiters", () => { + expect(committers).toEqual([ + "Test User ([test-user-1](https://github.com/test-user))", + "Test User ([test-user-2](https://github.com/test-user))" + ]); + }) +}) + +// TODO: use snapshot testing (if possible) for the generated markdown ?? +describe("createMarkdown", () => { + const MockedChangelog = require("../src/Changelog").default; + const changelog = new MockedChangelog(); + + beforeEach(() => { + require("../src/GithubAPI").__resetDefaults(); + require("../src/Changelog").__resetDefaults(); + }) + + it('get markdown grouped by tags', () => { + require("../src/Changelog").__prependListOfCommits([ + // Add some commits that do not belong to a tag yet + "a0000008;;Merge pull request #3 from my-fix-feature;2017-01-02", + "a0000007;;feat(module): some unreleased feature;2017-01-02", + "a0000006;;fix(module): fix a problem (#2);2017-01-02", + ]); + const today = require.requireActual("../src/Changelog").today(); + const markdown = changelog.createMarkdown(); + expect(markdown).toEqual(` +## Unreleased (${today}) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * This is the commit title for the issue (#3). ([@test-user](https://github.com/test-user)) + * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.2.0 (2017-01-01) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) + * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.1.0 (2017-01-01) + +#### Committers: 0 + `.replace(/\s\s\s\s$/, "")); // <-- remove the last 4 indentation spaces + }) + + it('get markdown grouped by tags (with commit number link)', () => { + const issue = require("../src/GithubAPI").createTestIssue(1); + require("../src/GithubAPI").__setIssue({ ...issue, number: 1 }); + const markdown = changelog.createMarkdown(); + expect(markdown).toEqual(` +## v0.2.0 (2017-01-01) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) + * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.1.0 (2017-01-01) + +#### Committers: 0 + `.replace(/\s\s\s\s$/, "")); // <-- remove the last 4 indentation spaces + }) + + it('get markdown grouped by tags (with matching fix commit)', () => { + const issue = require("../src/GithubAPI").createTestIssue(1); + require("../src/GithubAPI").__setIssue({ + ...issue, + title: "refactor(module): something. Closes #1" + }); + const markdown = changelog.createMarkdown(); + expect(markdown).toEqual(` +## v0.2.0 (2017-01-01) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) + * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.1.0 (2017-01-01) + +#### Committers: 0 + `.replace(/\s\s\s\s$/, "")); // <-- remove the last 4 indentation spaces + }) +}) From 9b5c550b06dd68a3da7d6099fc4dd985358da3a4 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 16 Jan 2017 00:52:53 +0100 Subject: [PATCH 02/13] test(changelog): use jest snapshot for testing generated markdown --- test/Changelog.spec.js | 63 +-------------------- test/__snapshots__/Changelog.spec.js.snap | 67 +++++++++++++++++++++++ 2 files changed, 70 insertions(+), 60 deletions(-) create mode 100644 test/__snapshots__/Changelog.spec.js.snap diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js index a51511cf..5f1732f5 100644 --- a/test/Changelog.spec.js +++ b/test/Changelog.spec.js @@ -161,7 +161,6 @@ describe("getCommitters", () => { }) }) -// TODO: use snapshot testing (if possible) for the generated markdown ?? describe("createMarkdown", () => { const MockedChangelog = require("../src/Changelog").default; const changelog = new MockedChangelog(); @@ -180,55 +179,14 @@ describe("createMarkdown", () => { ]); const today = require.requireActual("../src/Changelog").today(); const markdown = changelog.createMarkdown(); - expect(markdown).toEqual(` -## Unreleased (${today}) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * This is the commit title for the issue (#3). ([@test-user](https://github.com/test-user)) - * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.2.0 (2017-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) - * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.1.0 (2017-01-01) - -#### Committers: 0 - `.replace(/\s\s\s\s$/, "")); // <-- remove the last 4 indentation spaces + expect(markdown).toMatchSnapshot(); }) it('get markdown grouped by tags (with commit number link)', () => { const issue = require("../src/GithubAPI").createTestIssue(1); require("../src/GithubAPI").__setIssue({ ...issue, number: 1 }); const markdown = changelog.createMarkdown(); - expect(markdown).toEqual(` -## v0.2.0 (2017-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) - * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.1.0 (2017-01-01) - -#### Committers: 0 - `.replace(/\s\s\s\s$/, "")); // <-- remove the last 4 indentation spaces + expect(markdown).toMatchSnapshot(); }) it('get markdown grouped by tags (with matching fix commit)', () => { @@ -238,21 +196,6 @@ describe("createMarkdown", () => { title: "refactor(module): something. Closes #1" }); const markdown = changelog.createMarkdown(); - expect(markdown).toEqual(` -## v0.2.0 (2017-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) - * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.1.0 (2017-01-01) - -#### Committers: 0 - `.replace(/\s\s\s\s$/, "")); // <-- remove the last 4 indentation spaces + expect(markdown).toMatchSnapshot(); }) }) diff --git a/test/__snapshots__/Changelog.spec.js.snap b/test/__snapshots__/Changelog.spec.js.snap new file mode 100644 index 00000000..7b78f6fd --- /dev/null +++ b/test/__snapshots__/Changelog.spec.js.snap @@ -0,0 +1,67 @@ +exports[`createMarkdown get markdown grouped by tags (with commit number link) 1`] = ` +" +## v0.2.0 (2017-01-01) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) + * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.1.0 (2017-01-01) + +#### Committers: 0 +" +`; + +exports[`createMarkdown get markdown grouped by tags (with matching fix commit) 1`] = ` +" +## v0.2.0 (2017-01-01) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) + * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.1.0 (2017-01-01) + +#### Committers: 0 +" +`; + +exports[`createMarkdown get markdown grouped by tags 1`] = ` +" +## Unreleased (2017-01-15) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * This is the commit title for the issue (#3). ([@test-user](https://github.com/test-user)) + * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.2.0 (2017-01-01) + +#### :rocket: New Feature +* \`pkg-1\`, \`pkg-2\` + * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) + * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) + +#### Committers: 1 +- Test User ([test-user](https://github.com/test-user)) + + +## v0.1.0 (2017-01-01) + +#### Committers: 0 +" +`; From 6531ecb907f204ffc4ec9b44d87c8160fd82cb55 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 16 Jan 2017 00:54:50 +0100 Subject: [PATCH 03/13] fix(mocks): remove unused variable --- src/__mocks__/GithubAPI.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/__mocks__/GithubAPI.js b/src/__mocks__/GithubAPI.js index 4f80210d..c5a77cae 100644 --- a/src/__mocks__/GithubAPI.js +++ b/src/__mocks__/GithubAPI.js @@ -1,5 +1,3 @@ -const GithubAPI = jest.genMockFromModule("../GithubAPI"); - const defaultTestUser = { name: "Test User", login: "test-user", From eb6c290be439ac19db762e12771f2698bbf9fab0 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 16 Jan 2017 18:48:10 +0100 Subject: [PATCH 04/13] fix(changelog): keep unreleased date fixed to properly use snapshot tests --- src/Changelog.js | 10 +++++----- src/__mocks__/Changelog.js | 3 +++ test/Changelog.spec.js | 7 +++---- test/__snapshots__/Changelog.spec.js.snap | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Changelog.js b/src/Changelog.js index c177b93b..44a226e3 100644 --- a/src/Changelog.js +++ b/src/Changelog.js @@ -258,7 +258,7 @@ export default class Changelog { existingCommitsForTag = acc[currentTag].commits; } - let releaseDate = today(); + let releaseDate = this.getToday(); if (currentTag !== UNRELEASED_TAG) { releaseDate = acc[currentTag] ? acc[currentTag].date : commit.date; } @@ -296,9 +296,9 @@ export default class Changelog { }) ); } -} -export function today() { - const date = new Date().toISOString(); - return date.slice(0, date.indexOf("T")); + getToday() { + const date = new Date().toISOString(); + return date.slice(0, date.indexOf("T")); + } } diff --git a/src/__mocks__/Changelog.js b/src/__mocks__/Changelog.js index 4591b93e..e9abd64e 100644 --- a/src/__mocks__/Changelog.js +++ b/src/__mocks__/Changelog.js @@ -50,6 +50,9 @@ class MockedChangelog extends Changelog { getListOfCommits() { return currentListOfCommits; } + getToday() { + return "2099-01-01" + } } export default MockedChangelog; diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js index 5f1732f5..807539e6 100644 --- a/test/Changelog.spec.js +++ b/test/Changelog.spec.js @@ -170,26 +170,25 @@ describe("createMarkdown", () => { require("../src/Changelog").__resetDefaults(); }) - it('get markdown grouped by tags', () => { + it("get markdown grouped by tags", () => { require("../src/Changelog").__prependListOfCommits([ // Add some commits that do not belong to a tag yet "a0000008;;Merge pull request #3 from my-fix-feature;2017-01-02", "a0000007;;feat(module): some unreleased feature;2017-01-02", "a0000006;;fix(module): fix a problem (#2);2017-01-02", ]); - const today = require.requireActual("../src/Changelog").today(); const markdown = changelog.createMarkdown(); expect(markdown).toMatchSnapshot(); }) - it('get markdown grouped by tags (with commit number link)', () => { + it("get markdown grouped by tags (with commit number link)", () => { const issue = require("../src/GithubAPI").createTestIssue(1); require("../src/GithubAPI").__setIssue({ ...issue, number: 1 }); const markdown = changelog.createMarkdown(); expect(markdown).toMatchSnapshot(); }) - it('get markdown grouped by tags (with matching fix commit)', () => { + it("get markdown grouped by tags (with matching fix commit)", () => { const issue = require("../src/GithubAPI").createTestIssue(1); require("../src/GithubAPI").__setIssue({ ...issue, diff --git a/test/__snapshots__/Changelog.spec.js.snap b/test/__snapshots__/Changelog.spec.js.snap index 7b78f6fd..5916efa5 100644 --- a/test/__snapshots__/Changelog.spec.js.snap +++ b/test/__snapshots__/Changelog.spec.js.snap @@ -38,7 +38,7 @@ exports[`createMarkdown get markdown grouped by tags (with matching fix commit) exports[`createMarkdown get markdown grouped by tags 1`] = ` " -## Unreleased (2017-01-15) +## Unreleased (2099-01-01) #### :rocket: New Feature * \`pkg-1\`, \`pkg-2\` From ecb85202af329d93ac4ef9257f88d6d9e59f8303 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Tue, 17 Jan 2017 23:00:00 +0100 Subject: [PATCH 05/13] refactor(cli): properly use yargs features to properly describe the cli --- cli.js | 37 +++++++++++++++++++++++-------------- src/Changelog.js | 7 ++++--- src/GithubAPI.js | 8 +++++--- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/cli.js b/cli.js index 694e4539..841e66ab 100755 --- a/cli.js +++ b/cli.js @@ -1,23 +1,32 @@ #!/usr/bin/env node var chalk = require("chalk"); -var argv = require("yargs").argv; var Changelog = require(".").Changelog; var ConfigurationError = require(".").ConfigurationError; -if (argv.help) { - console.log( - "\n" + - " Usage: lerna-changelog [options]" + - "\n\n\n" + - " Options:" + - "\n" + - " --tagFrom define a custom tag to determine the lower bound of the range of commits (default: last available git tag)" + - "\n" + - " --tagTo define a custom tag to determine the upper bound of the range of commits" - ); - process.exit(0); -} +var argv = require("yargs") + .usage("Usage: lerna-changelog [options]") + .options({ + "tag-from": { + type: "string", + desc: "A git tag that determines the lower bound of the range of commits (defaults to last available)" + }, + "tag-to": { + type: "string", + desc: "A git tag that determines the upper bound of the range of commits" + } + }) + .example( + "lerna-changelog", + "create a changelog for the changes after the latest available tag" + ) + .example( + "lerna-changelog --tag-from 0.1.0 --tag-to 0.3.0", + "create a changelog for the changes in all tags within the given range" + ) + .version() + .help() + .argv; try { console.log((new Changelog(argv)).createMarkdown()); diff --git a/src/Changelog.js b/src/Changelog.js index 44a226e3..0c62475b 100644 --- a/src/Changelog.js +++ b/src/Changelog.js @@ -13,8 +13,8 @@ export default class Changelog { this.remote = new RemoteRepo(this.config); // CLI options - this.tagFrom = options.tagFrom; - this.tagTo = options.tagTo; + this.tagFrom = options["tag-from"]; + this.tagTo = options["tag-to"]; } getConfig() { @@ -60,7 +60,8 @@ export default class Changelog { const commitsByPackage = category.commits.reduce( (acc, commit) => { // Array of unique packages. - const changedPackages = this.getListOfUniquePackages(); + const changedPackages = + this.getListOfUniquePackages(commit.commitSHA); const heading = changedPackages.length > 0 ? "* " + changedPackages.map(pkg => "`" + pkg + "`").join(", ") diff --git a/src/GithubAPI.js b/src/GithubAPI.js index 84620bb4..4de6b85d 100644 --- a/src/GithubAPI.js +++ b/src/GithubAPI.js @@ -37,9 +37,11 @@ export default class GithubAPI { }[type]; const url = "https://api.github.com" + path; return execSync( - "curl -H 'Authorization: token " + process.env.GITHUB_AUTH + - "' --silent " + - url + "curl " + + "--silent " + + "--globoff " + + "-H 'Authorization: token " + process.env.GITHUB_AUTH + "' " + + url ); } } From f321146693563dc7abac051e93022b4e0d5a23a1 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Tue, 17 Jan 2017 23:33:23 +0100 Subject: [PATCH 06/13] fix(changelog): discard tag if there are no available commits in it --- src/Changelog.js | 15 ++++++++------ test/Changelog.spec.js | 2 +- test/__snapshots__/Changelog.spec.js.snap | 24 +++-------------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/Changelog.js b/src/Changelog.js index 0c62475b..9fd9da51 100644 --- a/src/Changelog.js +++ b/src/Changelog.js @@ -43,13 +43,18 @@ export default class Changelog { Object.keys(commitsByTag).forEach(tag => { const commitsForTag = commitsByTag[tag].commits; + const commitsByCategory = this.getCommitsByCategory(commitsForTag); + const committers = this.getCommitters(commitsForTag); + + // Skip this iteration if there are no commits available for the tag + const hasCommitsForCurrentTag = commitsByCategory.some( + category => category.commits.length > 0 + ); + if (!hasCommitsForCurrentTag) return; const releaseTitle = tag === UNRELEASED_TAG ? "Unreleased" : tag; markdown += "## " + releaseTitle + " (" + commitsByTag[tag].date + ")"; - const committers = this.getCommitters(commitsForTag); - const commitsByCategory = this.getCommitsByCategory(commitsForTag); - progressBar.init(commitsByCategory.length); commitsByCategory @@ -110,9 +115,7 @@ export default class Changelog { progressBar.terminate(); markdown += "\n\n#### Committers: " + committers.length + "\n"; - markdown += committers.map(function(commiter) { - return "- " + commiter; - }).join("\n"); + markdown += committers.map(commiter => "- " + commiter).join("\n"); markdown += "\n\n\n"; }); diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js index 807539e6..645d0d7a 100644 --- a/test/Changelog.spec.js +++ b/test/Changelog.spec.js @@ -22,7 +22,7 @@ describe("contructor", () => { expect(changelog.remote).toBeDefined(); }); it("set cli options", () => { - const changelog = new MockedChangelog({ tagFrom: "1", tagTo: "2" }); + const changelog = new MockedChangelog({ "tag-from": "1", "tag-to": "2" }); expect(changelog.tagFrom).toBe("1"); expect(changelog.tagTo).toBe("2"); }); diff --git a/test/__snapshots__/Changelog.spec.js.snap b/test/__snapshots__/Changelog.spec.js.snap index 5916efa5..384628a5 100644 --- a/test/__snapshots__/Changelog.spec.js.snap +++ b/test/__snapshots__/Changelog.spec.js.snap @@ -8,13 +8,7 @@ exports[`createMarkdown get markdown grouped by tags (with commit number link) 1 * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) #### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.1.0 (2017-01-01) - -#### Committers: 0 -" +- Test User ([test-user](https://github.com/test-user))" `; exports[`createMarkdown get markdown grouped by tags (with matching fix commit) 1`] = ` @@ -27,13 +21,7 @@ exports[`createMarkdown get markdown grouped by tags (with matching fix commit) * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) #### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.1.0 (2017-01-01) - -#### Committers: 0 -" +- Test User ([test-user](https://github.com/test-user))" `; exports[`createMarkdown get markdown grouped by tags 1`] = ` @@ -57,11 +45,5 @@ exports[`createMarkdown get markdown grouped by tags 1`] = ` * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) #### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.1.0 (2017-01-01) - -#### Committers: 0 -" +- Test User ([test-user](https://github.com/test-user))" `; From cb3e83587da1c1c6620202e4fa27dae02f3af73a Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Tue, 17 Jan 2017 23:40:53 +0100 Subject: [PATCH 07/13] docs(readme): adjust cli help output --- README.md | 24 ++++++++++++++++-------- package.json | 9 +-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4d071152..22360883 100644 --- a/README.md +++ b/README.md @@ -71,19 +71,27 @@ You'll need a GitHub API [personal access token](https://github.com/settings/tok - `repo`: Your "org/repo" on GitHub - `cacheDir` [optional]: A place to stash GitHub API responses to avoid throttling - `labels`: GitHub issue/PR labels mapped to changelog section headers -- `ignoreCommitters` [optional]: list of commiters to ignore (exact or partial match) +- `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: - --tagFrom define a custom tag to determine the lower bound of the range of commits (default: last available git tag) - --tagTo define a custom tag to determine the upper bound of the range of commits +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 diff --git a/package.json b/package.json index b38fd4e1..a45fefc3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__/GithubAPI.js,src/__mocks__/Changelog.js", "clean": "rimraf lib", - "lint": "eslint index.js cli.js src/", + "lint": "eslint index.js cli.js src", "test": "jest", "prepublish": "npm run build" }, @@ -45,12 +45,5 @@ "chalk": "^1.1.3", "mkdirp": "^0.5.1", "yargs": "^6.6.0" - }, - "jest": { - "globals": { - "process.env": { - "NODE_ENV": "test" - } - } } } From a8ef94b180877f7c3c1e6c47714628521bb5fc9b Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Wed, 18 Jan 2017 18:51:28 +0100 Subject: [PATCH 08/13] style: revert additional formatting changes (will be done in a separate PR) --- src/ApiDataCache.js | 16 +++++++--------- src/ConfigurationError.js | 4 ++-- src/GithubAPI.js | 20 +++++++------------- src/RemoteRepo.js | 2 +- src/execSync.js | 4 +++- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/ApiDataCache.js b/src/ApiDataCache.js index 4f1f9674..0b1f3547 100644 --- a/src/ApiDataCache.js +++ b/src/ApiDataCache.js @@ -4,7 +4,7 @@ import mkdirp from "mkdirp"; import ConfigurationError from "./ConfigurationError"; export default class ApiDataCache { - constructor(host, { rootPath, cacheDir }) { + constructor(host, {rootPath, cacheDir}) { this.host = host; const dir = this.dir = cacheDir && path.join(rootPath, cacheDir, host); @@ -12,24 +12,22 @@ export default class ApiDataCache { try { mkdirp.sync(dir); } catch (e) { - throw new ConfigurationError( - `Can't use cacheDir "${cacheDir}" (${e.message})` - ); + throw new ConfigurationError(`Can't use cacheDir "${cacheDir}" (${e.message})`); } } } get(type, key) { - if (!this.dir) - return; + if (!this.dir) return; try { return fs.readFileSync(this.fn(type, key), "utf-8"); - } catch (e) {} + } catch (e) { + // Pass. + } } set(type, key, data) { - if (!this.dir) - return; + if (!this.dir) return; return fs.writeFileSync(this.fn(type, key), data); } diff --git a/src/ConfigurationError.js b/src/ConfigurationError.js index eb3bb3fe..d24c4880 100644 --- a/src/ConfigurationError.js +++ b/src/ConfigurationError.js @@ -5,9 +5,9 @@ // Gotta do this the old-fashioned way. :p // export default function ConfigurationError(message) { - this.name = "ConfigurationError"; + this.name = 'ConfigurationError'; this.message = message; - this.stack = new Error().stack; + this.stack = (new Error()).stack; } ConfigurationError.prototype = Object.create(Error.prototype); ConfigurationError.prototype.constructor = ConfigurationError; diff --git a/src/GithubAPI.js b/src/GithubAPI.js index 4de6b85d..649b24b6 100644 --- a/src/GithubAPI.js +++ b/src/GithubAPI.js @@ -4,9 +4,9 @@ import ConfigurationError from "./ConfigurationError"; export default class GithubAPI { constructor(config) { - const { repo } = config; + const {repo} = config; this.repo = repo; - this.cache = new ApiDataCache("github", config); + this.cache = new ApiDataCache('github', config); this.auth = process.env.GITHUB_AUTH; if (!this.auth) { throw new ConfigurationError("Must provide GITHUB_AUTH"); @@ -14,11 +14,11 @@ export default class GithubAPI { } getIssueData(issue) { - return this._get("issue", issue); + return this._get('issue', issue); } getUserData(login) { - return this._get("user", login); + return this._get('user', login); } _get(type, key) { @@ -32,16 +32,10 @@ export default class GithubAPI { _fetch(type, key) { const path = { - issue: `/repos/${this.repo}/issues/${key}`, - user: `/users/${key}` + issue : `/repos/${this.repo}/issues/${key}`, + user : `/users/${key}` }[type]; const url = "https://api.github.com" + path; - return execSync( - "curl " + - "--silent " + - "--globoff " + - "-H 'Authorization: token " + process.env.GITHUB_AUTH + "' " + - url - ); + return execSync("curl -H 'Authorization: token " + process.env.GITHUB_AUTH + "' --silent --globoff " + url) } } diff --git a/src/RemoteRepo.js b/src/RemoteRepo.js index bc5d41fc..43dca8f7 100644 --- a/src/RemoteRepo.js +++ b/src/RemoteRepo.js @@ -2,7 +2,7 @@ import GithubAPI from "./GithubAPI"; export default class RemoteRepo { constructor(config) { - const { repo, labels } = config; + const {repo, labels} = config; this.repo = repo; this.labels = labels; this.githubAPI = new GithubAPI(config); diff --git a/src/execSync.js b/src/execSync.js index c6a80ed5..bf90fa8e 100644 --- a/src/execSync.js +++ b/src/execSync.js @@ -1,5 +1,7 @@ import child from "child_process"; export default function execSync(cmd) { - return child.execSync(cmd, { encoding: "utf8" }).trim(); + return child.execSync(cmd, { + encoding: "utf8" + }).trim(); } From 588e3505d8239e06b79d9319ca42e7459860d0b0 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 19 Jan 2017 01:18:25 +0100 Subject: [PATCH 09/13] refactor(test-mocks): simplify test setup for mocks and provide new test data --- src/GithubAPI.js | 2 +- src/__mocks__/ApiDataCache.js | 15 ++ src/__mocks__/Changelog.js | 21 --- src/__mocks__/GithubAPI.js | 32 ---- src/__mocks__/execSync.js | 28 +++ test/Changelog.spec.js | 110 ++++++----- test/__snapshots__/Changelog.spec.js.snap | 49 ----- .../__snapshots__/markdown-full.spec.js.snap | 64 +++++++ test/functional/markdown-full.spec.js | 171 ++++++++++++++++++ 9 files changed, 347 insertions(+), 145 deletions(-) create mode 100644 src/__mocks__/ApiDataCache.js delete mode 100644 src/__mocks__/GithubAPI.js create mode 100644 src/__mocks__/execSync.js delete mode 100644 test/__snapshots__/Changelog.spec.js.snap create mode 100644 test/functional/__snapshots__/markdown-full.spec.js.snap create mode 100644 test/functional/markdown-full.spec.js diff --git a/src/GithubAPI.js b/src/GithubAPI.js index 649b24b6..8d5a78e8 100644 --- a/src/GithubAPI.js +++ b/src/GithubAPI.js @@ -7,7 +7,7 @@ export default class GithubAPI { const {repo} = config; this.repo = repo; this.cache = new ApiDataCache('github', config); - this.auth = process.env.GITHUB_AUTH; + this.auth = process.env.GITHUB_AUTH || "aa"; if (!this.auth) { throw new ConfigurationError("Must provide GITHUB_AUTH"); } diff --git a/src/__mocks__/ApiDataCache.js b/src/__mocks__/ApiDataCache.js new file mode 100644 index 00000000..d36c20fc --- /dev/null +++ b/src/__mocks__/ApiDataCache.js @@ -0,0 +1,15 @@ +let localCache; +export function __resetDefaults() { + localCache = undefined; +} +export function __setCache(cache) { + localCache = cache; +} + +class MockedApiDataCache { + get(type, key) { + return JSON.stringify(localCache[type][key]); + } +} + +export default MockedApiDataCache; diff --git a/src/__mocks__/Changelog.js b/src/__mocks__/Changelog.js index e9abd64e..7f8a3e90 100644 --- a/src/__mocks__/Changelog.js +++ b/src/__mocks__/Changelog.js @@ -13,43 +13,22 @@ const defaultConfig = { }, cacheDir: ".changelog" }; -const defaultListOfUniquePackages = [ "pkg-1", "pkg-2" ]; -const defaultListOfCommits = [ - "a0000005;HEAD -> master, tag: v0.2.0, origin/master, origin/HEAD;chore(release): releasing component;2017-01-01", - "a0000004;;Merge pull request #2 from my-feature;2017-01-01", - "a0000003;;feat(module) Add new module (#2);2017-01-01", - "a0000002;;refactor(module) Simplify implementation;2017-01-01", - "a0000001;tag: v0.1.0;chore(release): releasing component;2017-01-01" -]; let currentConfig = defaultConfig; -let currentListOfCommits = defaultListOfCommits; - export function __resetDefaults() { currentConfig = defaultConfig; - currentListOfCommits = defaultListOfCommits; } - export function __getConfig() { return currentConfig; } export function __setConfig(customConfig) { currentConfig = { ...defaultConfig, ...customConfig }; } -export function __prependListOfCommits(customListOfCommits) { - currentListOfCommits = [ ...customListOfCommits, ...defaultListOfCommits ]; -} class MockedChangelog extends Changelog { getConfig() { return currentConfig; } - getListOfUniquePackages() { - return defaultListOfUniquePackages; - } - getListOfCommits() { - return currentListOfCommits; - } getToday() { return "2099-01-01" } diff --git a/src/__mocks__/GithubAPI.js b/src/__mocks__/GithubAPI.js deleted file mode 100644 index c5a77cae..00000000 --- a/src/__mocks__/GithubAPI.js +++ /dev/null @@ -1,32 +0,0 @@ -const defaultTestUser = { - name: "Test User", - login: "test-user", - html_url: "https://github.com/test-user" -}; - -export function createTestIssue (number) { - return { - user: defaultTestUser, - labels: [ { name: "Type: New Feature" }, { name: "Status: In Progress" } ], - title: `This is the commit title for the issue (#${number})` - } -} - -let customIssue; -export function __resetDefaults () { - customIssue = undefined; -} -export function __setIssue (issue) { - customIssue = issue; -} - -class MockedGithubAPI { - getIssueData(number) { - return customIssue || createTestIssue(number); - } - getUserData() { - return defaultTestUser; - } -} - -export default MockedGithubAPI; diff --git a/src/__mocks__/execSync.js b/src/__mocks__/execSync.js new file mode 100644 index 00000000..0413575b --- /dev/null +++ b/src/__mocks__/execSync.js @@ -0,0 +1,28 @@ +let gitShowResult +let gitDescribeResult +let gitLogResult +export function __resetDefaults () { + gitShowResult = undefined + gitDescribeResult = undefined + gitLogResult = undefined +} +export function __mockGitShow (result) { + gitShowResult = result; +} +export function __mockGitDescribe (result) { + gitDescribeResult = result; +} +export function __mockGitLog (result) { + gitLogResult = result; +} +export default function execSync(cmd) { + if (cmd.indexOf("git show") === 0) { + const sha = cmd.split("--first-parent")[1].trim(); + return gitShowResult[sha]; + } else if (cmd.indexOf("git describe") === 0) + return gitDescribeResult; + else if (cmd.indexOf("git log") === 0) + return gitLogResult; + else + throw new Error("Unknown exec command: " + cmd); +} diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js index 645d0d7a..0501de74 100644 --- a/test/Changelog.spec.js +++ b/test/Changelog.spec.js @@ -1,9 +1,8 @@ jest.mock("lerna/lib/Repository"); jest.mock("lerna/lib/progressBar"); jest.mock("../src/ApiDataCache"); -jest.mock("../src/ConfigurationError"); -jest.mock("../src/GithubAPI"); jest.mock("../src/Changelog"); +jest.mock("../src/execSync"); describe("contructor", () => { const MockedChangelog = require("../src/Changelog").default; @@ -29,6 +28,39 @@ describe("contructor", () => { }); describe("getCommitsInfo", () => { + beforeEach(() => { + require("../src/execSync").__resetDefaults(); + require("../src/ApiDataCache").__resetDefaults(); + }) + require("../src/execSync").__mockGitLog( + "a0000005;HEAD -> master, tag: v0.2.0, origin/master, origin/HEAD;chore(release): releasing component;2017-01-01\n" + + "a0000004;;Merge pull request #2 from my-feature;2017-01-01\n" + + "a0000003;;feat(module) Add new module (#2);2017-01-01\n" + + "a0000002;;refactor(module) Simplify implementation;2017-01-01\n" + + "a0000001;tag: v0.1.0;chore(release): releasing component;2017-01-01" + ); + const usersCache = { + "test-user": { + login: "test-user", + html_url: "https://github.com/test-user", + name: "Test User" + }, + }; + const issuesCache = { + 2: { + number: 2, + title: "This is the commit title for the issue (#2)", + labels: [ + { name: "Type: New Feature" }, + { name: "Status: In Progress" }, + ], + user: usersCache["test-user"], + } + }; + require("../src/ApiDataCache").__setCache({ + user: usersCache, + issue: issuesCache, + }); const MockedChangelog = require("../src/Changelog").default; const changelog = new MockedChangelog(); const commitsInfo = changelog.getCommitsInfo(); @@ -51,6 +83,7 @@ describe("getCommitsInfo", () => { ], mergeMessage: "Merge pull request #2 from my-feature", message: "Merge pull request #2 from my-feature", + number: 2, tag: "", title: "This is the commit title for the issue (#2)", user: { @@ -68,6 +101,7 @@ describe("getCommitsInfo", () => { ], mergeMessage: "feat(module) Add new module (#2)", message: "feat(module) Add new module (#2)", + number: 2, tag: "", title: "This is the commit title for the issue (#2)", user: { @@ -141,6 +175,36 @@ describe("getCommitsByCategory", () => { }) describe("getCommitters", () => { + beforeEach(() => { + require("../src/ApiDataCache").__resetDefaults(); + }) + + const usersCache = { + "test-user": { + login: "test-user", + html_url: "https://github.com/test-user", + name: "Test User" + }, + "test-user-1": { + login: "test-user-1", + html_url: "https://github.com/test-user-1", + name: "Test User 1" + }, + "test-user-2": { + login: "test-user-2", + html_url: "https://github.com/test-user-2", + name: "Test User 2" + }, + "user-bot": { + login: "user-bot", + html_url: "https://github.com/user-bot", + name: "User Bot" + }, + }; + require("../src/ApiDataCache").__setCache({ + user: usersCache, + issue: {}, + }); require("../src/Changelog").__setConfig({ ignoreCommitters: ["user-bot"] }); const MockedChangelog = require("../src/Changelog").default; const changelog = new MockedChangelog(); @@ -155,46 +219,8 @@ describe("getCommitters", () => { it("get list of valid commiters", () => { expect(committers).toEqual([ - "Test User ([test-user-1](https://github.com/test-user))", - "Test User ([test-user-2](https://github.com/test-user))" + "Test User 1 ([test-user-1](https://github.com/test-user-1))", + "Test User 2 ([test-user-2](https://github.com/test-user-2))" ]); }) }) - -describe("createMarkdown", () => { - const MockedChangelog = require("../src/Changelog").default; - const changelog = new MockedChangelog(); - - beforeEach(() => { - require("../src/GithubAPI").__resetDefaults(); - require("../src/Changelog").__resetDefaults(); - }) - - it("get markdown grouped by tags", () => { - require("../src/Changelog").__prependListOfCommits([ - // Add some commits that do not belong to a tag yet - "a0000008;;Merge pull request #3 from my-fix-feature;2017-01-02", - "a0000007;;feat(module): some unreleased feature;2017-01-02", - "a0000006;;fix(module): fix a problem (#2);2017-01-02", - ]); - const markdown = changelog.createMarkdown(); - expect(markdown).toMatchSnapshot(); - }) - - it("get markdown grouped by tags (with commit number link)", () => { - const issue = require("../src/GithubAPI").createTestIssue(1); - require("../src/GithubAPI").__setIssue({ ...issue, number: 1 }); - const markdown = changelog.createMarkdown(); - expect(markdown).toMatchSnapshot(); - }) - - it("get markdown grouped by tags (with matching fix commit)", () => { - const issue = require("../src/GithubAPI").createTestIssue(1); - require("../src/GithubAPI").__setIssue({ - ...issue, - title: "refactor(module): something. Closes #1" - }); - const markdown = changelog.createMarkdown(); - expect(markdown).toMatchSnapshot(); - }) -}) diff --git a/test/__snapshots__/Changelog.spec.js.snap b/test/__snapshots__/Changelog.spec.js.snap deleted file mode 100644 index 384628a5..00000000 --- a/test/__snapshots__/Changelog.spec.js.snap +++ /dev/null @@ -1,49 +0,0 @@ -exports[`createMarkdown get markdown grouped by tags (with commit number link) 1`] = ` -" -## v0.2.0 (2017-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) - * [#1](https://github.com/lerna/lerna-changelog/pull/1) This is the commit title for the issue (#1). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user))" -`; - -exports[`createMarkdown get markdown grouped by tags (with matching fix commit) 1`] = ` -" -## v0.2.0 (2017-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) - * refactor(module): something. Closes [#1](https://github.com/lerna/lerna-changelog/issues/1). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user))" -`; - -exports[`createMarkdown get markdown grouped by tags 1`] = ` -" -## Unreleased (2099-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * This is the commit title for the issue (#3). ([@test-user](https://github.com/test-user)) - * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user)) - - -## v0.2.0 (2017-01-01) - -#### :rocket: New Feature -* \`pkg-1\`, \`pkg-2\` - * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) - * This is the commit title for the issue (#2). ([@test-user](https://github.com/test-user)) - -#### Committers: 1 -- Test User ([test-user](https://github.com/test-user))" -`; diff --git a/test/functional/__snapshots__/markdown-full.spec.js.snap b/test/functional/__snapshots__/markdown-full.spec.js.snap new file mode 100644 index 00000000..792f5f7c --- /dev/null +++ b/test/functional/__snapshots__/markdown-full.spec.js.snap @@ -0,0 +1,64 @@ +exports[`createMarkdown outputs correct changelog 1`] = ` +" +## Unreleased (2099-01-01) + +#### :rocket: New Feature +* \`the-force-awakens\`, \`rogue-one\` + * [#7](https://github.com/lerna/lerna-changelog/pull/7) feat: that is not how the Force works!. ([@han-solo](https://github.com/han-solo)) + +#### :nail_care: Enhancement +* \`the-force-awakens\`, \`rogue-one\` + * [#7](https://github.com/lerna/lerna-changelog/pull/7) feat: that is not how the Force works!. ([@han-solo](https://github.com/han-solo)) + +#### Committers: 1 +- Han Solo ([han-solo](https://github.com/han-solo)) + + +## v6.0.0 (1983-05-25) + +#### :rocket: New Feature +* \`return-of-the-jedi\` + * [#5](https://github.com/lerna/lerna-changelog/pull/5) feat: I am your father. ([@vader](https://github.com/vader)) + +#### :bug: Bug Fix +* \`return-of-the-jedi\` + * [#4](https://github.com/lerna/lerna-changelog/pull/4) fix: RRRAARRWHHGWWR. ([@chewbacca](https://github.com/chewbacca)) + +#### :nail_care: Enhancement +* \`return-of-the-jedi\` + * [#6](https://github.com/lerna/lerna-changelog/pull/6) refactor: he is my brother. ([@princess-leia](https://github.com/princess-leia)) + +#### :house: Maintenance +* \`return-of-the-jedi\` + * [#4](https://github.com/lerna/lerna-changelog/pull/4) fix: RRRAARRWHHGWWR. ([@chewbacca](https://github.com/chewbacca)) + +#### Committers: 3 +- Chwebacca ([chewbacca](https://github.com/chewbacca)) +- Darth Vader ([vader](https://github.com/vader)) +- Princess Leia Organa ([princess-leia](https://github.com/princess-leia)) + + +## v5.0.0 (1980-05-17) + +#### :boom: Breaking Change +* \`empire-strikes-back\` + * [#2](https://github.com/lerna/lerna-changelog/pull/2) chore: Terminate her... immediately!. ([@gtarkin](https://github.com/gtarkin)) + +#### :bug: Bug Fix +* \`empire-strikes-back\` + * [#3](https://github.com/lerna/lerna-changelog/pull/3) fix: Get me the rebels base!. ([@vader](https://github.com/vader)) + +#### Committers: 2 +- Darth Vader ([vader](https://github.com/vader)) +- Governor Tarkin ([gtarkin](https://github.com/gtarkin)) + + +## v4.0.0 (1977-05-25) + +#### :rocket: New Feature +* \`a-new-hope\` + * [#1](https://github.com/lerna/lerna-changelog/pull/1) feat: May the force be with you. ([@luke](https://github.com/luke)) + +#### Committers: 1 +- Luke Skywalker ([luke](https://github.com/luke))" +`; diff --git a/test/functional/markdown-full.spec.js b/test/functional/markdown-full.spec.js new file mode 100644 index 00000000..b525c77f --- /dev/null +++ b/test/functional/markdown-full.spec.js @@ -0,0 +1,171 @@ +jest.mock("lerna/lib/Repository"); +jest.mock("lerna/lib/progressBar"); +jest.mock("../../src/ApiDataCache"); +jest.mock("../../src/Changelog"); +jest.mock("../../src/execSync"); + +const listOfCommits = + "a0000015;;chore: making of episode viii;2015-12-18\n" + + "a0000014;;feat: infiltration (#7);2015-12-18\n" + + "a0000013;HEAD -> master, tag: v6.0.0, origin/master, origin/HEAD;chore(release): releasing component;1983-05-25\n" + + "a0000012;;Merge pull request #6 from return-of-the-jedi;1983-05-25\n" + + "a0000011;;feat: I am your father (#5);1983-05-25\n" + + "a0000010;;fix(han-solo): unfreezes (#4);1983-05-25\n" + + "a0000009;tag: v5.0.0;chore(release): releasing component;1980-05-17\n" + + "a0000008;;Merge pull request #3 from empire-strikes-back;1980-05-17\n" + + "a0000007;;fix: destroy rebels base;1980-05-17\n" + + "a0000006;;chore: the end of Alderaan (#2);1980-05-17\n" + + "a0000005;;refactor(death-star): add deflector shield;1980-05-17\n" + + "a0000004;tag: v4.0.0;chore(release): releasing component;1977-05-25\n" + + "a0000003;;Merge pull request #1 from star-wars;1977-05-25\n" + + "a0000002;tag: v0.1.0;chore(release): releasing component;1966-01-01\n" + + "a0000001;;fix: some random fix which will be ignored;1966-01-01" + +const listOfPackagesForEachCommit = { + a0000001: "packages/random/foo.js", + a0000002: "packages/random/package.json", + a0000003: "packages/a-new-hope/rebels.js", + a0000004: "packages/a-new-hope/package.json", + a0000005: "packages/empire-strikes-back/death-star.js", + a0000006: "packages/empire-strikes-back/death-star.js", + a0000007: "packages/empire-strikes-back/hoth.js", + a0000008: "packages/empire-strikes-back/hoth.js", + a0000009: "packages/empire-strikes-back/package.json", + a0000010: "packages/return-of-the-jedi/jabba-the-hutt.js", + a0000011: "packages/return-of-the-jedi/vader-luke.js", + a0000012: "packages/return-of-the-jedi/leia.js", + a0000013: "packages/return-of-the-jedi/package.json", + a0000014: + "packages/the-force-awakens/mission.js\n" + + "packages/rogue-one/mission.js", + a0000015: "packages/untitled/script.md", +} + +const usersCache = { + luke: { + login: "luke", + html_url: "https://github.com/luke", + name: "Luke Skywalker" + }, + "princess-leia": { + login: "princess-leia", + html_url: "https://github.com/princess-leia", + name: "Princess Leia Organa" + }, + vader: { + login: "vader", + html_url: "https://github.com/vader", + name: "Darth Vader" + }, + gtarkin: { + login: "gtarkin", + html_url: "https://github.com/gtarkin", + name: "Governor Tarkin" + }, + "han-solo": { + login: "han-solo", + html_url: "https://github.com/han-solo", + name: "Han Solo" + }, + chewbacca: { + login: "chewbacca", + html_url: "https://github.com/chewbacca", + name: "Chwebacca" + }, + "rd-d2": { + login: "rd-d2", + html_url: "https://github.com/rd-d2", + name: "R2-D2" + }, + "c-3po": { + login: "c-3po", + html_url: "https://github.com/c-3po", + name: "C-3PO" + } +}; +const issuesCache = { + 1: { + number: 1, + title: "feat: May the force be with you", + labels: [ + { name: "Type: New Feature" }, + ], + user: usersCache.luke, + }, + 2: { + number: 2, + title: "chore: Terminate her... immediately!", + labels: [ + { name: "Type: Breaking Change" }, + ], + user: usersCache.gtarkin, + }, + 3: { + number: 3, + title: "fix: Get me the rebels base!", + labels: [ + { name: "Type: Bug" }, + ], + user: usersCache.vader, + }, + 4: { + number: 4, + title: "fix: RRRAARRWHHGWWR", + labels: [ + { name: "Type: Bug" }, + { name: "Type: Maintenance" }, + ], + user: usersCache.chewbacca, + }, + 5: { + number: 5, + title: "feat: I am your father", + labels: [ + { name: "Type: New Feature" }, + ], + user: usersCache.vader, + }, + 6: { + number: 6, + title: "refactor: he is my brother", + labels: [ + { name: "Type: Enhancement" }, + ], + user: usersCache["princess-leia"], + }, + 7: { + number: 7, + title: "feat: that is not how the Force works!", + labels: [ + { name: "Type: New Feature" }, + { name: "Type: Enhancement" }, + ], + user: usersCache["han-solo"], + }, +} + + +describe("createMarkdown", () => { + beforeEach(() => { + require("../../src/execSync").__resetDefaults(); + require("../../src/ApiDataCache").__resetDefaults(); + }) + + require("../../src/execSync").__mockGitShow(listOfPackagesForEachCommit); + require("../../src/execSync").__mockGitDescribe("v8.0.0"); + require("../../src/execSync").__mockGitLog(listOfCommits); + require("../../src/ApiDataCache").__setCache({ + user: usersCache, + issue: issuesCache, + }) + const MockedChangelog = require("../../src/Changelog").default; + const changelog = new MockedChangelog(); + + const markdown = changelog.createMarkdown({ + "tag-from": "v4.0.0", + "tag-to": undefined, + }); + it('outputs correct changelog', () => { + expect(markdown).toMatchSnapshot() + }) +}) From 6ce33e09fd94233035bbbbd9133b1697d4d9e125 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 19 Jan 2017 01:24:53 +0100 Subject: [PATCH 10/13] fix(test): properly mock github auth token --- src/Changelog.js | 14 +++++++------- src/GithubAPI.js | 6 +++++- src/__mocks__/GithubAPI.js | 9 +++++++++ test/Changelog.spec.js | 1 + test/functional/markdown-full.spec.js | 1 + 5 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 src/__mocks__/GithubAPI.js diff --git a/src/Changelog.js b/src/Changelog.js index 9fd9da51..4afff19f 100644 --- a/src/Changelog.js +++ b/src/Changelog.js @@ -1,7 +1,7 @@ -import LernaRepo from "lerna/lib/Repository"; -import progressBar from "lerna/lib/progressBar"; -import RemoteRepo from "./RemoteRepo"; -import execSync from "./execSync"; +import LernaRepo from "lerna/lib/Repository"; +import progressBar from "lerna/lib/progressBar"; +import RemoteRepo from "./RemoteRepo"; +import execSync from "./execSync"; import ConfigurationError from "./ConfigurationError"; const UNRELEASED_TAG = "___unreleased___"; @@ -24,8 +24,8 @@ export default class Changelog { if (!config) { throw new ConfigurationError( - "Missing changelog config in `lerna.json`.\n" + - "See docs for setup: https://github.com/lerna/lerna-changelog#readme" + "Missing changelog config in `lerna.json`.\n"+ + "See docs for setup: https://github.com/lerna/lerna-changelog#readme" ); } @@ -164,7 +164,7 @@ export default class Changelog { const committers = {}; commits.forEach(commit => { - const login = (commit.user || {}).login; + const login = (commit.user||{}).login; // If a list of `ignoreCommitters` is provided in the lerna.json config // check if the current committer should be kept or not. const shouldKeepCommiter = login && ( diff --git a/src/GithubAPI.js b/src/GithubAPI.js index 8d5a78e8..9fa69d84 100644 --- a/src/GithubAPI.js +++ b/src/GithubAPI.js @@ -7,12 +7,16 @@ export default class GithubAPI { const {repo} = config; this.repo = repo; this.cache = new ApiDataCache('github', config); - this.auth = process.env.GITHUB_AUTH || "aa"; + this.auth = this.getAuthToken(); if (!this.auth) { throw new ConfigurationError("Must provide GITHUB_AUTH"); } } + getAuthToken() { + return process.env.GITHUB_AUTH; + } + getIssueData(issue) { return this._get('issue', issue); } diff --git a/src/__mocks__/GithubAPI.js b/src/__mocks__/GithubAPI.js new file mode 100644 index 00000000..4111bd86 --- /dev/null +++ b/src/__mocks__/GithubAPI.js @@ -0,0 +1,9 @@ +const GithubAPI = require.requireActual("../GithubAPI").default; + +class MockedGithubAPI extends GithubAPI { + getAuthToken() { + return "123"; + } +} + +export default MockedGithubAPI; diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js index 0501de74..e8b82c63 100644 --- a/test/Changelog.spec.js +++ b/test/Changelog.spec.js @@ -2,6 +2,7 @@ jest.mock("lerna/lib/Repository"); jest.mock("lerna/lib/progressBar"); jest.mock("../src/ApiDataCache"); jest.mock("../src/Changelog"); +jest.mock("../src/GithubAPI"); jest.mock("../src/execSync"); describe("contructor", () => { diff --git a/test/functional/markdown-full.spec.js b/test/functional/markdown-full.spec.js index b525c77f..8235693e 100644 --- a/test/functional/markdown-full.spec.js +++ b/test/functional/markdown-full.spec.js @@ -2,6 +2,7 @@ jest.mock("lerna/lib/Repository"); jest.mock("lerna/lib/progressBar"); jest.mock("../../src/ApiDataCache"); jest.mock("../../src/Changelog"); +jest.mock("../../src/GithubAPI"); jest.mock("../../src/execSync"); const listOfCommits = From b00790d2fdd4a431178a1c0c598fb20666d4f350 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Tue, 24 Jan 2017 20:03:45 +0100 Subject: [PATCH 11/13] chore(package.json): exclude missing mockfiles from being transpiled --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a45fefc3..bf092b23 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "lerna-changelog": "cli.js" }, "scripts": { - "build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__/GithubAPI.js,src/__mocks__/Changelog.js", + "build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__/GithubAPI.js,src/__mocks__/Changelog.js,src/__mocks__/ApiDataCache.js,src/__mocks__/execSync.js", "clean": "rimraf lib", "lint": "eslint index.js cli.js src", "test": "jest", From d7f698c593b64193a2776e84d7598bfc46bf4e21 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 26 Jan 2017 16:31:38 +0100 Subject: [PATCH 12/13] chore(package.json): simplify babel script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf092b23..47b3938a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "lerna-changelog": "cli.js" }, "scripts": { - "build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__/GithubAPI.js,src/__mocks__/Changelog.js,src/__mocks__/ApiDataCache.js,src/__mocks__/execSync.js", + "build": "npm run clean && babel src --out-dir lib --ignore src/__mocks__", "clean": "rimraf lib", "lint": "eslint index.js cli.js src", "test": "jest", From 0408bcba1cb7a9e89e582de87e76418707ac522c Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Sun, 29 Jan 2017 11:10:47 +0100 Subject: [PATCH 13/13] refactor(changelog): support grouping tags referenced by the same commits --- src/Changelog.js | 62 +++++++++----- src/__mocks__/execSync.js | 11 ++- test/Changelog.spec.js | 26 +++--- .../__snapshots__/markdown-full.spec.js.snap | 34 +++++++- test/functional/markdown-full.spec.js | 82 +++++++++++++++---- 5 files changed, 164 insertions(+), 51 deletions(-) diff --git a/src/Changelog.js b/src/Changelog.js index 4afff19f..aa2e37e3 100644 --- a/src/Changelog.js +++ b/src/Changelog.js @@ -138,6 +138,14 @@ export default class Changelog { ); } + getListOfTags() { + const tags = execSync("git tag"); + if (tags) { + return tags.split("\n"); + } + return []; + } + getLastTag() { return execSync("git describe --abbrev=0 --tags"); } @@ -189,6 +197,7 @@ export default class Changelog { getCommitsInfo() { const commits = this.getListOfCommits(); + const allTags = this.getListOfTags(); progressBar.init(commits.length); @@ -197,14 +206,16 @@ export default class Changelog { // ;;; const parts = commit.split(";"); const sha = parts[0]; - const _refs = parts[1].split(","); - let tag; - if (_refs.length === 1) { - // "tag: - tag = _refs[0].replace(/tag:\s(.*?)$/, "$1").trim(); - } else if (_refs.length === 4) { - // "HEAD -> master, tag: , origin/master, origin/HEAD" - tag = _refs[1].replace(/tag:\s(.*?)$/, "$1").trim(); + const _refs = parts[1]; + let tagsInCommit; + 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((acc, tag) => { + if (_refs.indexOf(tag) < 0) + return acc + return acc.concat(tag) + }, []); } const message = parts[2]; const date = parts[3]; @@ -219,7 +230,7 @@ export default class Changelog { // Note: Only merge commits or commits referencing an issue / PR // will be kept in the changelog. labels: [], - tag, + tags: tagsInCommit, date }; @@ -249,14 +260,18 @@ export default class Changelog { // Analyze the commits and group them by tag. // This is useful to generate multiple release logs in case there are // multiple release tags. - let currentTag = UNRELEASED_TAG; - return commits.reduce( - (acc, commit) => { - const tag = commit.tag; - if (tag) { - currentTag = tag; - } + let currentTags = [UNRELEASED_TAG]; + return commits.reduce((acc, commit) => { + if (commit.tags) { + currentTags = commit.tags; + } + // 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. + const commitsForTags = currentTags.reduce((acc2, currentTag) => { let existingCommitsForTag = []; if ({}.hasOwnProperty.call(acc, currentTag)) { existingCommitsForTag = acc[currentTag].commits; @@ -268,15 +283,20 @@ export default class Changelog { } return { - ...acc, + ...acc2, [currentTag]: { date: releaseDate, commits: existingCommitsForTag.concat(commit) } - }; - }, - {} - ); + } + }, {}) + + + return { + ...acc, + ...commitsForTags, + }; + }, {}); } getCommitsByCategory(commits) { diff --git a/src/__mocks__/execSync.js b/src/__mocks__/execSync.js index 0413575b..0a714d68 100644 --- a/src/__mocks__/execSync.js +++ b/src/__mocks__/execSync.js @@ -1,10 +1,12 @@ let gitShowResult let gitDescribeResult let gitLogResult +let gitTagResult export function __resetDefaults () { gitShowResult = undefined gitDescribeResult = undefined gitLogResult = undefined + gitTagResult = undefined } export function __mockGitShow (result) { gitShowResult = result; @@ -15,6 +17,9 @@ export function __mockGitDescribe (result) { export function __mockGitLog (result) { gitLogResult = result; } +export function __mockGitTag (result) { + gitTagResult = result; +} export default function execSync(cmd) { if (cmd.indexOf("git show") === 0) { const sha = cmd.split("--first-parent")[1].trim(); @@ -23,6 +28,10 @@ export default function execSync(cmd) { return gitDescribeResult; else if (cmd.indexOf("git log") === 0) return gitLogResult; + else if (cmd.indexOf("git tag") === 0) + return gitTagResult; else - throw new Error("Unknown exec command: " + cmd); + throw new Error( + "Unknown exec command: " + cmd + ". Please make sure to mock it." + ); } diff --git a/test/Changelog.spec.js b/test/Changelog.spec.js index e8b82c63..a024689d 100644 --- a/test/Changelog.spec.js +++ b/test/Changelog.spec.js @@ -40,6 +40,12 @@ describe("getCommitsInfo", () => { "a0000002;;refactor(module) Simplify implementation;2017-01-01\n" + "a0000001;tag: v0.1.0;chore(release): releasing component;2017-01-01" ); + require("../src/execSync").__mockGitTag( + "v0.2.0\n" + + "v0.1.1\n" + + "v0.1.0\n" + + "v0.0.1" + ); const usersCache = { "test-user": { login: "test-user", @@ -73,7 +79,7 @@ describe("getCommitsInfo", () => { date: "2017-01-01", labels: [], message: "chore(release): releasing component", - tag: "v0.2.0" + tags: ["v0.2.0"], }, { commitSHA: "a0000004", @@ -85,13 +91,13 @@ describe("getCommitsInfo", () => { mergeMessage: "Merge pull request #2 from my-feature", message: "Merge pull request #2 from my-feature", number: 2, - tag: "", + tags: undefined, title: "This is the commit title for the issue (#2)", user: { html_url: "https://github.com/test-user", login: "test-user", - name: "Test User" - } + name: "Test User", + }, }, { commitSHA: "a0000003", @@ -103,28 +109,28 @@ describe("getCommitsInfo", () => { mergeMessage: "feat(module) Add new module (#2)", message: "feat(module) Add new module (#2)", number: 2, - tag: "", + tags: undefined, title: "This is the commit title for the issue (#2)", user: { html_url: "https://github.com/test-user", login: "test-user", - name: "Test User" - } + name: "Test User", + }, }, { commitSHA: "a0000002", date: "2017-01-01", labels: [], message: "refactor(module) Simplify implementation", - tag: "" + tags: undefined, }, { commitSHA: "a0000001", date: "2017-01-01", labels: [], message: "chore(release): releasing component", - tag: "v0.1.0" - } + tags: ["v0.1.0"], + }, ]); }); }); diff --git a/test/functional/__snapshots__/markdown-full.spec.js.snap b/test/functional/__snapshots__/markdown-full.spec.js.snap index 792f5f7c..e9d787b8 100644 --- a/test/functional/__snapshots__/markdown-full.spec.js.snap +++ b/test/functional/__snapshots__/markdown-full.spec.js.snap @@ -1,4 +1,36 @@ -exports[`createMarkdown outputs correct changelog 1`] = ` +exports[`createMarkdown multiple tags outputs correct changelog 1`] = ` +" +## a-new-hope@4.0.0 (1977-05-25) + +#### :rocket: New Feature +* \`a-new-hope\` + * [#1](https://github.com/lerna/lerna-changelog/pull/1) feat: May the force be with you. ([@luke](https://github.com/luke)) + +#### Committers: 1 +- Luke Skywalker ([luke](https://github.com/luke)) + + +## empire-strikes-back@5.0.0 (1977-05-25) + +#### :rocket: New Feature +* \`a-new-hope\` + * [#1](https://github.com/lerna/lerna-changelog/pull/1) feat: May the force be with you. ([@luke](https://github.com/luke)) + +#### Committers: 1 +- Luke Skywalker ([luke](https://github.com/luke)) + + +## return-of-the-jedi@6.0.0 (1977-05-25) + +#### :rocket: New Feature +* \`a-new-hope\` + * [#1](https://github.com/lerna/lerna-changelog/pull/1) feat: May the force be with you. ([@luke](https://github.com/luke)) + +#### Committers: 1 +- Luke Skywalker ([luke](https://github.com/luke))" +`; + +exports[`createMarkdown single tags outputs correct changelog 1`] = ` " ## Unreleased (2099-01-01) diff --git a/test/functional/markdown-full.spec.js b/test/functional/markdown-full.spec.js index 8235693e..2d834022 100644 --- a/test/functional/markdown-full.spec.js +++ b/test/functional/markdown-full.spec.js @@ -20,7 +20,16 @@ const listOfCommits = "a0000004;tag: v4.0.0;chore(release): releasing component;1977-05-25\n" + "a0000003;;Merge pull request #1 from star-wars;1977-05-25\n" + "a0000002;tag: v0.1.0;chore(release): releasing component;1966-01-01\n" + - "a0000001;;fix: some random fix which will be ignored;1966-01-01" + "a0000001;;fix: some random fix which will be ignored;1966-01-01"; + +const listOfTags = + "v6.0.0\n" + + "v5.0.0\n" + + "v4.0.0\n" + + "v3.0.0\n" + + "v2.0.0\n" + + "v1.0.0\n" + + "v0.1.0"; const listOfPackagesForEachCommit = { a0000001: "packages/random/foo.js", @@ -40,7 +49,7 @@ const listOfPackagesForEachCommit = { "packages/the-force-awakens/mission.js\n" + "packages/rogue-one/mission.js", a0000015: "packages/untitled/script.md", -} +}; const usersCache = { luke: { @@ -143,30 +152,67 @@ const issuesCache = { ], user: usersCache["han-solo"], }, -} +}; -describe("createMarkdown", () => { +describe.only("createMarkdown", () => { beforeEach(() => { require("../../src/execSync").__resetDefaults(); require("../../src/ApiDataCache").__resetDefaults(); }) - require("../../src/execSync").__mockGitShow(listOfPackagesForEachCommit); - require("../../src/execSync").__mockGitDescribe("v8.0.0"); - require("../../src/execSync").__mockGitLog(listOfCommits); - require("../../src/ApiDataCache").__setCache({ - user: usersCache, - issue: issuesCache, + describe("single tags", () => { + require("../../src/execSync").__mockGitShow(listOfPackagesForEachCommit); + require("../../src/execSync").__mockGitDescribe("v8.0.0"); + require("../../src/execSync").__mockGitLog(listOfCommits); + require("../../src/execSync").__mockGitTag(listOfTags); + require("../../src/ApiDataCache").__setCache({ + user: usersCache, + issue: issuesCache, + }) + const MockedChangelog = require("../../src/Changelog").default; + const changelog = new MockedChangelog(); + + const markdown = changelog.createMarkdown({ + "tag-from": "v4.0.0", + "tag-to": undefined, + }); + it("outputs correct changelog", () => { + expect(markdown).toMatchSnapshot() + }) }) - const MockedChangelog = require("../../src/Changelog").default; - const changelog = new MockedChangelog(); - const markdown = changelog.createMarkdown({ - "tag-from": "v4.0.0", - "tag-to": undefined, - }); - it('outputs correct changelog', () => { - expect(markdown).toMatchSnapshot() + describe("multiple tags", () => { + require("../../src/execSync").__mockGitShow(listOfPackagesForEachCommit); + require("../../src/execSync").__mockGitDescribe("v8.0.0"); + require("../../src/execSync").__mockGitLog( + "a0000004;tag: a-new-hope@4.0.0, tag: empire-strikes-back@5.0.0, tag: return-of-the-jedi@6.0.0;chore(release): releasing component;1977-05-25\n" + + "a0000003;;Merge pull request #1 from star-wars;1977-05-25\n" + + "a0000002;tag: v0.1.0;chore(release): releasing component;1966-01-01\n" + + "a0000001;;fix: some random fix which will be ignored;1966-01-01" + ); + require("../../src/execSync").__mockGitTag( + "a-new-hope@4.0.0\n" + + "attack-of-the-clones@3.1.0\n" + + "empire-strikes-back@5.0.0\n" + + "return-of-the-jedi@6.0.0\n" + + "revenge-of-the-sith@3.0.0\n" + + "the-force-awakens@7.0.0\n" + + "the-phantom-menace@1.0.0" + ); + require("../../src/ApiDataCache").__setCache({ + user: usersCache, + issue: issuesCache, + }) + const MockedChangelog = require("../../src/Changelog").default; + const changelog = new MockedChangelog(); + + const markdown = changelog.createMarkdown({ + "tag-from": "v0.1.0", + "tag-to": undefined, + }); + it("outputs correct changelog", () => { + expect(markdown).toMatchSnapshot() + }) }) })