1 | import { EOL } from 'os';
|
2 | import fs from 'fs';
|
3 | import { Plugin } from 'release-it';
|
4 | import conventionalRecommendedBump from 'conventional-recommended-bump';
|
5 | import conventionalChangelog from 'conventional-changelog';
|
6 | import semver from 'semver';
|
7 | import concat from 'concat-stream';
|
8 |
|
9 | class ConventionalChangelog extends Plugin {
|
10 | static disablePlugin(options) {
|
11 | return options.ignoreRecommendedBump ? null : 'version';
|
12 | }
|
13 |
|
14 | getInitialOptions(options, namespace) {
|
15 | const tagName = options.git ? options.git.tagName : null;
|
16 | options[namespace].tagPrefix = tagName ? tagName.replace(/v?\$\{version\}$/, '') : '';
|
17 | return options[namespace];
|
18 | }
|
19 |
|
20 | async getChangelog(latestVersion) {
|
21 | if (!latestVersion) latestVersion = '0.0.0';
|
22 | if (!this.config.isIncrement) {
|
23 | this.setContext({ version: latestVersion });
|
24 | } else {
|
25 | const { increment, isPreRelease, preReleaseId } = this.config.getContext('version');
|
26 | const version = await this.getRecommendedVersion({ increment, latestVersion, isPreRelease, preReleaseId });
|
27 | this.setContext({ version });
|
28 | }
|
29 | return this.generateChangelog();
|
30 | }
|
31 |
|
32 | getRecommendedVersion({ increment, latestVersion, isPreRelease, preReleaseId }) {
|
33 | const { version } = this.getContext();
|
34 | if (version) return version;
|
35 | const { options } = this;
|
36 | this.debug({ increment, latestVersion, isPreRelease, preReleaseId });
|
37 | this.debug('conventionalRecommendedBump', { options });
|
38 | return new Promise((resolve, reject) =>
|
39 | conventionalRecommendedBump(options, (err, result) => {
|
40 | this.debug({ err, result });
|
41 | if (err) return reject(err);
|
42 | let { releaseType } = result;
|
43 | if (increment) {
|
44 | this.log.warn(`The recommended bump is "${releaseType}", but is overridden with "${increment}".`);
|
45 | releaseType = increment;
|
46 | }
|
47 | if (increment && semver.valid(increment)) {
|
48 | resolve(increment);
|
49 | } else if (isPreRelease) {
|
50 | const type =
|
51 | releaseType && (options.strictSemVer || !semver.prerelease(latestVersion))
|
52 | ? `pre${releaseType}`
|
53 | : 'prerelease';
|
54 | resolve(semver.inc(latestVersion, type, preReleaseId));
|
55 | } else if (releaseType) {
|
56 | resolve(semver.inc(latestVersion, releaseType, preReleaseId));
|
57 | } else {
|
58 | resolve(null);
|
59 | }
|
60 | })
|
61 | );
|
62 | }
|
63 |
|
64 | getChangelogStream(opts = {}) {
|
65 | const { version } = this.getContext();
|
66 | const { isIncrement } = this.config;
|
67 | const { latestTag, secondLatestTag, tagTemplate } = this.config.getContext();
|
68 | const currentTag = isIncrement ? (tagTemplate ? tagTemplate.replace('${version}', version) : null) : latestTag;
|
69 | const previousTag = isIncrement ? latestTag : secondLatestTag;
|
70 | const releaseCount = opts.releaseCount === 0 ? 0 : isIncrement ? 1 : 2;
|
71 | const debug = this.config.isDebug ? this.debug : null;
|
72 | const options = Object.assign({}, { releaseCount }, this.options);
|
73 | const { context, gitRawCommitsOpts, parserOpts, writerOpts, ..._o } = options;
|
74 | const _c = Object.assign({ version, previousTag, currentTag }, context);
|
75 | const _r = Object.assign({ debug, from: previousTag }, gitRawCommitsOpts);
|
76 | this.debug('conventionalChangelog', { options: _o, context: _c, gitRawCommitsOpts: _r, parserOpts, writerOpts });
|
77 | return conventionalChangelog(_o, _c, _r, parserOpts, writerOpts);
|
78 | }
|
79 |
|
80 | generateChangelog(options) {
|
81 | return new Promise((resolve, reject) => {
|
82 | const resolver = result => resolve(result.toString().trim());
|
83 | const changelogStream = this.getChangelogStream(options);
|
84 | changelogStream.pipe(concat(resolver));
|
85 | changelogStream.on('error', reject);
|
86 | });
|
87 | }
|
88 |
|
89 | getPreviousChangelog() {
|
90 | const { infile } = this.options;
|
91 | return new Promise((resolve, reject) => {
|
92 | const readStream = fs.createReadStream(infile);
|
93 | const resolver = result => resolve(result.toString().trim());
|
94 | readStream.pipe(concat(resolver));
|
95 | readStream.on('error', reject);
|
96 | });
|
97 | }
|
98 |
|
99 | async writeChangelog() {
|
100 | const { infile, header: _header = '' } = this.options;
|
101 | let { changelog } = this.config.getContext();
|
102 | const header = _header.split(/\r\n|\r|\n/g).join(EOL);
|
103 |
|
104 | let hasInfile = false;
|
105 | try {
|
106 | fs.accessSync(infile);
|
107 | hasInfile = true;
|
108 | } catch (err) {
|
109 | this.debug(err);
|
110 | }
|
111 |
|
112 | let previousChangelog = '';
|
113 | try {
|
114 | previousChangelog = await this.getPreviousChangelog();
|
115 | previousChangelog = previousChangelog.replace(header, '');
|
116 | } catch (err) {
|
117 | this.debug(err);
|
118 | }
|
119 |
|
120 | if (!hasInfile) {
|
121 | changelog = await this.generateChangelog({ releaseCount: 0 });
|
122 | this.debug({ changelog });
|
123 | }
|
124 |
|
125 | fs.writeFileSync(
|
126 | infile,
|
127 | header +
|
128 | (changelog ? EOL + EOL + changelog.trim() : '') +
|
129 | (previousChangelog ? EOL + EOL + previousChangelog.trim() : '')
|
130 | );
|
131 |
|
132 | if (!hasInfile) {
|
133 | await this.exec(`git add ${infile}`);
|
134 | }
|
135 | }
|
136 |
|
137 | getIncrementedVersion(options) {
|
138 | const { ignoreRecommendedBump } = this.options;
|
139 | return ignoreRecommendedBump ? null : this.getRecommendedVersion(options);
|
140 | }
|
141 |
|
142 | getIncrementedVersionCI(options) {
|
143 | return this.getIncrementedVersion(options);
|
144 | }
|
145 |
|
146 | async bump(version) {
|
147 | const recommendedVersion = this.getContext('version');
|
148 |
|
149 | this.setContext({ version });
|
150 |
|
151 | if (this.options.ignoreRecommendedBump && recommendedVersion !== version) {
|
152 | const changelog = await this.generateChangelog();
|
153 | this.config.setContext({ changelog });
|
154 | }
|
155 | }
|
156 |
|
157 | async beforeRelease() {
|
158 | const { infile } = this.options;
|
159 | const { isDryRun } = this.config;
|
160 |
|
161 | this.log.exec(`Writing changelog to ${infile}`, isDryRun);
|
162 |
|
163 | if (infile && !isDryRun) {
|
164 | await this.writeChangelog();
|
165 | }
|
166 | }
|
167 | }
|
168 |
|
169 | export default ConventionalChangelog;
|