Skip to content

Commit

Permalink
feat: add release new keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
lekterable committed Jul 9, 2021
1 parent abfb3d8 commit 86aa5dc
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 52 deletions.
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ For the projects that have a long-existing git history, which could potentially

`perfekt release <version> --from <commit>`

where `<version>` is the version of your new release and `<commit>` is the hash of the last commit that should `NOT` be the part of the release.
where `<version>` is the version of your new release and `<commit>` is the hash of the last commit that should **NOT** be the part of the release.

after this, you can start referring to [new projects section](#new-projects) when executing future releases.

Expand Down Expand Up @@ -108,11 +108,11 @@ Options:

`-h, --help` - display help for command

`--from <commit>` - SHA of the last commit that will `NOT` be included in this release
`--from <commit>` - SHA of the last commit which will **NOT** be included in this release

Arguments:

`version` - _(required)_ version which will be used while executing the release. You can also use `major`, `minor` and `patch` instead of a specific version number to make **perfekt** bump the version for you automatically.
`version` - _(required)_ version which will be used while executing the release. You can use `major`, `minor` and `patch` instead of a specific version number to bump it or `new` to make **perfekt** determine the version for you automatically based on the unreleased changes.

### `changelog`

Expand All @@ -124,7 +124,7 @@ This will:

- Look for the latest git tag
- if found transform unreleased into a release and append with the previous changelog
- if `NOT`, try to generate a new changelog for the whole history
- if **NOT**, try to generate a new changelog for the whole history
- Output changelog in the console, if you want to save it in the `CHANGELOG.md` file use `--write` option

Options:
Expand All @@ -135,7 +135,7 @@ Options:

`--root` - generate changelog for the entire history

`--from <commit>` - SHA of the last commit that will `NOT` be included in this changelog
`--from <commit>` - SHA of the last commit that will **NOT** be included in this changelog

Arguments:

Expand All @@ -155,8 +155,16 @@ Default config looks like this:
"releaseFormat": "# %version%",
"breakingFormat": "## BREAKING",
"groups": [
{ "name": "## Features", "aliases": ["feat", "feature"] },
{ "name": "## Fixes", "aliases": ["fix"] }
{
"name": "## Features",
"change": "minor",
"aliases": ["feat", "feature"]
},
{
"name": "## Fixes",
"change": "patch",
"aliases": ["fix"]
}
],
"miscFormat": "## Misc",
"lineFormat": "- %message% %hash%",
Expand Down Expand Up @@ -204,15 +212,29 @@ Default:

Used to define how commits should be grouped inside of the release block.

Each object is a separate group. The `name` property will be used as the group's header and the `types` array contains all commit types which will be associated with it.
Each object is a separate group, e.g:

```json
{
"name": "## Features",
"change": "minor",
"types": ["feat", "feature"]
}
```

The `name` property will be used as the group's header in changelog, `change` is needed to determine the release type when using `new` as a release version and the `types` array contains all commit types which should be associated with that group.

All commits with unmatched types will become a part of the `Misc` group.

> commit type comes from: `type(scope?): message`
Default:

`[ { name: '## Features', types: ['feat', 'feature'] }, { name: '## Fixes', types: ['fix'] } ]`
`[ { name: '## Features', change: 'minor', types: ['feat', 'feature'] }, { name: '## Fixes', change: 'patch', types: ['fix'] } ]`

> change can be either `major`, `minor` or `patch`
> `Breaking` and `Misc` groups correspond to `major` and `patch` changes respectively.
### `miscFormat`

Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export const defaultConfig = {
releaseFormat: '# %version%',
breakingFormat: '## BREAKING',
groups: [
{ name: '## Features', types: ['feat', 'feature'] },
{ name: '## Fixes', types: ['fix'] }
{ name: '## Features', change: 'minor', types: ['feat', 'feature'] },
{ name: '## Fixes', change: 'patch', types: ['fix'] }
],
miscFormat: '## Misc',
lineFormat: '- %message% %hash%',
Expand Down
2 changes: 1 addition & 1 deletion src/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { commitRelease, defineVersion, updateVersion } from './utils'
export const release = async (input, options, config) => {
if (!input) throw new Error('Relese requires a version')

const newVersion = defineVersion(input)
const newVersion = await defineVersion(input, config)

await updateVersion(newVersion)
await changelog(newVersion, { write: true, from: options.from }, config)
Expand Down
2 changes: 1 addition & 1 deletion src/release.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('release', () => {
await release(mockedVersion, {}, defaultConfig)

expect(defineVersion).toBeCalledTimes(1)
expect(defineVersion).toBeCalledWith(mockedVersion)
expect(defineVersion).toBeCalledWith(mockedVersion, defaultConfig)
expect(updateVersion).toBeCalledTimes(1)
expect(updateVersion).toBeCalledWith(mockedVersion)
expect(changelog).toBeCalledTimes(1)
Expand Down
5 changes: 2 additions & 3 deletions src/utils/changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ export const groupCommits = (commits, config) =>
const group = grouped[grouped.length - 1]
const rest = grouped.slice(0, -1)
const commitDetails = getCommitDetails(commit)
const normalizedScope =
commitDetails.scope && commitDetails.scope.toLowerCase()
const normalizedScope = commitDetails.scope?.toLowerCase()

if (config.ignoredScopes.includes(normalizedScope)) return [...grouped]
if (normalizedScope === 'release') {
Expand Down Expand Up @@ -84,7 +83,7 @@ export const generateReleased = (previousVersion, config) =>
export const generateChangelog = (version, groupedCommits, config) => {
const releases = groupedCommits.map(group => {
const release = getCommitDetails(group.release)
const releaseVersion = (release && release.message) || version
const releaseVersion = release?.message || version
const title = releaseVersion
? config.releaseFormat.replace(/%version%/g, releaseVersion)
: config.unreleasedFormat
Expand Down
20 changes: 10 additions & 10 deletions src/utils/changelog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ describe('changelog', () => {
const config = {
...defaultConfig,
groups: [
{ name: '## Feat', types: ['feat', 'feature'] },
{ name: '## Fix', types: ['fix'] },
{ name: '## Custom', types: ['custom'] }
{ name: '## Feat', type: 'minor', types: ['feat', 'feature'] },
{ name: '## Fix', type: 'patch', types: ['fix'] },
{ name: '## Custom', type: 'patch', types: ['custom'] }
],
ignoredScopes: ['ignored']
}
Expand Down Expand Up @@ -209,7 +209,7 @@ describe('changelog', () => {
const error = 'error'

existsSync.mockReturnValueOnce(true)
readFile.mockImplementation((_, __, cb) => cb(error))
readFile.mockImplementationOnce((_, __, cb) => cb(error))

expect(generateReleased(null, defaultConfig)).rejects.toMatch(error)
})
Expand All @@ -220,7 +220,7 @@ describe('changelog', () => {
'# Latest\n- feat: include changelog in the releases 2da21c56\n- test: add utils tests 217b25d0'

existsSync.mockReturnValueOnce(true)
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))
readFile.mockImplementationOnce((_, __, cb) => cb(null, mockedInput))

expect(generateReleased('2.2.2', defaultConfig)).rejects.toThrow(error)
})
Expand All @@ -231,7 +231,7 @@ describe('changelog', () => {
const mockedOutput = '# 2.2.2\n- feat: add feature 2da21c56'

existsSync.mockReturnValueOnce(true)
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))
readFile.mockImplementationOnce((_, __, cb) => cb(null, mockedInput))

const released = await generateReleased('2.2.2', defaultConfig)

Expand All @@ -249,7 +249,7 @@ describe('changelog', () => {
const mockedOutput = '## Release v2.2.2\n- feat: add feature 2da21c56'

existsSync.mockReturnValueOnce(true)
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))
readFile.mockImplementationOnce((_, __, cb) => cb(null, mockedInput))

const released = await generateReleased('2.2.2', config)

Expand Down Expand Up @@ -301,9 +301,9 @@ describe('changelog', () => {
releaseFormat: '# v.%version%',
breakingFormat: '### BREAKING CHANGE',
groups: [
{ name: '## Feat', types: ['feat', 'feature'] },
{ name: '## Fix', types: ['fix'] },
{ name: '## Custom', types: ['custom'] }
{ name: '## Feat', change: 'minor', types: ['feat', 'feature'] },
{ name: '## Fix', change: 'patch', types: ['fix'] },
{ name: '## Custom', change: 'patch', types: ['custom'] }
],
miscFormat: '### Miscellaneous',
lineFormat: '* %message% %hash%'
Expand Down
12 changes: 9 additions & 3 deletions src/utils/git.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { execAsync } from './misc'

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

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

export const getCommits = async from => {
const query = from
? `git log --format="%H %s" ${from}..`
Expand All @@ -9,10 +15,10 @@ export const getCommits = async from => {
return commits.split('\n').filter(commit => commit)
}

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

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

export const getCommitDetails = commit => {
Expand Down
69 changes: 57 additions & 12 deletions src/utils/git.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import {
commitRelease,
getCommitDetails,
getCommits,
getLatestTag
getLatestTag,
getUnreleasedCommits
} from './git'

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

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

expect(latestTag).toBe(null)
Expand All @@ -20,7 +21,7 @@ describe('git', () => {
})

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

expect(latestTag).toBe('2.2.2')
Expand All @@ -32,7 +33,7 @@ describe('git', () => {
describe('getCommits', () => {
it('should reject if receives an error', async () => {
const error = 'error'
exec.mockImplementation((_, cb) => cb(error))
exec.mockImplementationOnce((_, cb) => cb(error))

expect(getCommits()).rejects.toMatch(error)
})
Expand All @@ -48,7 +49,7 @@ describe('git', () => {
'4e02179cae1234d7083036024080a3f25fcb52c2 feat: add execute release feature'
]

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

expect(exec).toBeCalledTimes(1)
Expand All @@ -61,21 +62,65 @@ describe('git', () => {

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'
'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'
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'
'f2191200bf7b6e5eec3d61fcef9eb756e0129cfb chore(release): 0.1.0'
]

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

const commits = await getCommits('0.1.0')

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

describe('getUnreleasedCommits', () => {
it('should return null if there is no tag', async () => {
exec.mockImplementationOnce((_, cb) => cb(null, null))

const commits = await getUnreleasedCommits()

expect(exec).toBeCalledTimes(1)
expect(exec).toHaveBeenCalledWith(
'git tag | tail -n 1',
expect.any(Function)
)
expect(commits).toEqual(null)
})

it('should return unreleased commits', async () => {
const mockedInput =
'abfb3d86c05b3680a6aed505d0f9194f13912878 chore: update README badges\nee24740b920b22e916ca5ab0449abb08e99143c4 ci: setup CI with GitHub Actions\n8c3ff1c8776cb3cf739b5c8133fa2883b7909f7a chore: reword question\nd0e72481e9b9dc2bed8e495b8943d2d90399db32 feat: replace all placeholder occurrences\n73f2ecbf494ed97fcf34fce833014241fe74a6b6 chore(release): 1.2.0'
const mockedOutput = [
'abfb3d86c05b3680a6aed505d0f9194f13912878 chore: update README badges',
'ee24740b920b22e916ca5ab0449abb08e99143c4 ci: setup CI with GitHub Actions',
'8c3ff1c8776cb3cf739b5c8133fa2883b7909f7a chore: reword question',
'd0e72481e9b9dc2bed8e495b8943d2d90399db32 feat: replace all placeholder occurrences',
'73f2ecbf494ed97fcf34fce833014241fe74a6b6 chore(release): 1.2.0'
]

exec.mockImplementationOnce((_, cb) => cb(null, '1.2.0'))
exec.mockImplementationOnce((_, cb) => cb(null, mockedInput))

const commits = await getUnreleasedCommits()

expect(exec).toBeCalledTimes(2)
expect(exec).toHaveBeenNthCalledWith(
1,
'git tag | tail -n 1',
expect.any(Function)
)
expect(exec).toHaveBeenNthCalledWith(
2,
'git log --format="%H %s" 1.2.0..',
expect.any(Function)
)
expect(commits).toEqual(mockedOutput)
Expand Down
Loading

0 comments on commit 86aa5dc

Please sign in to comment.