Skip to content

Commit

Permalink
just dont check previous, add another validation check (#36)
Browse files Browse the repository at this point in the history
* just dont check previous, add another validation check

* add period

* fix array.from brackets

* didnt get strictLatest, remove option

* add tests, order check, many unreleased (disallowed) check

* clarify, must be valid for greater than
  • Loading branch information
Mopsgamer authored Jan 7, 2025
1 parent dff5391 commit a770ba8
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 31 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ In [release-it](https://github.com/release-it/release-it) config:
| option | default value | description |
| ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| filename | `'CHANGELOG.md'` | File with changelogs. |
| strictLatest | `true` | Entry of latest version must be present in order to get correct changelog. Set this option to `false` if you expect latest version without logs. |
| addUnreleased | `false` | It leaves "Unreleased" title row if set to `true`. |
| keepUnreleased | `false` | It leaves "Unreleased" title row unchanged if set to `true`. |
| addVersionUrl | `false` | Links the version to the according changeset. Uses GitHub-compatible URLs by default, see other options to configure the URL format. |
Expand Down
42 changes: 34 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs';
import path from 'path';
import { detectNewline } from 'detect-newline';
import format from 'string-template';
import semver from "semver";

const pad = num => ('0' + num).slice(-2);

Expand All @@ -28,10 +29,9 @@ const defaultVersionUrlFormats = {
class KeepAChangelog extends Plugin {
async init() {
await super.init();
const { filename, strictLatest, addUnreleased, keepUnreleased, addVersionUrl, versionUrlFormats, head } = this.options;
const { filename, addUnreleased, keepUnreleased, addVersionUrl, versionUrlFormats, head } = this.options;

this.filename = filename || 'CHANGELOG.md';
this.strictLatest = strictLatest === undefined ? true : Boolean(strictLatest);
this.addUnreleased = addUnreleased === undefined ? false : Boolean(addUnreleased);
this.keepUnreleased = keepUnreleased === undefined ? false : Boolean(keepUnreleased);
this.addVersionUrl = addVersionUrl === undefined ? false : Boolean(addVersionUrl);
Expand All @@ -56,15 +56,41 @@ class KeepAChangelog extends Plugin {
const { changelog } = this.getContext();
if (changelog) return changelog;

const { filename, strictLatest } = this;
const previousReleaseTitle = strictLatest ? `## [${latestVersion}]` : `## [`;
const hasPreviousReleaseSection = this.changelogContent.includes(previousReleaseTitle);
if (strictLatest && !hasPreviousReleaseSection) {
throw Error(`Missing section for previous release ("${latestVersion}") in ${filename}.`);
const unreleasedTitleRawList = Array.from(this.changelogContent.matchAll(/(?<=## \[)Unreleased(?=])/g)).map(ver => ver?.[0])
if(unreleasedTitleRawList.length > 1) {
throw new Error(`Too many "Unreleased" sections in ${this.filename}: ${unreleasedTitleRawList.length}.`)
}
const versionTitleRawList = Array.from(this.changelogContent.matchAll(/(?<=## \[)((\d+\.){2}\d+[^\]]*)/g)).map(ver => ver?.[0])
const badTitleRawList = versionTitleRawList.filter(ver => {
// invalid: ver > latestVersion
return !semver.valid(ver) || semver.gt(ver, latestVersion);
})
if(badTitleRawList.length > 0) {
throw new Error(`Invalid versions in ${this.filename}: ${badTitleRawList.join(', ')}. Current: ${latestVersion}.`)
}
const unorderedTitleRawList = versionTitleRawList.filter((ver, veri) => {
const previous = versionTitleRawList[veri - 1];
const next = versionTitleRawList[veri + 1];

if(previous) {
// bad order: ver > previous
return semver.gt(ver, previous)
}

if(next) {
// bad order: ver < next
return semver.lt(ver, next)
}

return false
})
if(unorderedTitleRawList.length > 0) {
throw new Error(`Invalid sections order in ${this.filename}: ${unorderedTitleRawList.join(', ')}.`)
}

const { isIncrement } = this.config;
const titleToFind = isIncrement ? this.unreleasedTitleRaw : latestVersion;
// use unreleased if initial release
const titleToFind = isIncrement || versionTitleRawList.length === 0 ? this.unreleasedTitleRaw : latestVersion;
const changelogContent = this.getChangelogEntryContent(titleToFind);

this.setContext({ changelog: changelogContent });
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"dependencies": {
"detect-newline": "^4.0.1",
"semver": "^7.6.3",
"string-template": "^1.0.0"
},
"devDependencies": {
Expand Down
32 changes: 10 additions & 22 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const initialDryRunFileContents =

vol.fromJSON({
'./CHANGELOG-FOO.md': '## [FOO]\n\n* Item A\n* Item B',
'./CHANGELOG-MISSING.md': '## [Unreleased]\n\n* Item A\n* Item B',
'./CHANGELOG-MULTIPLE_UNRELEASED.md': '## [Unreleased]\n* Item A\n\n## [Unreleased]* Item B\nB\n* Item C\n* Item D',
'./CHANGELOG-INVALID_ORDER.md': '## [Unreleased]\n\n## [0.2.0]\n* Item A\n\n## [0.3.0]* Item B\nB\n\n## [0.1.0]\n* Item C\n* Item D',
'./CHANGELOG-EMPTY.md': '## [Unreleased]\n\n\n\n## [1.0.0]\n\n* Item A\n* Item B',
'./CHANGELOG-FULL.md':
'# Changelog\n\n## [Unreleased]\n\n* Item A\n* Item B\n\n## [1.0.0] - 2020-05-02\n\n* Item C\n* Item D',
'./CHANGELOG-NO-STRICT.md': '## [Unreleased]\n\n* Item A\n* Item B\n\n## [1.0.0] - 2020-05-02\n\n* Item C\n* Item D',
'./CHANGELOG-DRYRUN.md': initialDryRunFileContents,
'./CHANGELOG-LESS_NEW_LINES.md': '## [Unreleased]\n* Item A\n* Item B\n## [1.0.0] - 2020-05-02\n* Item C\n* Item D',
'./CHANGELOG-EOL.md':
Expand Down Expand Up @@ -65,10 +65,15 @@ test('should throw for missing "unreleased" section when no-increment flag is se
await assert.rejects(runTasks(plugin), /Missing "Unreleased" section in CHANGELOG-FOO.md/);
});

test('should throw for missing "1.0.0" section when no-increment flag is set', async t => {
const options = { increment: false, [namespace]: { filename: 'CHANGELOG-MISSING.md' } };
test('should throw for multiple "unreleased" sections', async t => {
const options = { [namespace]: { filename: 'CHANGELOG-MULTIPLE_UNRELEASED.md' } };
const plugin = factory(Plugin, { namespace, options });
await assert.rejects(runTasks(plugin), /Missing section for previous release \("1\.0\.0"\) in CHANGELOG-MISSING\.md/);
await assert.rejects(runTasks(plugin), /Too many "Unreleased" sections in CHANGELOG-MULTIPLE_UNRELEASED.md: \d+./);
});
test('should throw for invalid order', async t => {
const options = { [namespace]: { filename: 'CHANGELOG-INVALID_ORDER.md' } };
const plugin = factory(Plugin, { namespace, options });
await assert.rejects(runTasks(plugin), /Invalid sections order in CHANGELOG-INVALID_ORDER.md: 0.2.0, 0.3.0./);
});

test('should find "1.0.0" section when no-increment flag is set when items under version', async t => {
Expand All @@ -85,12 +90,6 @@ test('should find "1.0.0" section when no-increment flag is set when items under
assert.equal(plugin.getChangelog('1.0.0'), '* Item C\n* Item D');
});

test('should throw for missing section for previous release', async t => {
const options = { [namespace]: { filename: 'CHANGELOG-MISSING.md' } };
const plugin = factory(Plugin, { namespace, options });
await assert.rejects(runTasks(plugin), /Missing section for previous release \("1\.0\.0"\) in CHANGELOG-MISSING\.md/);
});

test('should find very first changelog with disabled strict latest option', async t => {
const options = { [namespace]: { filename: 'CHANGELOG-UNRELEASED.md', strictLatest: false } };
const plugin = factory(Plugin, { namespace, options });
Expand All @@ -109,17 +108,6 @@ test('should write changelog', async t => {
);
});

test('should write changelog even with `strictLatest: false`', async t => {
const options = { [namespace]: { filename: 'CHANGELOG-NO-STRICT.md', strictLatest: false } };
const plugin = factory(Plugin, { namespace, options });
await runTasks(plugin);
assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
assert.match(
readFile('./CHANGELOG-NO-STRICT.md'),
/## \[1\.0\.1] - [0-9]{4}-[0-9]{2}-[0-9]{2}\n\n\* Item A\n\* Item B\n\n## \[1\.0\.0] - 2020-05-02\n\n\* Item C\n*\* Item D/
);
});

test('should not write changelog in dry run', async t => {
const options = { 'dry-run': true, [namespace]: { filename: 'CHANGELOG-DRYRUN.md' } };
const plugin = factory(Plugin, { namespace, options });
Expand Down

0 comments on commit a770ba8

Please sign in to comment.