Skip to content

Commit

Permalink
feat: prepend the CHANGELOG file instead of rewriting it
Browse files Browse the repository at this point in the history
  • Loading branch information
lekterable committed Apr 22, 2020
1 parent fdef971 commit 960ea6f
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Latest

- feat: add update version feature fdef971e

## 0.2.0

- feat: include changelog in the releases 2da21c56
Expand Down
15 changes: 11 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ import semver from 'semver'
import {
commitRelease,
generateLine,
generateReleased,
getCommitDetails,
getCommits,
getLatestTag,
updateVersion
} from './utils'

export const changelog = async (version, options) => {
const title = version || 'Latest'
const commits = await getCommits()
const latestTag = await getLatestTag()
const commits = await getCommits(latestTag)
const latestCommit = getCommitDetails(commits[0])
const isReleaseLatest = latestCommit.scope === 'release'
let changelog = isReleaseLatest ? '' : `## ${title}\n\n`

const released = latestTag && (await generateReleased(latestTag))

commits.forEach((commit, index) => {
const commitDetails = getCommitDetails(commit)
const nextCommit = getCommitDetails(commits[index + 1])
Expand All @@ -24,9 +29,11 @@ export const changelog = async (version, options) => {
if (line) changelog += line
})

return options && options.write
? writeFileSync('CHANGELOG.md', changelog)
: process.stdout.write(changelog)
if (!options || !options.write) return process.stdout.write(changelog)

const newChangelog = released ? changelog + '\n' + released : changelog

return writeFileSync('CHANGELOG.md', newChangelog)
}

export const release = async version => {
Expand Down
47 changes: 45 additions & 2 deletions src/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { exec } from 'child_process'
import { readFile } from 'fs'

const execAsync = command =>
new Promise((resolve, reject) =>
Expand All @@ -14,12 +15,54 @@ export const commitRelease = async version => {
await execAsync(`git tag ${version}`)
}

export const getCommits = async () => {
const commits = await execAsync('git log --format="%H %s"')
export const getCommits = async tag => {
const query = tag
? `git log --format="%H %s" ${tag}..`
: 'git log --format="%H %s"'
const commits = await execAsync(query)

return commits.split('\n').filter(commit => commit)
}

export const generateReleased = previousVersion =>
new Promise((resolve, reject) =>
readFile('CHANGELOG.md', 'utf8', (err, data) => {
if (err) return reject(err)

let isLatest = false
const released = data
.split('\n')
.filter(line => {
if (line === '## Latest') {
isLatest = true

return false
}

if (isLatest && line === `## ${previousVersion}`) {
isLatest = false

return true
}

return !isLatest
})
.join('\n')

if (isLatest) {
return reject(new Error('Previous release not found in CHANGELOG'))
}

return resolve(released)
})
)

export const getLatestTag = async () => {
const latestTag = await execAsync('git tag | tail -n 1')

return latestTag ? latestTag.replace('\n', '') : null
}

export const getCommitDetails = commit => {
if (!commit) return null

Expand Down
76 changes: 76 additions & 0 deletions src/utils/index.test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { exec } from 'child_process'
import { readFile } from 'fs'
import {
commitRelease,
generateLine,
generateReleased,
getCommitDetails,
getCommits,
getLatestTag,
updateVersion
} from './index'

jest.mock('child_process', () => ({
exec: jest.fn()
}))
jest.mock('fs', () => ({
readFile: jest.fn()
}))

describe('utils', () => {
beforeEach(() => jest.resetAllMocks())
Expand All @@ -33,6 +39,26 @@ describe('utils', () => {
})
})

describe('getLatestTag', () => {
it('should return null if there is no tag', async () => {
exec.mockImplementation((_, cb) => cb(null))
const latestTag = await getLatestTag()

expect(latestTag).toBe(null)
expect(exec).toBeCalledTimes(1)
expect(exec).toBeCalledWith('git tag | tail -n 1', expect.any(Function))
})

it('should return latest tag', async () => {
exec.mockImplementation((_, cb) => cb(null, '2.2.2'))
const latestTag = await getLatestTag()

expect(latestTag).toBe('2.2.2')
expect(exec).toBeCalledTimes(1)
expect(exec).toBeCalledWith('git tag | tail -n 1', expect.any(Function))
})
})

describe('getCommits', () => {
it('should reject if receives an error', async () => {
const error = 'error'
Expand Down Expand Up @@ -62,6 +88,28 @@ describe('utils', () => {
)
expect(commits).toEqual(mockedOutput)
})

it('should return commits since tag', async () => {
const mockedInput =
'bffc2f9e8da1c7ac133689bc9cd14494f3be08e3 refactor: extract line generating logic to function and promisify exec\naa805ce71ee103965ce3db46d4f6ed2658efd08d feat: add option to write to local CHANGELOG file\nf2191200bf7b6e5eec3d61fcef9eb756e0129cfb chore(release): 0.1.0\nb2f5901922505efbfb6dd684252e8df0cdffeeb2 fix: support other conventions\n4e02179cae1234d7083036024080a3f25fcb52c2 feat: add execute release feature'
const mockedOutput = [
'bffc2f9e8da1c7ac133689bc9cd14494f3be08e3 refactor: extract line generating logic to function and promisify exec',
'aa805ce71ee103965ce3db46d4f6ed2658efd08d feat: add option to write to local CHANGELOG file',
'f2191200bf7b6e5eec3d61fcef9eb756e0129cfb chore(release): 0.1.0',
'b2f5901922505efbfb6dd684252e8df0cdffeeb2 fix: support other conventions',
'4e02179cae1234d7083036024080a3f25fcb52c2 feat: add execute release feature'
]

exec.mockImplementation((_, cb) => cb(null, mockedInput))
const commits = await getCommits('2.2.2')

expect(exec).toBeCalledTimes(1)
expect(exec).toBeCalledWith(
'git log --format="%H %s" 2.2.2..',
expect.any(Function)
)
expect(commits).toEqual(mockedOutput)
})
})

describe('getCommitDetails', () => {
Expand Down Expand Up @@ -90,6 +138,34 @@ describe('utils', () => {
})
})

describe('generateReleased', () => {
it('should reject if receives an error', async () => {
const error = 'error'
readFile.mockImplementation((_, __, cb) => cb(error))

expect(generateReleased()).rejects.toMatch(error)
})

it('should reject if there is no released but tag provided', async () => {
const error = 'Previous release not found in CHANGELOG'
const mockedInput =
'## Latest\n- feat: include changelog in the releases 2da21c56\n- test: add utils tests 217b25d0'
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))

expect(generateReleased('2.2.2')).rejects.toThrow(error)
})

it('should generate released', async () => {
const mockedInput =
'## Latest\n- feat: include changelog in the releases 2da21c56\n- test: add utils tests 217b25d0\n## 2.2.2\n- feat: add feature 2da21c56'
const mockedOutput = '## 2.2.2\n- feat: add feature 2da21c56'
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))
const released = await generateReleased('2.2.2')

expect(released).toBe(mockedOutput)
})
})

describe('generateLine', () => {
it('should generate release line', () => {
const mockedInput = {
Expand Down

0 comments on commit 960ea6f

Please sign in to comment.