Skip to content

Commit abdcfe2

Browse files
fix: preserve frontmatter when updating changelog (#108)
* chore: add failing test for retaining frontmatter Signed-off-by: Tim Knight <[email protected]> * fix: preserve frontmatter when updating changelog fixes #106 Signed-off-by: Tim Knight <[email protected]> * chore: add tests for upgrade path from standard-version Signed-off-by: Tim Knight <[email protected]> * fix: preserve front matter when swapping from standard-version - Matches based off # Changelog title position rather than new header position Signed-off-by: Tim Knight <[email protected]> * chore: run linting Signed-off-by: Tim Knight <[email protected]> * chore: refactoring, extract functions Signed-off-by: Tim Knight <[email protected]> * chore: front matter usually on separate lines to title Signed-off-by: Tim Knight <[email protected]> * chore: apply eslint . --fix Signed-off-by: Tim Knight <[email protected]> --------- Signed-off-by: Tim Knight <[email protected]>
1 parent e4419a7 commit abdcfe2

File tree

2 files changed

+86
-9
lines changed

2 files changed

+86
-9
lines changed

lib/lifecycles/changelog.js

+28-7
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,38 @@ Changelog.START_OF_LAST_RELEASE_PATTERN = START_OF_LAST_RELEASE_PATTERN
1818

1919
module.exports = Changelog
2020

21+
/**
22+
* Front matter is only extracted and therefore retained in final output where Changelog "header" begins with #Changelog,
23+
* e.g. meets Standard-Version (last release) or commit-and-tag-version(current) format
24+
*/
25+
function extractFrontMatter (oldContent) {
26+
const headerStart = oldContent.indexOf('# Changelog')
27+
return headerStart !== -1 || headerStart !== 0
28+
? oldContent.substring(0, headerStart)
29+
: ''
30+
}
31+
32+
/**
33+
* find the position of the last release and remove header
34+
*/
35+
function extractChangelogBody (oldContent) {
36+
const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
37+
return oldContentStart !== -1
38+
? oldContent.substring(oldContentStart)
39+
: oldContent
40+
}
41+
2142
function outputChangelog (args, newVersion) {
2243
return new Promise((resolve, reject) => {
2344
createIfMissing(args)
2445
const header = args.header
2546

26-
let oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8')
27-
const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN)
28-
// find the position of the last release and remove header:
29-
if (oldContentStart !== -1) {
30-
oldContent = oldContent.substring(oldContentStart)
31-
}
47+
const oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8')
48+
49+
const oldContentBody = extractChangelogBody(oldContent)
50+
51+
const changelogFrontMatter = extractFrontMatter(oldContent)
52+
3253
let content = ''
3354
const context = { version: newVersion }
3455
const changelogStream = conventionalChangelog({
@@ -48,7 +69,7 @@ function outputChangelog (args, newVersion) {
4869
changelogStream.on('end', function () {
4970
checkpoint(args, 'outputting changes to %s', [args.infile])
5071
if (args.dryRun) console.info(`\n---\n${chalk.gray(content.trim())}\n---\n`)
51-
else writeFile(args, args.infile, header + '\n' + (content + oldContent).replace(/\n+$/, '\n'))
72+
else writeFile(args, args.infile, changelogFrontMatter + header + '\n' + (content + oldContentBody).replace(/\n+$/, '\n'))
5273
return resolve()
5374
})
5475
})

test/core.spec.js

+58-2
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,73 @@ describe('cli', function () {
165165
})
166166

167167
describe('CHANGELOG.md exists', function () {
168-
it('appends the new release above the last release, removing the old header (legacy format)', async function () {
168+
it('appends the new release above the last release, removing the old header (legacy format), and does not retain any front matter', async function () {
169+
const frontMatter =
170+
'---\nstatus: new\n---\n'
169171
mock({
170172
bump: 'patch',
171173
changelog: 'release 1.0.1\n',
172-
fs: { 'CHANGELOG.md': 'legacy header format<a name="1.0.0">\n' },
174+
fs: { 'CHANGELOG.md': frontMatter + 'legacy header format<a name="1.0.0">\n' },
173175
tags: ['v1.0.0']
174176
})
175177
await exec()
176178
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
177179
content.should.match(/1\.0\.1/)
178180
content.should.not.match(/legacy header format/)
181+
content.should.not.match(/---status: new---/)
182+
})
183+
184+
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 () {
185+
const { header } = require('../defaults')
186+
187+
const standardVersionHeader =
188+
'# 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.'
189+
190+
const frontMatter =
191+
'---\nstatus: new\n---\n'
192+
193+
const changelog101 =
194+
'### [1.0.1](/compare/v1.0.0...v1.0.1) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* patch release ABCDEFXY\n'
195+
196+
const changelog100 =
197+
'### [1.0.0](/compare/v0.0.1...v1.0.0) (YYYY-MM-DD)\n\n\n### Features\n\n* Version one feature set\n'
198+
199+
const initialChangelog = frontMatter + '\n' + standardVersionHeader + '\n' + changelog100
200+
201+
mock({
202+
bump: 'patch',
203+
changelog: changelog101,
204+
fs: { 'CHANGELOG.md': initialChangelog },
205+
tags: ['v1.0.0']
206+
})
207+
await exec()
208+
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
209+
content.should.equal(frontMatter + '\n' + header + '\n' + changelog101 + changelog100)
210+
})
211+
212+
it('appends the new release above the last release, removing the old header (new format), and retains any front matter', async function () {
213+
const { header } = require('../defaults')
214+
const frontMatter =
215+
'---\nstatus: new\n---\n'
216+
217+
const changelog101 =
218+
'### [1.0.1](/compare/v1.0.0...v1.0.1) (YYYY-MM-DD)\n\n\n### Bug Fixes\n\n* patch release ABCDEFXY\n'
219+
220+
const changelog100 =
221+
'### [1.0.0](/compare/v0.0.1...v1.0.0) (YYYY-MM-DD)\n\n\n### Features\n\n* Version one feature set\n'
222+
223+
const initialChangelog = frontMatter + '\n' + header + '\n' + changelog100
224+
225+
mock({
226+
bump: 'patch',
227+
changelog: changelog101,
228+
fs: { 'CHANGELOG.md': initialChangelog },
229+
tags: ['v1.0.0']
230+
})
231+
await exec()
232+
233+
const content = fs.readFileSync('CHANGELOG.md', 'utf-8')
234+
content.should.equal(frontMatter + '\n' + header + '\n' + changelog101 + changelog100)
179235
})
180236

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

0 commit comments

Comments
 (0)