From fd1cfe9ee68dc4569aaad4c23bc913e1cf19f5ea Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 13:47:21 +0100 Subject: [PATCH 1/8] chore: add failing test for retaining frontmatter Signed-off-by: Tim Knight --- test/core.spec.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/core.spec.js b/test/core.spec.js index 077d45150..fc0a78a9d 100644 --- a/test/core.spec.js +++ b/test/core.spec.js @@ -178,6 +178,31 @@ describe('cli', function () { content.should.not.match(/legacy header format/) }) + it('retains any frontmatter in an existing Changelog with correct header', async function () { + const { header } = require('../defaults') + const frontMatter = + '---\nstatus: new\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() + + let 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 () { const { header } = require('../defaults') const changelog1 = From ee7953c1262380e85d68b977645521d051ca6a8a Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 14:10:47 +0100 Subject: [PATCH 2/8] fix: preserve frontmatter when updating changelog fixes #106 Signed-off-by: Tim Knight --- lib/lifecycles/changelog.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/lifecycles/changelog.js b/lib/lifecycles/changelog.js index 6d4ba990a..f7cf8f7a4 100644 --- a/lib/lifecycles/changelog.js +++ b/lib/lifecycles/changelog.js @@ -23,12 +23,21 @@ function outputChangelog (args, newVersion) { 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) + const oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8') + // find the position of the last release and remove header: - if (oldContentStart !== -1) { - oldContent = oldContent.substring(oldContentStart) - } + const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN) + const oldContentBody = + oldContentStart !== -1 + ? oldContent.substring(oldContentStart) + : oldContent; + + const headerStart = oldContent.indexOf(header); + const changelogFrontMatter = + headerStart !== -1 || headerStart !== 0 + ? oldContent.substring(0, headerStart) + : '' + let content = '' const context = { version: newVersion } const changelogStream = conventionalChangelog({ @@ -48,7 +57,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() }) }) From c223490279b8354a30d85628fdfd043b093c44b5 Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 14:36:38 +0100 Subject: [PATCH 3/8] chore: add tests for upgrade path from standard-version Signed-off-by: Tim Knight --- test/core.spec.js | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/test/core.spec.js b/test/core.spec.js index fc0a78a9d..7aa539fea 100644 --- a/test/core.spec.js +++ b/test/core.spec.js @@ -15,6 +15,7 @@ const cli = require('../command') const formatCommitMessage = require('../lib/format-commit-message') const chai = require('chai') +const {header} = require("../defaults"); const should = chai.should() const expect = chai.expect chai.use(require('chai-as-promised')) @@ -165,20 +166,51 @@ 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---' mock({ bump: 'patch', changelog: 'release 1.0.1\n', - fs: { 'CHANGELOG.md': 'legacy header format\n' }, + fs: { 'CHANGELOG.md': frontMatter + 'legacy header format\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---' + + 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('retains any frontmatter in an existing Changelog with correct header', async function () { + 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---' From fac92ab54c0045569e6fcfbb7a84ec77b2cbe906 Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 14:37:23 +0100 Subject: [PATCH 4/8] 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 --- lib/lifecycles/changelog.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/lifecycles/changelog.js b/lib/lifecycles/changelog.js index f7cf8f7a4..bee5a93d6 100644 --- a/lib/lifecycles/changelog.js +++ b/lib/lifecycles/changelog.js @@ -32,7 +32,9 @@ function outputChangelog (args, newVersion) { ? oldContent.substring(oldContentStart) : oldContent; - const headerStart = oldContent.indexOf(header); + // Front matter is only retained where Changelog "header" begins with #Changelog, + // e.g. meets Standard-Version (last release) or commit-and-tag-version(current) format + const headerStart = oldContent.indexOf('# Changelog'); const changelogFrontMatter = headerStart !== -1 || headerStart !== 0 ? oldContent.substring(0, headerStart) From 50d8f8fd8d2f24343320c16aa45587c63606c2a1 Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 14:40:40 +0100 Subject: [PATCH 5/8] chore: run linting Signed-off-by: Tim Knight --- lib/lifecycles/changelog.js | 10 +++++----- test/core.spec.js | 11 +++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/lifecycles/changelog.js b/lib/lifecycles/changelog.js index bee5a93d6..80a6a21dc 100644 --- a/lib/lifecycles/changelog.js +++ b/lib/lifecycles/changelog.js @@ -29,16 +29,16 @@ function outputChangelog (args, newVersion) { const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN) const oldContentBody = oldContentStart !== -1 - ? oldContent.substring(oldContentStart) - : oldContent; + ? oldContent.substring(oldContentStart) + : oldContent // Front matter is only retained where Changelog "header" begins with #Changelog, // e.g. meets Standard-Version (last release) or commit-and-tag-version(current) format - const headerStart = oldContent.indexOf('# Changelog'); + const headerStart = oldContent.indexOf('# Changelog') const changelogFrontMatter = headerStart !== -1 || headerStart !== 0 - ? oldContent.substring(0, headerStart) - : '' + ? oldContent.substring(0, headerStart) + : '' let content = '' const context = { version: newVersion } diff --git a/test/core.spec.js b/test/core.spec.js index 7aa539fea..014071d56 100644 --- a/test/core.spec.js +++ b/test/core.spec.js @@ -15,7 +15,6 @@ const cli = require('../command') const formatCommitMessage = require('../lib/format-commit-message') const chai = require('chai') -const {header} = require("../defaults"); const should = chai.should() const expect = chai.expect chai.use(require('chai-as-promised')) @@ -197,7 +196,7 @@ describe('cli', function () { 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; + const initialChangelog = frontMatter + '\n' + standardVersionHeader + '\n' + changelog100 mock({ bump: 'patch', @@ -207,7 +206,7 @@ describe('cli', function () { }) await exec() const content = fs.readFileSync('CHANGELOG.md', 'utf-8') - content.should.equal(frontMatter + '\n' + header + '\n' + changelog101 + changelog100 ) + 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 () { @@ -221,7 +220,7 @@ describe('cli', function () { 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; + const initialChangelog = frontMatter + '\n' + header + '\n' + changelog100 mock({ bump: 'patch', @@ -231,8 +230,8 @@ describe('cli', function () { }) await exec() - let content = fs.readFileSync('CHANGELOG.md', 'utf-8') - content.should.equal(frontMatter + '\n' + header + '\n' + changelog101 + changelog100 ) + 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 () { From df6b24f31eab8581c782caef02127ce0f2b0db9f Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 14:43:12 +0100 Subject: [PATCH 6/8] chore: refactoring, extract functions Signed-off-by: Tim Knight --- lib/lifecycles/changelog.js | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/lifecycles/changelog.js b/lib/lifecycles/changelog.js index 80a6a21dc..d20262065 100644 --- a/lib/lifecycles/changelog.js +++ b/lib/lifecycles/changelog.js @@ -18,6 +18,27 @@ 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) @@ -25,20 +46,9 @@ function outputChangelog (args, newVersion) { const oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8') - // find the position of the last release and remove header: - const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN) - const oldContentBody = - oldContentStart !== -1 - ? oldContent.substring(oldContentStart) - : oldContent + const oldContentBody = extractChangelogBody(oldContent); - // Front matter is only retained where Changelog "header" begins with #Changelog, - // e.g. meets Standard-Version (last release) or commit-and-tag-version(current) format - const headerStart = oldContent.indexOf('# Changelog') - const changelogFrontMatter = - headerStart !== -1 || headerStart !== 0 - ? oldContent.substring(0, headerStart) - : '' + const changelogFrontMatter = extractFrontMatter(oldContent); let content = '' const context = { version: newVersion } From 483c555431853b3d66619b3fa9585c69dadff3ad Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Mon, 23 Oct 2023 14:46:26 +0100 Subject: [PATCH 7/8] chore: front matter usually on separate lines to title Signed-off-by: Tim Knight --- test/core.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/core.spec.js b/test/core.spec.js index 014071d56..a8515dc60 100644 --- a/test/core.spec.js +++ b/test/core.spec.js @@ -167,7 +167,7 @@ describe('cli', function () { describe('CHANGELOG.md exists', 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---' + '---\nstatus: new\n---\n' mock({ bump: 'patch', changelog: 'release 1.0.1\n', @@ -188,7 +188,7 @@ describe('cli', function () { '# 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---' + '---\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' @@ -212,7 +212,7 @@ describe('cli', function () { 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---' + '---\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' From 807a82e3cbb113eb4e7a337efb51e5bcea4f0887 Mon Sep 17 00:00:00 2001 From: Tim Knight Date: Tue, 31 Oct 2023 10:08:05 +0000 Subject: [PATCH 8/8] chore: apply eslint . --fix Signed-off-by: Tim Knight --- lib/lifecycles/changelog.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/lifecycles/changelog.js b/lib/lifecycles/changelog.js index d20262065..5bdd573ac 100644 --- a/lib/lifecycles/changelog.js +++ b/lib/lifecycles/changelog.js @@ -22,21 +22,21 @@ 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) { +function extractFrontMatter (oldContent) { const headerStart = oldContent.indexOf('# Changelog') return headerStart !== -1 || headerStart !== 0 - ? oldContent.substring(0, headerStart) - : ''; + ? oldContent.substring(0, headerStart) + : '' } /** * find the position of the last release and remove header */ -function extractChangelogBody(oldContent) { +function extractChangelogBody (oldContent) { const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN) return oldContentStart !== -1 - ? oldContent.substring(oldContentStart) - : oldContent; + ? oldContent.substring(oldContentStart) + : oldContent } function outputChangelog (args, newVersion) { @@ -46,9 +46,9 @@ function outputChangelog (args, newVersion) { const oldContent = args.dryRun || args.releaseCount === 0 ? '' : fs.readFileSync(args.infile, 'utf-8') - const oldContentBody = extractChangelogBody(oldContent); + const oldContentBody = extractChangelogBody(oldContent) - const changelogFrontMatter = extractFrontMatter(oldContent); + const changelogFrontMatter = extractFrontMatter(oldContent) let content = '' const context = { version: newVersion }