@release-it/keep-a-changelog 2.0.0 → 2.2.2

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.
Files changed (4) hide show
  1. package/README.md +6 -4
  2. package/index.js +42 -7
  3. package/package.json +9 -5
  4. package/test.js +97 -22
package/README.md CHANGED
@@ -21,8 +21,10 @@ In [release-it](https://github.com/release-it/release-it) config:
21
21
 
22
22
  ## Options
23
23
 
24
- | option | default value | description |
25
- | -------------- | -------------- | ------------------------------------------------------------ |
26
- | filename | `CHANGELOG.md` | File with changelogs. |
24
+ | option | default value | description |
25
+ | -------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
26
+ | filename | `CHANGELOG.md` | File with changelogs. |
27
27
  | 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. |
28
- | keepUnreleased | `false` | It leaves "Unreleased" title row unchanged if set to `true`. |
28
+ | addUnreleased | `false` | It leaves "Unreleased" title row if set to `true`. |
29
+ | keepUnreleased | `false` | It leaves "Unreleased" title row unchanged if set to `true`. |
30
+ | addVersionUrl | `false` | Links the version to the according changeset. |
package/index.js CHANGED
@@ -11,18 +11,21 @@ const getFormattedDate = () => {
11
11
  };
12
12
 
13
13
  class KeepAChangelog extends Plugin {
14
- init() {
15
- const { filename, strictLatest, keepUnreleased } = this.options;
14
+ async init() {
15
+ await super.init();
16
+ const { filename, strictLatest, addUnreleased, keepUnreleased, addVersionUrl } = this.options;
16
17
 
17
18
  this.filename = filename || 'CHANGELOG.md';
18
19
  this.strictLatest = strictLatest === undefined ? true : Boolean(strictLatest);
20
+ this.addUnreleased = addUnreleased === undefined ? false : Boolean(addUnreleased);
19
21
  this.keepUnreleased = keepUnreleased === undefined ? false : Boolean(keepUnreleased);
22
+ this.addVersionUrl = addVersionUrl === undefined ? false : Boolean(addVersionUrl);
20
23
 
21
24
  this.changelogPath = path.resolve(this.filename);
22
25
  this.changelogContent = fs.readFileSync(this.changelogPath, 'utf-8');
23
26
  this.EOL = detectNewline(this.changelogContent);
24
27
  this.unreleasedTitleRaw = 'Unreleased';
25
- this.unreleasedTitle = `${this.EOL}${this.EOL}## [${this.unreleasedTitleRaw}]${this.EOL}`;
28
+ this.unreleasedTitle = `## [${this.unreleasedTitleRaw}]`;
26
29
 
27
30
  const hasUnreleasedSection = this.changelogContent.includes(this.unreleasedTitle);
28
31
  if (!hasUnreleasedSection) {
@@ -62,15 +65,47 @@ class KeepAChangelog extends Plugin {
62
65
  this.setContext({ version });
63
66
  }
64
67
 
68
+ addVersionUrls(changelog) {
69
+ const { version, latestVersion, tagName, latestTag, repo } = this.config.getContext();
70
+ let updatedChangelog = changelog;
71
+
72
+ const repositoryUrl = `https://${repo.host}/${repo.repository}`;
73
+
74
+ // Add or update the Unreleased link
75
+ const unreleasedUrl = `${repositoryUrl}/compare/${tagName}...HEAD`;
76
+ const unreleasedLink = `[Unreleased]: ${unreleasedUrl}`;
77
+ if (updatedChangelog.includes('[Unreleased]:')) {
78
+ updatedChangelog = updatedChangelog.replace(/\[Unreleased\]\:.*HEAD/, unreleasedLink);
79
+ } else {
80
+ updatedChangelog = `${updatedChangelog}${this.EOL}${this.EOL}${unreleasedLink}`;
81
+ }
82
+
83
+ // Add a link for the new version
84
+ const latestVersionLink = `[${latestVersion}]:`;
85
+ const releaseUrl = `${repositoryUrl}/compare/${latestTag}...${tagName}`;
86
+ const releaseLink = `[${version}]: ${releaseUrl}`;
87
+ if (updatedChangelog.includes(latestVersionLink)) {
88
+ return updatedChangelog.replace(latestVersionLink, `${releaseLink}${this.EOL}${latestVersionLink}`);
89
+ } else {
90
+ return `${updatedChangelog}${this.EOL}${releaseLink}`;
91
+ }
92
+ }
93
+
65
94
  beforeRelease() {
66
- const { keepUnreleased } = this;
95
+ const { addUnreleased, keepUnreleased, addVersionUrl } = this;
67
96
  const { isDryRun } = this.config;
68
97
  if (isDryRun || keepUnreleased) return;
69
98
  const { version } = this.getContext();
70
99
  const formattedDate = getFormattedDate();
71
- const releaseTitle = `${this.EOL}${this.EOL}## [${version}] - ${formattedDate}${this.EOL}`;
72
- const changelog = this.changelogContent.replace(this.unreleasedTitle, releaseTitle);
73
- fs.writeFileSync(this.changelogPath, changelog);
100
+ const unreleasedTitle = addUnreleased ? this.unreleasedTitle + this.EOL + this.EOL : '';
101
+ const releaseTitle = `${unreleasedTitle}## [${version}] - ${formattedDate}`;
102
+ let changelog = this.changelogContent.replace(this.unreleasedTitle, releaseTitle);
103
+
104
+ if (addVersionUrl) {
105
+ changelog = this.addVersionUrls(changelog);
106
+ }
107
+
108
+ fs.writeFileSync(this.changelogPath, changelog.trim() + this.EOL);
74
109
  }
75
110
  }
76
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@release-it/keep-a-changelog",
3
- "version": "2.0.0",
3
+ "version": "2.2.2",
4
4
  "description": "Keep-a-changelog plugin for release-it",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -10,7 +10,11 @@
10
10
  "keywords": [
11
11
  "release",
12
12
  "release-it",
13
- "release-it-plugin"
13
+ "release-it-plugin",
14
+ "keep-a-changelog",
15
+ "changelog",
16
+ "changes",
17
+ "unreleased"
14
18
  ],
15
19
  "license": "MIT",
16
20
  "repository": {
@@ -30,11 +34,11 @@
30
34
  "devDependencies": {
31
35
  "bron": "^1.1.1",
32
36
  "mock-fs": "^4.13.0",
33
- "release-it": "^14.0.0",
34
- "sinon": "^9.0.3"
37
+ "release-it": "^14.2.0",
38
+ "sinon": "^9.2.1"
35
39
  },
36
40
  "peerDependencies": {
37
- "release-it": "^14.0.0"
41
+ "release-it": "^14.2.0"
38
42
  },
39
43
  "engines": {
40
44
  "node": ">=10"
package/test.js CHANGED
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const test = require('bron');
3
3
  const assert = require('assert').strict;
4
4
  const mock = require('mock-fs');
5
+ const sinon = require('sinon');
5
6
  const { factory, runTasks } = require('release-it/test/util');
6
7
  const Plugin = require('.');
7
8
 
@@ -9,18 +10,25 @@ const initialDryRunFileContents =
9
10
  '\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';
10
11
 
11
12
  mock({
12
- './CHANGELOG-FOO.md': '\n\n## [FOO]\n\n* Item A\n* Item B',
13
- './CHANGELOG-UNRELEASED.md': '\n\n## [Unreleased]\n\n* Item A\n* Item B',
14
- './CHANGELOG-EMPTY.md': '\n\n## [Unreleased]\n\n\n\n## [1.0.0]\n\n* Item A\n* Item B',
15
- './CHANGELOG-FULL.md': '\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',
13
+ './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-EMPTY.md': '## [Unreleased]\n\n\n\n## [1.0.0]\n\n* Item A\n* Item B',
16
+ './CHANGELOG-FULL.md':
17
+ '# 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',
16
19
  './CHANGELOG-DRYRUN.md': initialDryRunFileContents,
17
- './CHANGELOG-LESS_NEW_LINES.md':
18
- '\n\n## [Unreleased]\n* Item A\n* Item B\n## [1.0.0] - 2020-05-02\n* Item C\n* Item D',
20
+ './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',
19
21
  './CHANGELOG-EOL.md':
20
- '\r\n\r\n## [Unreleased]\r\n\r\n* Item A\r\n* Item B\r\n\r\n## [1.0.0] - 2020-05-02\r\n\r\n* Item C\r\n* Item D'
22
+ '\r\n\r\n## [Unreleased]\r\n\r\n* Item A\r\n* Item B\r\n\r\n## [1.0.0] - 2020-05-02\r\n\r\n* Item C\r\n* Item D',
23
+ './CHANGELOG-UNRELEASED.md': '## [Unreleased]\n\n* Item A\n* Item B\n\n## [1.0.0] - 2020-05-02\n\n* Item C\n* Item D',
24
+ './CHANGELOG-VERSION_URL.md':
25
+ '## [Unreleased]\n\n* Item A\n* Item B\n\n## [1.0.0] - 2020-05-02\n\n* Item C\n* Item D\n\n[Unreleased]: https://github.com/release-it/release-it/compare/1.0.0..HEAD\n[1.0.0]: https://github.com/release-it/release-it/compare/0.0.0...1.0.0',
26
+ './CHANGELOG-VERSION_URL_UNRELEASED.md':
27
+ '## [Unreleased]\n\n* Item A\n* Item B\n\n## [1.0.0] - 2020-05-02\n\n* Item C\n* Item D\n\n[Unreleased]: https://github.com/user/project/compare/1.0.0..HEAD\n[1.0.0]: https://github.com/user/project/compare/0.0.0...1.0.0',
28
+ './CHANGELOG-VERSION_URL_NEW.md': '## [Unreleased]\n\n* Item A\n* Item B'
21
29
  });
22
30
 
23
- const readFile = file => fs.readFileSync(file).toString().trim();
31
+ const readFile = file => fs.readFileSync(file).toString();
24
32
 
25
33
  const namespace = 'keep-a-changelog';
26
34
 
@@ -43,12 +51,9 @@ test('should throw for empty "unreleased" section', async t => {
43
51
  });
44
52
 
45
53
  test('should throw for missing section for previous release', async t => {
46
- const options = { [namespace]: { filename: 'CHANGELOG-UNRELEASED.md' } };
54
+ const options = { [namespace]: { filename: 'CHANGELOG-MISSING.md' } };
47
55
  const plugin = factory(Plugin, { namespace, options });
48
- await assert.rejects(
49
- runTasks(plugin),
50
- /Missing section for previous release \("1\.0\.0"\) in CHANGELOG-UNRELEASED\.md/
51
- );
56
+ await assert.rejects(runTasks(plugin), /Missing section for previous release \("1\.0\.0"\) in CHANGELOG-MISSING\.md/);
52
57
  });
53
58
 
54
59
  test('should find very first changelog with disabled strict latest option', async t => {
@@ -65,18 +70,18 @@ test('should write changelog', async t => {
65
70
  assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
66
71
  assert.match(
67
72
  readFile('./CHANGELOG-FULL.md'),
68
- /## \[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/
73
+ /^# Changelog\n\n## \[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\n$/
69
74
  );
70
75
  });
71
76
 
72
- test('should write changelog even with disabled strict latest option', async t => {
73
- const options = { [namespace]: { filename: 'CHANGELOG-FULL.md', strictLatest: false } };
77
+ test('should write changelog even with `strictLatest: false`', async t => {
78
+ const options = { [namespace]: { filename: 'CHANGELOG-NO-STRICT.md', strictLatest: false } };
74
79
  const plugin = factory(Plugin, { namespace, options });
75
80
  await runTasks(plugin);
76
81
  assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
77
82
  assert.match(
78
- readFile('./CHANGELOG-FULL.md'),
79
- /## \[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/
83
+ readFile('./CHANGELOG-NO-STRICT.md'),
84
+ /## \[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/
80
85
  );
81
86
  });
82
87
 
@@ -84,14 +89,14 @@ test('should not write changelog in dry run', async t => {
84
89
  const options = { 'dry-run': true, [namespace]: { filename: 'CHANGELOG-DRYRUN.md' } };
85
90
  const plugin = factory(Plugin, { namespace, options });
86
91
  await runTasks(plugin);
87
- assert.equal(fs.readFileSync('./CHANGELOG-DRYRUN.md').toString(), initialDryRunFileContents);
92
+ assert.equal(readFile('./CHANGELOG-DRYRUN.md'), initialDryRunFileContents);
88
93
  });
89
94
 
90
95
  test('should not write changelog with keep unreleased option', async t => {
91
96
  const options = { [namespace]: { filename: 'CHANGELOG-DRYRUN.md', keepUnreleased: true } };
92
97
  const plugin = factory(Plugin, { namespace, options });
93
98
  await runTasks(plugin);
94
- assert.equal(fs.readFileSync('./CHANGELOG-DRYRUN.md').toString(), initialDryRunFileContents);
99
+ assert.equal(readFile('./CHANGELOG-DRYRUN.md'), initialDryRunFileContents);
95
100
  });
96
101
 
97
102
  test('should find changelog even if less new lines is used', async t => {
@@ -101,13 +106,83 @@ test('should find changelog even if less new lines is used', async t => {
101
106
  assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
102
107
  });
103
108
 
104
- test('should write changelog (with different EOL)', async t => {
109
+ test('should write changelog with different EOL', async t => {
105
110
  const options = { [namespace]: { filename: 'CHANGELOG-EOL.md' } };
106
111
  const plugin = factory(Plugin, { namespace, options });
107
112
  await runTasks(plugin);
108
113
  assert.equal(plugin.getChangelog(), '* Item A\r\n* Item B');
109
114
  assert.match(
110
115
  readFile('./CHANGELOG-EOL.md'),
111
- /## \[1\.0\.1\] - [0-9]{4}-[0-9]{2}-[0-9]{2}\r\n\r\n\* Item A\r\n\* Item B\r\n\r\n## \[1\.0\.0\] - 2020-05-02\r\n\r\n\* Item C\r\n*\* Item D/
116
+ /^## \[1\.0\.1] - [0-9]{4}-[0-9]{2}-[0-9]{2}\r\n\r\n\* Item A\r\n\* Item B\r\n\r\n## \[1\.0\.0] - 2020-05-02\r\n\r\n\* Item C\r\n*\* Item D\r\n/
117
+ );
118
+ });
119
+
120
+ test('should write changelog and add unreleased section', async t => {
121
+ const options = { [namespace]: { filename: 'CHANGELOG-UNRELEASED.md', addUnreleased: true } };
122
+ const plugin = factory(Plugin, { namespace, options });
123
+ await runTasks(plugin);
124
+ assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
125
+ assert.match(
126
+ readFile('./CHANGELOG-UNRELEASED.md'),
127
+ /^## \[Unreleased]\n\n## \[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/
128
+ );
129
+ });
130
+
131
+ test('should add links to the end of the file', async t => {
132
+ const options = { [namespace]: { filename: 'CHANGELOG-VERSION_URL.md', addVersionUrl: true } };
133
+ const plugin = factory(Plugin, { namespace, options });
134
+ plugin.config.setContext({
135
+ latestTag: '1.0.0',
136
+ repo: {
137
+ host: 'github.com',
138
+ repository: 'release-it/release-it'
139
+ }
140
+ });
141
+ await runTasks(plugin);
142
+ assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
143
+ assert.match(
144
+ readFile('./CHANGELOG-VERSION_URL.md'),
145
+ /^## \[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\n\n\[Unreleased]: https:\/\/github\.com\/release-it\/release-it\/compare\/1\.0\.1\.\.\.HEAD\n\[1\.0\.1]: https:\/\/github\.com\/release-it\/release-it\/compare\/1\.0\.0\.\.\.1\.0\.1\n\[1\.0\.0]: https:\/\/github\.com\/release-it\/release-it\/compare\/0\.0\.0\.\.\.1\.0\.0\n$/
146
+ );
147
+ });
148
+
149
+ test('should add unreleased section and links to the end of the file', async t => {
150
+ const options = {
151
+ [namespace]: { filename: 'CHANGELOG-VERSION_URL_UNRELEASED.md', addVersionUrl: true, addUnreleased: true }
152
+ };
153
+ const plugin = factory(Plugin, { namespace, options });
154
+ plugin.config.setContext({
155
+ latestTag: '1.0.0',
156
+ repo: {
157
+ host: 'github.com',
158
+ repository: 'user/project'
159
+ }
160
+ });
161
+ await runTasks(plugin);
162
+ assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
163
+ assert.match(
164
+ readFile('./CHANGELOG-VERSION_URL_UNRELEASED.md'),
165
+ /^## \[Unreleased]\n\n## \[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\n\n\[Unreleased]: https:\/\/github\.com\/user\/project\/compare\/1\.0\.1\.\.\.HEAD\n\[1\.0\.1]: https:\/\/github\.com\/user\/project\/compare\/1\.0\.0\.\.\.1\.0\.1\n\[1\.0\.0]: https:\/\/github\.com\/user\/project\/compare\/0\.0\.0\.\.\.1\.0\.0\n/
166
+ );
167
+ });
168
+
169
+ test('should add link to the end of a new changelog', async t => {
170
+ const options = {
171
+ [namespace]: { filename: 'CHANGELOG-VERSION_URL_NEW.md', addVersionUrl: true, strictLatest: false }
172
+ };
173
+ const plugin = factory(Plugin, { namespace, options });
174
+ sinon.stub(plugin, 'getLatestVersion').returns('0.0.0');
175
+ plugin.config.setContext({
176
+ increment: 'major',
177
+ repo: {
178
+ host: 'github.com',
179
+ repository: 'user/project'
180
+ }
181
+ });
182
+ await runTasks(plugin);
183
+ assert.equal(plugin.getChangelog(), '* Item A\n* Item B');
184
+ assert.match(
185
+ readFile('./CHANGELOG-VERSION_URL_NEW.md'),
186
+ /^## \[1\.0\.0] - [0-9]{4}-[0-9]{2}-[0-9]{2}\n\n\* Item A\n\* Item B\n\n\[Unreleased]: https:\/\/github\.com\/user\/project\/compare\/1\.0\.0\.\.\.HEAD\n\[1.0.0]: https:\/\/github\.com\/user\/project\/compare\/0\.0\.0\.\.\.1\.0\.0\n$/
112
187
  );
113
188
  });