diff --git a/.travis.yml b/.travis.yml index aa9c634..6dc5416 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,4 @@ cache: yarn script: - yarn lint - yarn test + - yarn test:e2e diff --git a/jest.config-e2e.js b/jest.config-e2e.js new file mode 100644 index 0000000..12e8eb1 --- /dev/null +++ b/jest.config-e2e.js @@ -0,0 +1,3 @@ +module.exports = { + testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(e2e).[jt]s?(x)"], +} diff --git a/package.json b/package.json index 1be2ed9..4537819 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "homepage": "https://github.com/frinyvonnick/gitmoji-changelog#readme", "scripts": { "test": "jest", + "test:e2e": "jest --config ./jest.config-e2e.js", "lint": "eslint packages/**/src" }, "devDependencies": { diff --git a/packages/gitmoji-changelog-cli/package.json b/packages/gitmoji-changelog-cli/package.json index 5aca6af..3a0554e 100644 --- a/packages/gitmoji-changelog-cli/package.json +++ b/packages/gitmoji-changelog-cli/package.json @@ -37,6 +37,7 @@ "inquirer": "^6.3.1", "libnpm": "^1.0.0", "lodash": "^4.17.11", + "semver": "^5.6.0", "semver-compare": "^1.0.0", "simple-git": "^1.113.0", "yargs": "^12.0.1" diff --git a/packages/gitmoji-changelog-cli/src/cli.e2e.js b/packages/gitmoji-changelog-cli/src/cli.e2e.js new file mode 100644 index 0000000..982df65 --- /dev/null +++ b/packages/gitmoji-changelog-cli/src/cli.e2e.js @@ -0,0 +1,343 @@ +const os = require('os') +const fs = require('fs') +const { removeSync } = require('fs-extra') +const path = require('path') +const simpleGit = require('simple-git/promise') +const childProcess = require('child_process') + +expect.extend({ + includes(str, items) { + const pass = items.every(item => str.includes(item)) + return { + pass, + message: () => pass + ? `Expected ${str} to not includes ${items.join(', ')}` + : `Expected ${str} to includes ${items.join(', ')}`, + } + }, +}) + +expect.extend({ + toDisplayError(str) { + const pass = str.includes('Error') + return { + pass, + message: () => pass ? 'It passes' : `Expected ${str} to includes Error`, + } + }, +}) + +describe('generate changelog', () => { + let testDir + let repo + + beforeEach(async () => { + testDir = path.join(os.tmpdir(), 'gitmoji-changelog') + if (fs.existsSync(testDir)) removeSync(testDir) + fs.mkdirSync(testDir) + repo = simpleGit(testDir) + await repo.init() + fs.copyFileSync( + path.join(__dirname, '..', '..', '..', 'misc', 'package.sample.json'), + path.join(testDir, 'package.json'), + ) + }) + + afterEach(() => { + removeSync(testDir) + }) + + describe('init', () => { + it("should get a 1.0.0 version while initializing changelog by calling cli without arguments and having package.json's version set to 1.0.0", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + + expect(getChangelog()).includes(['1.0.0']) + }) + + it("should get a 1.0.0 version while initializing changelog by calling cli with 1.0.0 and having package.json's version set to 0.0.1", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + gitmojiChangelog('1.0.0') + await commit(':bookmark: Version 1.0.0') + + expect(getChangelog()).includes(['1.0.0']) + }) + + it("should get a next version while initializing changelog by calling cli without arguments and having package.json's version set to 1.0.0", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0', 'next']) + }) + + it("should get two versions 1.0.0 and 2.0.0 while initializing changelog by calling cli without and having package.json's version set to 1.0.0", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + await bumpVersion('2.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 2.0.0') + + expect(getChangelog()).includes(['1.0.0', '2.0.0']) + }) + }) + + describe('update', () => { + it("should get two versions 1.0.0 and next while updating changelog by calling cli without arguments and having package.json's version set to 1.0.0", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0', 'next']) + }) + it("should get two versions 1.0.0 and 2.0.0 while updating changelog by calling cli without arguments and having package.json's version set to 2.0.0", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + await bumpVersion('2.0.0') + gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0', '2.0.0']) + }) + it("should get two versions 1.0.0 and 2.0.0 while updating changelog by calling cli with 2.0.0 and having package.json's version set to 1.0.0", async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog('2.0.0') + + expect(getChangelog()).includes(['1.0.0', '2.0.0']) + }) + it('should get three versions 1.0.0, 2.0.0, 3.0.0 while updating changelog by calling cli without arguments and skipping two tags creation 2.0.0 and 3.0.0', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + await bumpVersion('2.0.0') + await commit(':bookmark: Version 2.0.0') + await tag('2.0.0') + + await makeChanges('file3') + await commit(':sparkles: Add a third file') + await bumpVersion('3.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 3.0.0') + await tag('3.0.0') + + expect(getChangelog()).includes(['1.0.0', '2.0.0', '3.0.0']) + }) + + it('should get three versions 1.0.0, 2.0.0, 3.0.0 and next while updating changelog by calling cli without arguments and skipping two tags creation 2.0.0 and 3.0.0', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + await bumpVersion('2.0.0') + await commit(':bookmark: Version 2.0.0') + await tag('2.0.0') + + await makeChanges('file3') + await commit(':sparkles: Add a third file') + await bumpVersion('3.0.0') + await commit(':bookmark: Version 3.0.0') + await tag('3.0.0') + + await makeChanges('file4') + await commit(':sparkles: Add a fourth file') + gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0', '2.0.0', '3.0.0', 'next']) + }) + + it('should get two versions 1.0.0, 2.0.0 and next while updating changelog by calling cli without arguments', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + await bumpVersion('2.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 2.0.0') + await tag('2.0.0') + + await makeChanges('file4') + await commit(':sparkles: Add a fourth file') + gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0', '2.0.0', 'next']) + }) + + it('shouldn\'t generate changelog when gimoji-changelog if there isn\'t any changes', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + const output = gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0']) + expect(output.toString('utf8')).toDisplayError() + }) + + it('should get two versions 1.0.0 and next after two generation while updating changelog by calling cli without arguments', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog() + + await makeChanges('file4') + await commit(':sparkles: Add a fourth file') + gitmojiChangelog() + + expect(getChangelog()).includes(['1.0.0', 'next']) + }) + + it('should get two versions 1.0.0 and 1.1.0 after three generations while updating changelog by calling cli with version 1.1.0', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog() + + await makeChanges('file4') + await commit(':sparkles: Add a fourth file') + gitmojiChangelog('1.1.0') + + expect(getChangelog()).includes(['1.0.0', '1.1.0']) + }) + + it('should get two versions 1.0.0 and next after three generations while updating changelog by calling cli without arguments', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog('1.1.0') + gitmojiChangelog() + + expect(getChangelog()).not.includes(['1.1.0']) + expect(getChangelog()).includes(['1.0.0', 'next']) + }) + + it('should get two versions 1.0.0 and 1.2.0 after three generations while updating changelog by calling cli without arguments', async () => { + await makeChanges('file1') + await commit(':sparkles: Add some file') + await bumpVersion('1.0.0') + gitmojiChangelog() + await commit(':bookmark: Version 1.0.0') + await tag('1.0.0') + + await makeChanges('file2') + await commit(':sparkles: Add another file') + gitmojiChangelog('1.1.0') + gitmojiChangelog('1.2.0') + + expect(getChangelog()).not.includes(['1.1.0']) + expect(getChangelog()).includes(['1.0.0', '1.2.0']) + }) + + it('should display an error if requested version isn\'t semver', async () => { + const output = gitmojiChangelog('awesomeversion') + + expect(output.toString('utf8')).toDisplayError() + }) + }) + + async function makeChanges(fileName) { + fs.writeFileSync(path.join(testDir, fileName)) + } + + async function commit(message) { + await repo.add('.') + await repo.commit(message) + } + + async function tag(version) { + await repo.addTag(`v${version}`) + } + + function gitmojiChangelog(args = []) { + if (!Array.isArray(args)) { + // eslint-disable-next-line no-param-reassign + args = [args] + } + return childProcess.execFileSync('node', [path.join(__dirname, 'index.js'), ...args], { cwd: testDir }) + } + + function getChangelog() { + return fs.readFileSync(path.join(testDir, 'CHANGELOG.md')).toString('utf8') + } + + function bumpVersion(to) { + const pkg = path.join(testDir, 'package.json') + // eslint-disable-next-line global-require + const content = fs.readFileSync(pkg).toString('utf8') + const { version } = JSON.parse(content) + const updatedContent = content.replace(version, to) + fs.writeFileSync(pkg, updatedContent) + } +}) diff --git a/packages/gitmoji-changelog-cli/src/cli.e2e.spec.js b/packages/gitmoji-changelog-cli/src/cli.e2e.spec.js deleted file mode 100644 index 72e5559..0000000 --- a/packages/gitmoji-changelog-cli/src/cli.e2e.spec.js +++ /dev/null @@ -1,76 +0,0 @@ -const os = require('os') -const fs = require('fs') -const { removeSync } = require('fs-extra') -const path = require('path') -const simpleGit = require('simple-git/promise') -const util = require('util') -const execFile = util.promisify(require('child_process').execFile) - -expect.extend({ - includes(str, items) { - const pass = items.every(item => str.includes(item)) - return { - pass, - message: () => pass ? '' : '', - } - }, -}) - -describe('generate changelog', () => { - let testDir - let repo - - beforeAll(async () => { - testDir = path.join(os.tmpdir(), 'gitmoji-changelog') - if (fs.existsSync(testDir)) removeSync(testDir) - fs.mkdirSync(testDir) - repo = simpleGit(testDir) - await repo.init() - fs.copyFileSync( - path.join(__dirname, '..', '..', '..', 'misc', 'package.sample.json'), - path.join(testDir, 'package.json'), - ) - }) - - afterAll(() => { - removeSync(testDir) - }) - - it('should generate changelog even if user doesn\'t call cli at each tag', async () => { - await newVersion('1.0.0') - await gitmojiChangelog() - - expect(getChangelog()).toMatch(/1.0.0/) - - await newVersion('1.0.1') - await newVersion('1.0.2') - await gitmojiChangelog() - - expect(getChangelog()).includes(['1.0.0', '1.0.1', '1.0.2']) - }) - - async function newVersion(version) { - fs.writeFileSync(path.join(testDir, version)) - bumpVersion(version) - await repo.add('.') - await repo.commit(`Commit ${version}`) - await repo.addTag(`v${version}`) - } - - function gitmojiChangelog() { - return execFile('node', [path.join(__dirname, 'index.js')], { cwd: testDir }) - } - - function getChangelog() { - return fs.readFileSync(path.join(testDir, 'CHANGELOG.md')).toString('utf8') - } - - function bumpVersion(to) { - const pkg = path.join(testDir, 'package.json') - // eslint-disable-next-line global-require - const { version } = require(pkg) - const content = fs.readFileSync(pkg).toString('utf8') - const updatedContent = content.replace(version, to) - fs.writeFileSync(pkg, updatedContent) - } -}) diff --git a/packages/gitmoji-changelog-cli/src/cli.js b/packages/gitmoji-changelog-cli/src/cli.js index 3973131..11ab8e7 100644 --- a/packages/gitmoji-changelog-cli/src/cli.js +++ b/packages/gitmoji-changelog-cli/src/cli.js @@ -1,11 +1,14 @@ const fs = require('fs') const { set } = require('immutadot') const libnpm = require('libnpm') +const semver = require('semver') const semverCompare = require('semver-compare') const { generateChangelog, logger } = require('@gitmoji-changelog/core') const { buildMarkdownFile, getLatestVersion } = require('@gitmoji-changelog/markdown') const { executeInteractiveMode } = require('./interactiveMode') +const { getPackageInfo, getRepoInfo } = require('./metaInfo') + const pkg = require('../package.json') async function getGitmojiChangelogLatestVersion() { @@ -47,7 +50,7 @@ async function main(options = {}) { break } default: { - const lastVersion = getLatestVersion(options.output) + const lastVersion = await getLatestVersion(options.output) const newOptions = set(options, 'meta.lastVersion', lastVersion) const changelog = await getChangelog(newOptions) @@ -67,12 +70,29 @@ async function main(options = {}) { } async function getChangelog(options) { - let changelog = await generateChangelog(options) + const packageInfo = await getPackageInfo() + const repository = await getRepoInfo(packageInfo) + + const release = options.release === 'from-package' ? packageInfo.version : options.release + + if (!semver.valid(release)) { + throw new Error(`${release} is not a valid semver version.`) + } + + const enhancedOptions = { + ...options, + release, + } + + let changelog = await generateChangelog(enhancedOptions) if (options.interactive) { changelog = await executeInteractiveMode(changelog) } + changelog.meta.package = packageInfo + changelog.meta.repository = repository + return changelog } diff --git a/packages/gitmoji-changelog-core/src/metaInfo.js b/packages/gitmoji-changelog-cli/src/metaInfo.js similarity index 100% rename from packages/gitmoji-changelog-core/src/metaInfo.js rename to packages/gitmoji-changelog-cli/src/metaInfo.js diff --git a/packages/gitmoji-changelog-core/src/metaInfo.spec.js b/packages/gitmoji-changelog-cli/src/metaInfo.spec.js similarity index 100% rename from packages/gitmoji-changelog-core/src/metaInfo.spec.js rename to packages/gitmoji-changelog-cli/src/metaInfo.spec.js diff --git a/packages/gitmoji-changelog-core/src/index.js b/packages/gitmoji-changelog-core/src/index.js index 789dbfc..229d594 100644 --- a/packages/gitmoji-changelog-core/src/index.js +++ b/packages/gitmoji-changelog-core/src/index.js @@ -4,11 +4,10 @@ const gitSemverTags = require('git-semver-tags') const semverCompare = require('semver-compare') const through = require('through2') const concat = require('concat-stream') -const { head, isEmpty, get } = require('lodash') +const { isEmpty } = require('lodash') const { promisify } = require('util') const { parseCommit } = require('./parser') -const { getPackageInfo, getRepoInfo } = require('./metaInfo') const groupMapping = require('./groupMapping') const logger = require('./logger') const { groupSentencesByDistance } = require('./utils') @@ -16,6 +15,8 @@ const { groupSentencesByDistance } = require('./utils') const gitSemverTagsAsync = promisify(gitSemverTags) const COMMIT_FORMAT = '%n%H%n%an%n%cI%n%s%n%b' +const HEAD = '' +const START = '' function getCommits(from, to) { return new Promise((resolve) => { @@ -75,36 +76,51 @@ async function generateVersion(options) { })) } - return { + const result = { version, - date: version !== 'next' ? getLastCommitDate(commits) : undefined, groups: makeGroups(commits), } + + if (version !== 'next') { + result.date = getLastCommitDate(commits) + } + + return result } -async function generateVersions({ tags, groupSimilarCommits, meta }) { - let nextTag = '' - - const initialFrom = meta && meta.lastVersion ? meta.lastVersion : '' - return Promise.all( - [...tags, initialFrom] - .map(tag => { - const params = { - groupSimilarCommits, - from: tag, - to: nextTag, - version: nextTag ? sanitizeVersion(nextTag) : 'next', - } - nextTag = tag - return params - }) - .map(generateVersion) - ) - .then(versions => versions.sort((c1, c2) => { - if (c1.version === 'next') return -1 - if (c2.version === 'next') return 1 - return semverCompare(c2.version, c1.version) - })) +function sortVersions(c1, c2) { + if (c1.version === 'next') return -1 + if (c2.version === 'next') return 1 + return semverCompare(c2.version, c1.version) +} + +function hasNextVersion(tags, release) { + if (!release || release === 'next') return true + return tags.some(tag => semver.eq(tag, release)) +} + +async function generateVersions({ + tags, + hasNext, + release, + groupSimilarCommits, +}) { + let nextTag = HEAD + const changes = await Promise.all(tags.map(async tag => { + let version = sanitizeVersion(nextTag) + if (!nextTag) { + version = hasNext ? 'next' : release + } + const from = tag + const to = nextTag + nextTag = tag + return generateVersion({ + from, to, version, groupSimilarCommits, + }) + })) + .then(versions => versions.sort(sortVersions)) + + return changes } async function generateChangelog(options = {}) { @@ -112,57 +128,44 @@ async function generateChangelog(options = {}) { mode, release, groupSimilarCommits, - meta, } = options - const packageInfo = await getPackageInfo() - - let version = release === 'from-package' ? packageInfo.version : release - if (version && version !== 'next') { - if (!semver.valid(version)) { - throw new Error(`${version} is not a valid semver version.`) - } - - version = sanitizeVersion(version) - } - - let changes = [] - - const tags = await gitSemverTagsAsync() - const lastTag = get(options, 'meta.lastVersion', head(tags)) + const gitTags = await gitSemverTagsAsync() + let tagsToProcess = [...gitTags] + const hasNext = hasNextVersion(gitTags, release) + let lastVersion if (mode === 'init') { - changes = await generateVersions({ tags, groupSimilarCommits }) - } else if (lastTag === head(tags)) { - const lastChanges = await generateVersion({ - groupSimilarCommits, - from: lastTag, - version, - }) + lastVersion = release + tagsToProcess = [...tagsToProcess, START] + } else { + const { meta } = options + lastVersion = meta && meta.lastVersion ? meta.lastVersion : undefined - if (isEmpty(lastChanges.groups)) { - throw new Error('No changes found. You may need to fetch or pull the last changes.') + if (lastVersion !== undefined) { + const lastVersionIndex = tagsToProcess.findIndex(tag => semver.eq(tag, lastVersion)) + tagsToProcess.splice(lastVersionIndex + 1) } - changes.push(lastChanges) - } else { - const lastTagIndex = tags.findIndex(tag => tag === lastTag) - const missingTags = tags.splice(0, lastTagIndex) - - const lastChanges = await generateVersions({ tags: missingTags, groupSimilarCommits, meta }) - changes = [ - ...changes, - ...lastChanges, - ] + if (hasNext && isEmpty(tagsToProcess)) { + tagsToProcess.push('') + } } - const repository = await getRepoInfo(packageInfo) + const changes = await generateVersions({ + tags: tagsToProcess, + hasNext, + release, + groupSimilarCommits, + }) + + if (mode === 'update' && isEmpty(changes[0].groups)) { + throw new Error('No changes found. You may need to fetch or pull the last changes.') + } return { meta: { - package: packageInfo, - repository, - lastVersion: sanitizeVersion(lastTag), + lastVersion: sanitizeVersion(lastVersion), }, changes: changes.filter(({ groups }) => groups.length), } diff --git a/packages/gitmoji-changelog-core/src/index.spec.js b/packages/gitmoji-changelog-core/src/index.spec.js index fd552dc..cd14bfb 100644 --- a/packages/gitmoji-changelog-core/src/index.spec.js +++ b/packages/gitmoji-changelog-core/src/index.spec.js @@ -81,8 +81,29 @@ const secondLipstickCommit = { } describe('changelog', () => { + it('should generate changelog for next release on init', async () => { + mockGroup([sparklesCommit]) + + gitSemverTags.mockImplementation(cb => cb(null, [])) + + const { changes } = await generateChangelog({ mode: 'init', release: 'next' }) + + expect(changes).toEqual([ + { + version: 'next', + groups: [ + { + group: 'added', + label: 'Added', + commits: expect.arrayContaining([expect.objectContaining(sparklesCommit)]), + }, + ], + }, + ]) + }) + it('should generate changelog for next release', async () => { - mockGroups() + mockGroup([sparklesCommit]) gitSemverTags.mockImplementation(cb => cb(null, [])) @@ -103,7 +124,8 @@ describe('changelog', () => { }) it('should generate changelog for all tags', async () => { - mockGroups() + mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit]) + mockGroup([sparklesCommit]) gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0'])) @@ -117,10 +139,10 @@ describe('changelog', () => { group: 'changed', label: 'Changed', commits: [ - secondRecycleCommit, - secondLipstickCommit, - lipstickCommit, - recycleCommit, + expect.objectContaining({ subject: secondRecycleCommit.subject }), + expect.objectContaining({ subject: secondLipstickCommit.subject }), + expect.objectContaining({ subject: lipstickCommit.subject }), + expect.objectContaining({ subject: recycleCommit.subject }), ], }, ], @@ -132,7 +154,35 @@ describe('changelog', () => { { group: 'added', label: 'Added', - commits: [sparklesCommit], + commits: [ + expect.objectContaining({ subject: sparklesCommit.subject }), + ], + }, + ], + }, + ]) + }) + + it('should generate a changelog with only next since lastVersion is provided to v1.0.0', async () => { + mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit]) + + gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0'])) + + const { changes } = await generateChangelog({ mode: 'update', meta: { lastVersion: 'v1.0.0' } }) + + expect(changes).toEqual([ + { + version: 'next', + groups: [ + { + group: 'changed', + label: 'Changed', + commits: [ + expect.objectContaining({ subject: secondRecycleCommit.subject }), + expect.objectContaining({ subject: secondLipstickCommit.subject }), + expect.objectContaining({ subject: lipstickCommit.subject }), + expect.objectContaining({ subject: recycleCommit.subject }), + ], }, ], }, @@ -140,7 +190,8 @@ describe('changelog', () => { }) it('should group similar commits', async () => { - mockGroups() + mockGroup([]) + mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit]) gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0'])) @@ -190,8 +241,9 @@ describe('changelog', () => { }) it('should get previous tag in from', async () => { - mockGroups() - mockGroup([sparklesCommit]) + mockGroup([]) + mockGroup([]) + mockGroup([]) gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.1', 'v1.0.0'])) @@ -281,8 +333,3 @@ function mockNoCommits() { return readable }) } - -function mockGroups() { - mockGroup([sparklesCommit]) - mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit]) -} diff --git a/packages/gitmoji-changelog-markdown/package.json b/packages/gitmoji-changelog-markdown/package.json index 8289414..1d40e52 100644 --- a/packages/gitmoji-changelog-markdown/package.json +++ b/packages/gitmoji-changelog-markdown/package.json @@ -22,7 +22,8 @@ "dependencies": { "handlebars": "^4.0.14", "immutadot": "^1.0.0", - "lodash": "^4.17.11" + "lodash": "^4.17.11", + "semver": "^5.6.0" }, "publishConfig": { "access": "public", diff --git a/packages/gitmoji-changelog-markdown/src/index.js b/packages/gitmoji-changelog-markdown/src/index.js index bfbeb2d..e0c958b 100755 --- a/packages/gitmoji-changelog-markdown/src/index.js +++ b/packages/gitmoji-changelog-markdown/src/index.js @@ -1,3 +1,4 @@ +const semver = require('semver') const { promisify } = require('util') const fs = require('fs') const path = require('path') @@ -5,6 +6,9 @@ const { Transform } = require('stream') const handlebars = require('handlebars') const { update } = require('immutadot') const { isEmpty } = require('lodash') +const gitSemverTags = require('git-semver-tags') + +const gitSemverTagsAsync = promisify(gitSemverTags) const MARKDOWN_TEMPLATE = path.join(__dirname, 'template.md') @@ -103,7 +107,7 @@ function matchVersionBreakpoint(tested, version = '.*') { return regex.test(tested) } -function getLatestVersion(markdownFile) { +async function getLatestVersion(markdownFile) { let markdownContent try { @@ -112,11 +116,19 @@ function getLatestVersion(markdownFile) { return null } - const result = markdownContent.match(/<\/a>/) + const versions = markdownContent.match(/<\/a>/g) + + if (!versions) return null + + const [lastVersion, previousVersion] = versions - if (!result) return null + const tags = await gitSemverTagsAsync() + const result = lastVersion.match(/<\/a>/) + const isNext = result[1] === 'next' || !tags.some(tag => semver.eq(tag, result[1])) + if (!isNext) return result[1] - return `v${result[1]}` + const previousResult = previousVersion.match(/<\/a>/) + return previousResult[1] } function getShortHash(hash, repository) {