UNPKG

6 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 async 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 try {
39 const result = await conventionalRecommendedBump(options, options?.parserOpts);
40 this.debug({ result });
41 let { releaseType } = result;
42 if (increment) {
43 this.log.warn(`The recommended bump is "${releaseType}", but is overridden with "${increment}".`);
44 releaseType = increment;
45 }
46 if (increment && semver.valid(increment)) {
47 return increment;
48 } else if (isPreRelease) {
49 const type =
50 releaseType && (options.strictSemVer || !semver.prerelease(latestVersion))
51 ? `pre${releaseType}`
52 : 'prerelease';
53 return semver.inc(latestVersion, type, preReleaseId);
54 } else if (releaseType) {
55 return semver.inc(latestVersion, releaseType, preReleaseId);
56 } else {
57 return null;
58 }
59 } catch (err) {
60 this.debug({ err });
61 throw err;
62 }
63 }
64
65 getChangelogStream(rawOptions = {}) {
66 const { version } = this.getContext();
67 const { isIncrement } = this.config;
68 const { latestTag, secondLatestTag, tagTemplate } = this.config.getContext();
69 const currentTag = isIncrement ? (tagTemplate ? tagTemplate.replace('${version}', version) : null) : latestTag;
70 const previousTag = isIncrement ? latestTag : secondLatestTag;
71 const releaseCount = rawOptions.releaseCount === 0 ? 0 : isIncrement ? 1 : 2;
72 const debug = this.config.isDebug ? this.debug : null;
73 const mergedOptions = Object.assign({}, { releaseCount }, this.options);
74
75 const { context, gitRawCommitsOpts, parserOpts, writerOpts, ...options } = mergedOptions;
76
77 const mergedContext = Object.assign({ version, previousTag, currentTag }, context);
78 const mergedGitRawCommitsOpts = Object.assign({ debug, from: previousTag }, gitRawCommitsOpts);
79
80 this.debug('conventionalChangelog', {
81 options,
82 context: mergedContext,
83 gitRawCommitsOpts: mergedGitRawCommitsOpts,
84 parserOpts,
85 writerOpts
86 });
87
88 return conventionalChangelog(options, mergedContext, mergedGitRawCommitsOpts, parserOpts, writerOpts);
89 }
90
91 generateChangelog(options) {
92 return new Promise((resolve, reject) => {
93 const resolver = result => resolve(result.toString().trim());
94 const changelogStream = this.getChangelogStream(options);
95 changelogStream.pipe(concat(resolver));
96 changelogStream.on('error', reject);
97 });
98 }
99
100 getPreviousChangelog() {
101 const { infile } = this.options;
102 return new Promise((resolve, reject) => {
103 const readStream = fs.createReadStream(infile);
104 const resolver = result => resolve(result.toString().trim());
105 readStream.pipe(concat(resolver));
106 readStream.on('error', reject);
107 });
108 }
109
110 async writeChangelog() {
111 const { infile, header: _header = '' } = this.options;
112 let { changelog } = this.config.getContext();
113 const header = _header.split(/\r\n|\r|\n/g).join(EOL);
114
115 let hasInfile = false;
116 try {
117 fs.accessSync(infile);
118 hasInfile = true;
119 } catch (err) {
120 this.debug(err);
121 }
122
123 let previousChangelog = '';
124 try {
125 previousChangelog = await this.getPreviousChangelog();
126 previousChangelog = previousChangelog.replace(header, '');
127 } catch (err) {
128 this.debug(err);
129 }
130
131 if (!hasInfile) {
132 changelog = await this.generateChangelog({ releaseCount: 0 });
133 this.debug({ changelog });
134 }
135
136 fs.writeFileSync(
137 infile,
138 header +
139 (changelog ? EOL + EOL + changelog.trim() : '') +
140 (previousChangelog ? EOL + EOL + previousChangelog.trim() : '') +
141 EOL
142 );
143
144 if (!hasInfile) {
145 await this.exec(`git add ${infile}`);
146 }
147 }
148
149 getIncrementedVersion(options) {
150 const { ignoreRecommendedBump } = this.options;
151 return ignoreRecommendedBump ? null : this.getRecommendedVersion(options);
152 }
153
154 getIncrementedVersionCI(options) {
155 return this.getIncrementedVersion(options);
156 }
157
158 async bump(version) {
159 const recommendedVersion = this.getContext('version');
160
161 this.setContext({ version });
162
163 if (this.options.ignoreRecommendedBump && recommendedVersion !== version) {
164 const changelog = await this.generateChangelog();
165 this.config.setContext({ changelog });
166 }
167 }
168
169 async beforeRelease() {
170 const { infile } = this.options;
171 const { isDryRun } = this.config;
172
173 this.log.exec(`Writing changelog to ${infile}`, isDryRun);
174
175 if (infile && !isDryRun) {
176 await this.writeChangelog();
177 }
178 }
179}
180
181export default ConventionalChangelog;