UNPKG

5.86 kBJavaScriptView Raw
1import { EOL } from 'os';
2import fs from 'fs';
3import { Plugin } from 'release-it';
4import conventionalRecommendedBump from 'conventional-recommended-bump';
5import conventionalChangelog from 'conventional-changelog';
6import semver from 'semver';
7import concat from 'concat-stream';
8
9class 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
169export default ConventionalChangelog;