Skip to content

fix: preserve frontmatter when updating changelog #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 31, 2023
35 changes: 28 additions & 7 deletions lib/lifecycles/changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,38 @@ Changelog.START_OF_LAST_RELEASE_PATTERN = START_OF_LAST_RELEASE_PATTERN

module.exports = Changelog

/**
* Front matter is only extracted and therefore retained in final output where Changelog "header" begins with #Changelog,
* e.g. meets Standard-Version (last release) or commit-and-tag-version(current) format
*/
function extractFrontMatter (oldContent) {
const headerStart = oldContent.indexOf('# Changelog')
return headerStart !== -1 || headerStart !== 0
? oldContent.substring(0, headerStart)
: ''
}

/**
* find the position of the last release and remove header
*/
function extractChangelogBody (oldContent) {
const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
return oldContentStart !== -1
? oldContent.substring(oldContentStart)
: oldContent
}

function outputChangelog (args, newVersion) {
return new Promise((resolve, reject) => {
createIfMissing(args)
const header = args.header

let oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8')
const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
// find the position of the last release and remove header:
if (oldContentStart !== -1) {
oldContent = oldContent.substring(oldContentStart)
}
const oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8')

const oldContentBody = extractChangelogBody(oldContent)

const changelogFrontMatter = extractFrontMatter(oldContent)

let content = ''
const context = { version: newVersion }
const changelogStream = conventionalChangelog({
Expand All @@ -48,7 +69,7 @@ function outputChangelog (args, newVersion) {
changelogStream.on('end', function () {
checkpoint(args, 'outputting changes to %s', [args.infile])
if (args.dryRun) console.info(`\n---\n${chalk.gray(content.trim())}\n---\n`)
else writeFile(args, args.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'))
else writeFile(args, args.infile, changelogFrontMatter + header + '\n' + (content + oldContentBody).replace(/\n+$/, '\n'))
return resolve()
})
})
Expand Down
60 changes: 58 additions & 2 deletions test/core.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,73 @@ describe('cli', function () {
})

describe('CHANGELOG.md exists', function () {
it('appends the new release above the last release, removing the old header (legacy format)', async function () {
it('appends the new release above the last release, removing the old header (legacy format), and does not retain any front matter', async function () {
const frontMatter =
'---\nstatus: new\n---\n'
mock({
bump: 'patch',
changelog: 'release 1.0.1\n',
fs: { 'CHANGELOG.md': 'legacy header format<a name="1.0.0">\n' },
fs: { 'CHANGELOG.md': frontMatter + 'legacy header format<a name="1.0.0">\n' },
tags: ['v1.0.0']
})
await exec()
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
content.should.match(/1\.0\.1/)
content.should.not.match(/legacy header format/)
content.should.not.match(/---status: new---/)
})

it('appends the new release above the last release, replacing the old header (standard-version format) with header (new format), and retains any front matter', async function () {
const { header } = require('../defaults')

const standardVersionHeader =
'# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.'

const frontMatter =
'---\nstatus: new\n---\n'

const changelog101 =
'### [1.0.1](/compare/v1.0.0...v1.0.1) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* patch release ABCDEFXY\n'

const changelog100 =
'### [1.0.0](/compare/v0.0.1...v1.0.0) (YYYY-MM-DD)\n\n\n### Features\n\n* Version one feature set\n'

const initialChangelog = frontMatter + '\n' + standardVersionHeader + '\n' + changelog100

mock({
bump: 'patch',
changelog: changelog101,
fs: { 'CHANGELOG.md': initialChangelog },
tags: ['v1.0.0']
})
await exec()
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
content.should.equal(frontMatter + '\n' + header + '\n' + changelog101 + changelog100)
})

it('appends the new release above the last release, removing the old header (new format), and retains any front matter', async function () {
const { header } = require('../defaults')
const frontMatter =
'---\nstatus: new\n---\n'

const changelog101 =
'### [1.0.1](/compare/v1.0.0...v1.0.1) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* patch release ABCDEFXY\n'

const changelog100 =
'### [1.0.0](/compare/v0.0.1...v1.0.0) (YYYY-MM-DD)\n\n\n### Features\n\n* Version one feature set\n'

const initialChangelog = frontMatter + '\n' + header + '\n' + changelog100

mock({
bump: 'patch',
changelog: changelog101,
fs: { 'CHANGELOG.md': initialChangelog },
tags: ['v1.0.0']
})
await exec()

const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
content.should.equal(frontMatter + '\n' + header + '\n' + changelog101 + changelog100)
})

it('appends the new release above the last release, removing the old header (new format)', async function () {
Expand Down