1 | import test from 'bron';
|
2 | import { strict as assert } from 'assert';
|
3 | import fs from 'fs';
|
4 | import path from 'path';
|
5 | import { EOL } from 'os';
|
6 | import sh from 'shelljs';
|
7 | import tmp from 'tmp';
|
8 | import runTasks from 'release-it';
|
9 |
|
10 | sh.config.silent = true;
|
11 |
|
12 | try {
|
13 | fs.unlinkSync('CHANGES.md');
|
14 | } catch (error) {}
|
15 |
|
16 | const noop = () => {};
|
17 | const log = {
|
18 | log: noop,
|
19 | error: noop,
|
20 | verbose: noop,
|
21 | info: noop,
|
22 | obtrusive: noop,
|
23 | exec: noop,
|
24 | warn: noop,
|
25 | preview: noop
|
26 | };
|
27 |
|
28 | const namespace = 'conventional-changelog';
|
29 | const { pathname } = new URL('./index.js', import.meta.url);
|
30 | const preset = 'angular';
|
31 |
|
32 | const getOptions = options => [
|
33 | {
|
34 | ci: true,
|
35 | git: { commit: false, tag: false, push: false, requireUpstream: false },
|
36 | plugins: { [pathname]: [namespace, options] }
|
37 | },
|
38 | { log }
|
39 | ];
|
40 |
|
41 | const mkTmpDir = () => {
|
42 | const dir = tmp.dirSync({ prefix: namespace });
|
43 | return dir.name;
|
44 | };
|
45 |
|
46 | const add = (type, file) => {
|
47 | sh.ShellString(file).toEnd(file);
|
48 | sh.exec(`git add ${file}`);
|
49 | sh.exec(`git commit -m "${type}(${file}): ${type} ${file}"`);
|
50 | };
|
51 |
|
52 | const setup = () => {
|
53 | const dir = mkTmpDir();
|
54 | sh.pushd(dir);
|
55 | sh.exec(`git init .`);
|
56 | add('fix', 'foo');
|
57 | return { dir };
|
58 | };
|
59 |
|
60 | const date = /\([0-9]{4}-[0-9]{2}-[0-9]{2}\)/.source;
|
61 | const sha = /[0-9a-f]{7}/.source;
|
62 | const header = (from, to, version) => `# \\[${version || to}\\]\\(/compare/${from}...${to}\\) ${date}`;
|
63 | const features = EOL + EOL + EOL + '### Features' + EOL;
|
64 | const fixes = EOL + EOL + EOL + '### Bug Fixes' + EOL;
|
65 | const commit = (type, name) => EOL + `\\* \\*\\*${name}:\\*\\* ${type} ${name} ${sha}`;
|
66 |
|
67 | const nl = value => value.split(/\r\n|\r|\n/g).join(EOL);
|
68 |
|
69 | test('should generate changelog using recommended bump (minor)', async () => {
|
70 | setup();
|
71 |
|
72 | sh.exec(`git tag 1.0.0`);
|
73 | add('fix', 'bar');
|
74 | add('feat', 'baz');
|
75 |
|
76 | const options = getOptions({ preset });
|
77 | const { changelog } = await runTasks(...options);
|
78 | const title = header('1.0.0', '1.1.0');
|
79 | const bar = commit('fix', 'bar');
|
80 | const baz = commit('feat', 'baz');
|
81 | assert.match(nl(changelog), new RegExp(title + fixes + bar + features + baz));
|
82 | });
|
83 |
|
84 | test('should generate changelog using recommended bump (patch)', async () => {
|
85 | setup();
|
86 |
|
87 | sh.exec(`git tag 1.0.0`);
|
88 | add('fix', 'bar');
|
89 | add('fix', 'baz');
|
90 |
|
91 | const options = getOptions({ preset });
|
92 | const { changelog } = await runTasks(...options);
|
93 | const title = header('1.0.0', '1.0.1');
|
94 | const bar = commit('fix', 'bar');
|
95 | const baz = commit('fix', 'baz');
|
96 | assert.match(nl(changelog), new RegExp(title + fixes + bar + baz));
|
97 | });
|
98 |
|
99 | test('should support tag prefix', async () => {
|
100 | setup();
|
101 |
|
102 | sh.exec(`git tag next-2.0.0`);
|
103 | add('fix', 'bar');
|
104 |
|
105 | const [config, container] = getOptions({ preset });
|
106 | config.git.tagName = 'next-${version}';
|
107 | const { changelog } = await runTasks(config, container);
|
108 | assert.match(
|
109 | nl(changelog),
|
110 | /# \[2\.0\.1\]\(\/compare\/next-2\.0\.0\.\.\.next-2\.0\.1\) \([0-9]{4}-[0-9]{2}-[0-9]{2}\)\s*### Bug Fixes\s*\* \*\*bar:\*\* fix bar [0-9a-f]{7}/
|
111 | );
|
112 | const title = header('next-2.0.0', 'next-2.0.1', '2.0.1');
|
113 | const bar = commit('fix', 'bar');
|
114 | assert.match(nl(changelog), new RegExp(title + fixes + bar));
|
115 | });
|
116 |
|
117 | test('should respect --no-increment and return previous, identical changelog', async () => {
|
118 | setup();
|
119 |
|
120 | sh.exec(`git tag 1.0.0`);
|
121 | add('feat', 'bar');
|
122 | add('fix', 'baz');
|
123 | sh.exec(`git tag 1.1.0`);
|
124 | add('fix', 'bar');
|
125 | add('feat', 'baz');
|
126 | sh.exec(`git tag 1.2.0`);
|
127 |
|
128 | const [config, container] = getOptions({ preset });
|
129 | config.increment = false;
|
130 | const { changelog } = await runTasks(config, container);
|
131 | const title = header('1.1.0', '1.2.0');
|
132 | const bar = commit('fix', 'bar');
|
133 | const baz = commit('feat', 'baz');
|
134 | assert.match(nl(changelog), new RegExp(title + fixes + bar + features + baz));
|
135 | });
|
136 |
|
137 | test('should ignore recommended bump (option)', async () => {
|
138 | setup();
|
139 | sh.exec(`git tag 1.0.0`);
|
140 | add('feat', 'baz');
|
141 |
|
142 | const options = getOptions({ preset, ignoreRecommendedBump: true });
|
143 | const { version } = await runTasks(...options);
|
144 | assert.equal(version, '1.0.1');
|
145 | });
|
146 |
|
147 | test('should use provided pre-release id', async t => {
|
148 | setup();
|
149 | sh.exec(`git tag 1.0.0`);
|
150 | add('feat', 'baz');
|
151 |
|
152 | const [config, container] = getOptions({ preset });
|
153 | config.preRelease = 'alpha';
|
154 | const { version } = await runTasks(config, container);
|
155 | assert.equal(version, '1.1.0-alpha.0');
|
156 | });
|
157 |
|
158 | test('should use provided pre-release id (pre-release continuation)', async t => {
|
159 | setup();
|
160 | sh.exec(`git tag 1.0.1-alpha.0`);
|
161 | add('feat', 'baz');
|
162 |
|
163 | const [config, container] = getOptions({ preset });
|
164 | config.preRelease = 'alpha';
|
165 | const { version } = await runTasks(config, container);
|
166 | assert.equal(version, '1.0.1-alpha.1');
|
167 | });
|
168 |
|
169 | test('should use provided pre-release id (next pre-release)', async t => {
|
170 | setup();
|
171 | sh.exec(`git tag 1.1.0-alpha.1`);
|
172 |
|
173 | const [config, container] = getOptions({ preset });
|
174 | config.preRelease = 'beta';
|
175 | const { version } = await runTasks(config, container);
|
176 | assert.equal(version, '1.1.0-beta.0');
|
177 | });
|
178 |
|
179 | test('should use recommended bump (after pre-rerelease)', async t => {
|
180 | setup();
|
181 | sh.exec(`git tag 1.0.1-beta.0`);
|
182 | add('feat', 'baz');
|
183 |
|
184 | const options = getOptions({ preset });
|
185 | const { version } = await runTasks(...options);
|
186 | assert.equal(version, '1.1.0');
|
187 | });
|
188 |
|
189 | test('should follow true semver (pre-release continuation)', async t => {
|
190 | setup();
|
191 | sh.exec(`git tag 1.1.0-alpha.0`);
|
192 | add('feat', 'baz');
|
193 |
|
194 | const [config, container] = getOptions({ preset, strictSemVer: true });
|
195 | config.preRelease = 'alpha';
|
196 | const { version } = await runTasks(config, container);
|
197 | assert.equal(version, '1.2.0-alpha.0');
|
198 | });
|
199 |
|
200 | test('should use provided increment', async () => {
|
201 | setup();
|
202 | sh.exec(`git tag 1.0.0`);
|
203 |
|
204 | const [config, container] = getOptions({ preset });
|
205 | config.increment = 'major';
|
206 | const { version } = await runTasks(config, container);
|
207 | assert.equal(version, '2.0.0');
|
208 | });
|
209 |
|
210 | test('should use provided version (ignore recommended bump)', async () => {
|
211 | setup();
|
212 |
|
213 | const [config, container] = getOptions({ preset });
|
214 | config.increment = '1.2.3';
|
215 | const { version } = await runTasks(config, container);
|
216 | assert.equal(version, '1.2.3');
|
217 | });
|
218 |
|
219 | test('should not throw with Git plugin disabled', async () => {
|
220 | setup();
|
221 |
|
222 | const [config, container] = getOptions({ preset });
|
223 | config.git = false;
|
224 | const { version, changelog } = await runTasks(config, container);
|
225 | assert.equal(version, '0.0.1');
|
226 | const title = `## 0.0.1 ${date}`;
|
227 | const fix = commit('fix', 'foo');
|
228 | assert.match(nl(changelog), new RegExp(title + fixes + fix));
|
229 | });
|
230 |
|
231 | test(`should write and update infile`, async () => {
|
232 | const { dir } = setup();
|
233 | sh.exec(`git tag 1.0.0`);
|
234 | add('fix', 'foo');
|
235 | add('feat', 'bar');
|
236 |
|
237 | const h = 'The header' + EOL + EOL + 'The subheader';
|
238 | const infile = path.join(dir, 'CHANGES.md');
|
239 | const [config, container] = getOptions({ preset, infile, header: h });
|
240 | config.git.tag = true;
|
241 | await runTasks(config, container);
|
242 | const changelog = fs.readFileSync(infile).toString();
|
243 | const title = header('1.0.0', '1.1.0');
|
244 | const fix1 = commit('fix', 'foo');
|
245 | const feat1 = commit('feat', 'bar');
|
246 | assert.match(nl(changelog), new RegExp(h + EOL + EOL + title + fixes + fix1 + features + feat1));
|
247 | {
|
248 | add('fix', 'bar');
|
249 | add('fix', 'baz');
|
250 |
|
251 | const options = getOptions({ preset, infile, header: h });
|
252 | await runTasks(...options);
|
253 | const changelog = fs.readFileSync(infile).toString();
|
254 |
|
255 | const title2 = '#' + header('1.1.0', '1.1.1');
|
256 | const fix2 = commit('fix', 'bar');
|
257 | const fix3 = commit('fix', 'baz');
|
258 | assert.match(
|
259 | nl(changelog),
|
260 | new RegExp(h + EOL + EOL + title2 + fixes + fix2 + fix3 + EOL + EOL + title + fixes + fix1 + features + feat1)
|
261 | );
|
262 | }
|
263 | });
|
264 |
|
265 | test('should reject if conventional bump passes error', async () => {
|
266 | setup();
|
267 | const options = getOptions({ preset: 'what?' });
|
268 | await assert.rejects(
|
269 | runTasks(...options),
|
270 | 'Error: Unable to load the "what?" preset package. Please make sure it\'s installed.'
|
271 | );
|
272 | });
|
273 |
|
274 | test('should reject if conventional changelog has error', async () => {
|
275 | setup();
|
276 | const options = getOptions({ preset: () => {} });
|
277 | await assert.rejects(runTasks(...options), /preset must be string or object with key name/);
|
278 | });
|
279 |
|
280 | test('should not write infile in dry run', async () => {
|
281 | const { dir } = setup();
|
282 | const infile = path.join(dir, 'DRYRUN.md');
|
283 | const [config, container] = getOptions({ preset, infile });
|
284 | config['dry-run'] = true;
|
285 | await runTasks(config, container);
|
286 | assert.throws(() => fs.readFileSync(infile), /no such file/);
|
287 | });
|
288 |
|
289 | test('should not bump when recommended bump returns null', async () => {
|
290 | setup();
|
291 | sh.exec(`git tag 1.0.0`);
|
292 | add('fix', 'bar');
|
293 | add('feat', 'baz');
|
294 | {
|
295 | const options = getOptions({ preset: 'angular' });
|
296 | const { version } = await runTasks(...options);
|
297 | assert.equal(version, '1.1.0');
|
298 | }
|
299 | add('blorp', 'faz');
|
300 | add('faz', 'blorp');
|
301 | {
|
302 | const options = getOptions({ preset: 'angular' });
|
303 | const { version } = await runTasks(...options);
|
304 | assert.equal(version, '1.1.0');
|
305 | }
|
306 | {
|
307 | const whatBump = commits => ({ level: null, reason: 'Parsed commits do not warrant a version bump.' });
|
308 | const options = getOptions({ whatBump });
|
309 | const { version } = await runTasks(...options);
|
310 | assert.equal(version, undefined);
|
311 | }
|
312 | });
|
313 |
|
314 |
|
315 | test.skip('should pass parserOpts and writerOpts', async t => {
|
316 | setup();
|
317 | const parserOpts = {
|
318 | mergePattern: /^Merge pull request #(\d+) from (.*)$/,
|
319 | mergeCorrespondence: ['id', 'source']
|
320 | };
|
321 | const writerOpts = {
|
322 | groupBy: 'type'
|
323 | };
|
324 | const [config, container] = getOptions({ preset, parserOpts, writerOpts });
|
325 | await runTasks(config, container);
|
326 | });
|