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 | 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 |
|
181 | export default ConventionalChangelog;
|