@release-it/keep-a-changelog 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: Publish through pkr.pr.new
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - '**'
7
+ tags:
8
+ - '!**'
9
+ pull_request:
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 20
21
+ - run: npx --yes pkg-pr-new publish --compact --comment=update
package/README.md CHANGED
@@ -31,7 +31,6 @@ In [release-it](https://github.com/release-it/release-it) config:
31
31
  | option | default value | description |
32
32
  | ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
33
33
  | filename | `'CHANGELOG.md'` | File with changelogs. |
34
- | 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. |
35
34
  | addUnreleased | `false` | It leaves "Unreleased" title row if set to `true`. |
36
35
  | keepUnreleased | `false` | It leaves "Unreleased" title row unchanged if set to `true`. |
37
36
  | addVersionUrl | `false` | Links the version to the according changeset. Uses GitHub-compatible URLs by default, see other options to configure the URL format. |
package/index.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { detectNewline } from 'detect-newline';
5
5
  import format from 'string-template';
6
+ import semver from "semver";
6
7
 
7
8
  const pad = num => ('0' + num).slice(-2);
8
9
 
@@ -28,10 +29,9 @@ const defaultVersionUrlFormats = {
28
29
  class KeepAChangelog extends Plugin {
29
30
  async init() {
30
31
  await super.init();
31
- const { filename, strictLatest, addUnreleased, keepUnreleased, addVersionUrl, versionUrlFormats, head } = this.options;
32
+ const { filename, addUnreleased, keepUnreleased, addVersionUrl, versionUrlFormats, head } = this.options;
32
33
 
33
34
  this.filename = filename || 'CHANGELOG.md';
34
- this.strictLatest = strictLatest === undefined ? true : Boolean(strictLatest);
35
35
  this.addUnreleased = addUnreleased === undefined ? false : Boolean(addUnreleased);
36
36
  this.keepUnreleased = keepUnreleased === undefined ? false : Boolean(keepUnreleased);
37
37
  this.addVersionUrl = addVersionUrl === undefined ? false : Boolean(addVersionUrl);
@@ -56,15 +56,41 @@ class KeepAChangelog extends Plugin {
56
56
  const { changelog } = this.getContext();
57
57
  if (changelog) return changelog;
58
58
 
59
- const { filename, strictLatest } = this;
60
- const previousReleaseTitle = strictLatest ? `## [${latestVersion}]` : `## [`;
61
- const hasPreviousReleaseSection = this.changelogContent.includes(previousReleaseTitle);
62
- if (strictLatest && !hasPreviousReleaseSection) {
63
- throw Error(`Missing section for previous release ("${latestVersion}") in ${filename}.`);
59
+ const unreleasedTitleRawList = Array.from(this.changelogContent.matchAll(/(?<=## \[)Unreleased(?=])/g)).map(ver => ver?.[0])
60
+ if(unreleasedTitleRawList.length > 1) {
61
+ throw new Error(`Too many "Unreleased" sections in ${this.filename}: ${unreleasedTitleRawList.length}.`)
62
+ }
63
+ const versionTitleRawList = Array.from(this.changelogContent.matchAll(/(?<=## \[)((\d+\.){2}\d+[^\]]*)/g)).map(ver => ver?.[0])
64
+ const badTitleRawList = versionTitleRawList.filter(ver => {
65
+ // invalid: ver > latestVersion
66
+ return !semver.valid(ver) || semver.gt(ver, latestVersion);
67
+ })
68
+ if(badTitleRawList.length > 0) {
69
+ throw new Error(`Invalid versions in ${this.filename}: ${badTitleRawList.join(', ')}. Current: ${latestVersion}.`)
70
+ }
71
+ const unorderedTitleRawList = versionTitleRawList.filter((ver, veri) => {
72
+ const previous = versionTitleRawList[veri - 1];
73
+ const next = versionTitleRawList[veri + 1];
74
+
75
+ if(previous) {
76
+ // bad order: ver > previous
77
+ return semver.gt(ver, previous)
78
+ }
79
+
80
+ if(next) {
81
+ // bad order: ver < next
82
+ return semver.lt(ver, next)
83
+ }
84
+
85
+ return false
86
+ })
87
+ if(unorderedTitleRawList.length > 0) {
88
+ throw new Error(`Invalid sections order in ${this.filename}: ${unorderedTitleRawList.join(', ')}.`)
64
89
  }
65
90
 
66
91
  const { isIncrement } = this.config;
67
- const titleToFind = isIncrement ? this.unreleasedTitleRaw : latestVersion;
92
+ // use unreleased if initial release
93
+ const titleToFind = isIncrement || versionTitleRawList.length === 0 ? this.unreleasedTitleRaw : latestVersion;
68
94
  const changelogContent = this.getChangelogEntryContent(titleToFind);
69
95
 
70
96
  this.setContext({ changelog: changelogContent });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@release-it/keep-a-changelog",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Keep-a-changelog plugin for release-it",
5
5
  "type": "module",
6
6
  "exports": "./index.js",
@@ -31,16 +31,18 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "detect-newline": "^4.0.1",
34
+ "semver": "^7.6.3",
34
35
  "string-template": "^1.0.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "bron": "^2.0.3",
38
- "mock-fs": "^5.1.4",
39
- "release-it": "^17.0.0",
40
- "sinon": "^17.0.1"
39
+ "fs-monkey": "^1.0.6",
40
+ "memfs": "^4.15.3",
41
+ "release-it": "^18.0.0",
42
+ "sinon": "^19.0.2"
41
43
  },
42
44
  "peerDependencies": {
43
- "release-it": "^17.0.0"
45
+ "release-it": "^18.0.0"
44
46
  },
45
47
  "engines": {
46
48
  "node": ">=18"
package/test.js CHANGED
@@ -1,7 +1,8 @@
1
- import { strict as assert } from 'assert';
1
+ import { strict as assert } from 'node:assert';
2
2
  import test from 'bron';
3
- import fs from 'fs';
4
- import mock from 'mock-fs';
3
+ import fs from 'node:fs';
4
+ import { vol } from 'memfs';
5
+ import { patchFs } from 'fs-monkey';
5
6
  import { factory, runTasks } from 'release-it/test/util/index.js';
6
7
  import sinon from 'sinon';
7
8
  import Plugin from './index.js';
@@ -9,13 +10,13 @@ import Plugin from './index.js';
9
10
  const initialDryRunFileContents =
10
11
  '\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';
11
12
 
12
- mock({
13
+ vol.fromJSON({
13
14
  './CHANGELOG-FOO.md': '## [FOO]\n\n* Item A\n* Item B',
14
- './CHANGELOG-MISSING.md': '## [Unreleased]\n\n* Item A\n* Item B',
15
+ './CHANGELOG-MULTIPLE_UNRELEASED.md': '## [Unreleased]\n* Item A\n\n## [Unreleased]* Item B\nB\n* Item C\n* Item D',
16
+ './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',
15
17
  './CHANGELOG-EMPTY.md': '## [Unreleased]\n\n\n\n## [1.0.0]\n\n* Item A\n* Item B',
16
18
  './CHANGELOG-FULL.md':
17
19
  '# 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',
18
- './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',
19
20
  './CHANGELOG-DRYRUN.md': initialDryRunFileContents,
20
21
  './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',
21
22
  './CHANGELOG-EOL.md':
@@ -34,6 +35,8 @@ mock({
34
35
  './CHANGELOG-VERSION_URL_NEW.md': '## [Unreleased]\n\n* Item A\n* Item B\n'
35
36
  });
36
37
 
38
+ patchFs(vol);
39
+
37
40
  const readFile = file => fs.readFileSync(file).toString();
38
41
 
39
42
  const namespace = 'keep-a-changelog';
@@ -62,10 +65,15 @@ test('should throw for missing "unreleased" section when no-increment flag is se
62
65
  await assert.rejects(runTasks(plugin), /Missing "Unreleased" section in CHANGELOG-FOO.md/);
63
66
  });
64
67
 
65
- test('should throw for missing "1.0.0" section when no-increment flag is set', async t => {
66
- const options = { increment: false, [namespace]: { filename: 'CHANGELOG-MISSING.md' } };
68
+ test('should throw for multiple "unreleased" sections', async t => {
69
+ const options = { [namespace]: { filename: 'CHANGELOG-MULTIPLE_UNRELEASED.md' } };
70
+ const plugin = factory(Plugin, { namespace, options });
71
+ await assert.rejects(runTasks(plugin), /Too many "Unreleased" sections in CHANGELOG-MULTIPLE_UNRELEASED.md: \d+./);
72
+ });
73
+ test('should throw for invalid order', async t => {
74
+ const options = { [namespace]: { filename: 'CHANGELOG-INVALID_ORDER.md' } };
67
75
  const plugin = factory(Plugin, { namespace, options });
68
- await assert.rejects(runTasks(plugin), /Missing section for previous release \("1\.0\.0"\) in CHANGELOG-MISSING\.md/);
76
+ await assert.rejects(runTasks(plugin), /Invalid sections order in CHANGELOG-INVALID_ORDER.md: 0.2.0, 0.3.0./);
69
77
  });
70
78
 
71
79
  test('should find "1.0.0" section when no-increment flag is set when items under version', async t => {
@@ -82,12 +90,6 @@ test('should find "1.0.0" section when no-increment flag is set when items under
82
90
  assert.equal(plugin.getChangelog('1.0.0'), '* Item C\n* Item D');
83
91
  });
84
92
 
85
- test('should throw for missing section for previous release', async t => {
86
- const options = { [namespace]: { filename: 'CHANGELOG-MISSING.md' } };
87
- const plugin = factory(Plugin, { namespace, options });
88
- await assert.rejects(runTasks(plugin), /Missing section for previous release \("1\.0\.0"\) in CHANGELOG-MISSING\.md/);
89
- });
90
-
91
93
  test('should find very first changelog with disabled strict latest option', async t => {
92
94
  const options = { [namespace]: { filename: 'CHANGELOG-UNRELEASED.md', strictLatest: false } };
93
95
  const plugin = factory(Plugin, { namespace, options });
@@ -106,17 +108,6 @@ test('should write changelog', async t => {
106
108
  );
107
109
  });
108
110
 
109
- test('should write changelog even with `strictLatest: false`', async t => {
110
- const options = { [namespace]: { filename: 'CHANGELOG-NO-STRICT.md', strictLatest: false } };
111
- const plugin = factory(Plugin, { namespace, options });
112
- await runTasks(plugin);
113
- assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
114
- assert.match(
115
- readFile('./CHANGELOG-NO-STRICT.md'),
116
- /## \[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/
117
- );
118
- });
119
-
120
111
  test('should not write changelog in dry run', async t => {
121
112
  const options = { 'dry-run': true, [namespace]: { filename: 'CHANGELOG-DRYRUN.md' } };
122
113
  const plugin = factory(Plugin, { namespace, options });