From dd97682287419d50a6c67dce859d75c6ab7cd4d3 Mon Sep 17 00:00:00 2001 From: Bernhard Wittmann Date: Sat, 21 Jan 2023 12:48:21 +0100 Subject: [PATCH] feat: :sparkles: add skipOnPrerelease config to not update changelog on prereleases When working a lot with prereleases the changelog file can become quite bloated with the prereleases To provide easier overview one might only want to include normal releases in the changelog. That is why i added the skipOnPrerelease config flag which is false by default But if you turn it on it will prevent updates on prereleases --- README.md | 11 +++--- lib/definitions/errors.js | 6 ++++ lib/prepare.js | 11 ++++-- lib/resolve-config.js | 3 +- lib/verify.js | 3 +- test/integration.test.js | 15 ++++++++ test/prepare.test.js | 75 ++++++++++++++++++++++++++++++++++++++- test/verify.test.js | 8 +++++ 8 files changed, 122 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ad0921f3..4a2f9722 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ | Step | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `verifyConditions` | Verify the `changelogFile` and `changelogTitle` options configuration. | +| `verifyConditions` | Verify the `changelogFile`, `changelogTitle` and `skipOnPrerelease` options configuration. | | `prepare` | Create or update a changelog file in the local project directory with the changelog content created in the [generate notes step](https://github.com/semantic-release/semantic-release#release-steps). | ## Install @@ -47,10 +47,11 @@ With this example, for each release, a `docs/CHANGELOG.md` will be created or up ### Options -| Options | Description | Default | -| ---------------- | ----------------------------------------------------- | -------------- | -| `changelogFile` | File path of the changelog. | `CHANGELOG.md` | -| `changelogTitle` | Title of the changelog file (first line of the file). | - | +| Options | Description | Default | +| ------------------ | -------------------------------------------------------- | -------------- | +| `changelogFile` | File path of the changelog. | `CHANGELOG.md` | +| `changelogTitle` | Title of the changelog file (first line of the file). | - | +| `skipOnPrerelease` | Skip changelog update when branch is a prerelease branch | `false` | ### Examples diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index fecd1369..1bb16aae 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -20,4 +20,10 @@ Your configuration for the \`changelogFile\` option is \`${changelogFile}\`.`, Your configuration for the \`changelogTitle\` option is \`${changelogTitle}\`.`, }), + EINVALIDSKIPONPRERELEASE: ({skipOnPrerelease}) => ({ + message: 'Invalid `skipOnPrerelease` option.', + details: `The [skipOnPrerelease option](${linkify('README.md#options')}) option, if defined, must be a \`Boolean\`. + +Your configuration for the \`skipOnPrerelease\` option is \`${skipOnPrerelease}\`.`, + }), }; diff --git a/lib/prepare.js b/lib/prepare.js index 7ce101a3..19f1bd8e 100644 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -2,10 +2,17 @@ const path = require('path'); const {readFile, writeFile, ensureFile} = require('fs-extra'); const resolveConfig = require('./resolve-config.js'); -module.exports = async (pluginConfig, {cwd, nextRelease: {notes}, logger}) => { - const {changelogFile, changelogTitle} = resolveConfig(pluginConfig); +const isPrerelease = ({type, main}) => type === 'prerelease' || (type === 'release' && !main); + +module.exports = async (pluginConfig, {cwd, nextRelease: {notes}, logger, branch}) => { + const {changelogFile, changelogTitle, skipOnPrerelease} = resolveConfig(pluginConfig); const changelogPath = path.resolve(cwd, changelogFile); + if (skipOnPrerelease && isPrerelease(branch)) { + logger.log('Skipping because branch is a prerelease branch and option skipOnPrerelease is active'); + return; + } + if (notes) { await ensureFile(changelogPath); const currentFile = (await readFile(changelogPath)).toString().trim(); diff --git a/lib/resolve-config.js b/lib/resolve-config.js index 2ee6c59a..fd6defe4 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -1,6 +1,7 @@ const {isNil} = require('lodash'); -module.exports = ({changelogFile, changelogTitle}) => ({ +module.exports = ({changelogFile, changelogTitle, skipOnPrerelease}) => ({ changelogFile: isNil(changelogFile) ? 'CHANGELOG.md' : changelogFile, changelogTitle, + skipOnPrerelease: isNil(skipOnPrerelease) ? false : skipOnPrerelease, }); diff --git a/lib/verify.js b/lib/verify.js index f4b93edb..c047b8f7 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -1,4 +1,4 @@ -const {isString, isNil} = require('lodash'); +const {isString, isNil, isBoolean} = require('lodash'); const AggregateError = require('aggregate-error'); const getError = require('./get-error.js'); const resolveConfig = require('./resolve-config.js'); @@ -8,6 +8,7 @@ const isNonEmptyString = (value) => isString(value) && value.trim(); const VALIDATORS = { changelogFile: isNonEmptyString, changelogTitle: isNonEmptyString, + skipOnPrerelease: isBoolean, }; module.exports = (pluginConfig) => { diff --git a/test/integration.test.js b/test/integration.test.js index ac294a8a..e4be3b2a 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -48,6 +48,21 @@ test.serial('Skip changelog update if the release is empty', async (t) => { t.is((await readFile(changelogPath)).toString(), 'Initial CHANGELOG'); }); +test.serial('Skip changelog update if the release is a prerelease and skipOnPrerelease option is set', async (t) => { + const cwd = tempy.directory(); + const changelogFile = 'CHANGELOG.txt'; + const changelogPath = path.resolve(cwd, changelogFile); + await outputFile(changelogPath, 'Initial CHANGELOG'); + + await t.context.m.prepare( + {skipOnPrerelease: true}, + {cwd, options: {}, nextRelease: {}, logger: t.context.logger, branch: {type: 'prerelease'}} + ); + + // Verify the content of the CHANGELOG.md + t.is((await readFile(changelogPath)).toString(), 'Initial CHANGELOG'); +}); + test.serial('Verify only on the fist call', async (t) => { const cwd = tempy.directory(); const notes = 'Test release note'; diff --git a/test/prepare.test.js b/test/prepare.test.js index e3037d35..26a99b75 100644 --- a/test/prepare.test.js +++ b/test/prepare.test.js @@ -1,6 +1,6 @@ const path = require('path'); const test = require('ava'); -const {outputFile, readFile} = require('fs-extra'); +const {outputFile, readFile, pathExists} = require('fs-extra'); const {stub} = require('sinon'); const tempy = require('tempy'); const prepare = require('../lib/prepare.js'); @@ -90,3 +90,76 @@ test.serial('Create new changelog with title if specified', async (t) => { t.is((await readFile(changelogPath)).toString(), `${changelogTitle}\n\n${notes}\n`); }); + +test.serial('Skip creation of changelog if skipOnPrerelease is set on prerelease branches', async (t) => { + const cwd = tempy.directory(); + const notes = 'Test release note'; + const changelogFile = 'CHANGELOG.md'; + const changelogPath = path.resolve(cwd, changelogFile); + + await prepare( + {skipOnPrerelease: true}, + {cwd, nextRelease: {notes}, branch: {type: 'prerelease', main: false}, logger: t.context.logger} + ); + + // Verify the content of the CHANGELOG.md + t.is(await pathExists(changelogPath), false); + t.deepEqual(t.context.log.args[0], [ + 'Skipping because branch is a prerelease branch and option skipOnPrerelease is active', + ]); +}); + +test('Skip update of changelog if skipOnPrerelease is set on prerelease branches', async (t) => { + const cwd = tempy.directory(); + const notes = 'Test release note'; + const changelogFile = 'CHANGELOG.md'; + const changelogPath = path.resolve(cwd, changelogFile); + await outputFile(changelogPath, 'Initial CHANGELOG'); + + await prepare( + {skipOnPrerelease: true}, + {cwd, nextRelease: {notes}, branch: {type: 'prerelease', main: false}, logger: t.context.logger} + ); + + // Verify the content of the CHANGELOG.md + t.is((await readFile(changelogPath)).toString(), `Initial CHANGELOG`); + t.deepEqual(t.context.log.args[0], [ + 'Skipping because branch is a prerelease branch and option skipOnPrerelease is active', + ]); +}); + +test('Skip update of changelog if skipOnPrerelease is set on release branches but it is not main', async (t) => { + const cwd = tempy.directory(); + const notes = 'Test release note'; + const changelogFile = 'CHANGELOG.md'; + const changelogPath = path.resolve(cwd, changelogFile); + await outputFile(changelogPath, 'Initial CHANGELOG'); + + await prepare( + {skipOnPrerelease: true}, + {cwd, nextRelease: {notes}, branch: {type: 'release', main: false}, logger: t.context.logger} + ); + + // Verify the content of the CHANGELOG.md + t.is((await readFile(changelogPath)).toString(), `Initial CHANGELOG`); + t.deepEqual(t.context.log.args[0], [ + 'Skipping because branch is a prerelease branch and option skipOnPrerelease is active', + ]); +}); + +test('Ensure update of changelog if skipOnPrerelease is not set on prerelease branches', async (t) => { + const cwd = tempy.directory(); + const notes = 'Test release note'; + const changelogFile = 'CHANGELOG.md'; + const changelogPath = path.resolve(cwd, changelogFile); + await outputFile(changelogPath, 'Initial CHANGELOG'); + + await prepare( + {skipOnPrerelease: false}, + {cwd, nextRelease: {notes}, branch: {type: 'prerelease', main: false}, logger: t.context.logger} + ); + + // Verify the content of the CHANGELOG.md + t.is((await readFile(changelogPath)).toString(), `${notes}\n\nInitial CHANGELOG\n`); + t.deepEqual(t.context.log.args[0], ['Update %s', changelogPath]); +}); diff --git a/test/verify.test.js b/test/verify.test.js index 57703e64..3e7e1e1e 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -57,3 +57,11 @@ test('Throw SemanticReleaseError if "changelogTitle" option is a whitespace Stri t.is(error.name, 'SemanticReleaseError'); t.is(error.code, 'EINVALIDCHANGELOGTITLE'); }); + +test('Throw SemanticReleaseError if "skipOnPrerelease" option is not a boolean', (t) => { + const skipOnPrerelease = 'wrong'; + const [error] = t.throws(() => verify({skipOnPrerelease})); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSKIPONPRERELEASE'); +});