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