release-it 19.0.0-next.1 → 19.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/test/tasks.js CHANGED
@@ -1,11 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import childProcess from 'node:child_process';
3
3
  import { appendFileSync, mkdirSync, renameSync } from 'node:fs';
4
- import test from 'ava';
4
+ import test, { after, afterEach, before, beforeEach, describe } from 'node:test';
5
+ import assert from 'node:assert/strict';
5
6
  import semver from 'semver';
6
- import sinon from 'sinon';
7
- import Log from '../lib/log.js';
8
- import Spinner from '../lib/spinner.js';
9
7
  import Config from '../lib/config.js';
10
8
  import runTasks from '../lib/index.js';
11
9
  import Git from '../lib/plugin/git/Git.js';
@@ -24,487 +22,471 @@ import {
24
22
  interceptCreate as interceptGitHubCreate,
25
23
  interceptAsset as interceptGitHubAsset
26
24
  } from './stub/github.js';
27
- import { factory } from './util/index.js';
25
+ import { factory, LogStub, SpinnerStub } from './util/index.js';
26
+ import { mockFetch } from './util/mock.js';
28
27
 
29
- const rootDir = new URL('..', import.meta.url);
28
+ describe('tasks', () => {
29
+ const rootDir = new URL('..', import.meta.url);
30
30
 
31
- const noop = Promise.resolve();
32
-
33
- const sandbox = sinon.createSandbox();
31
+ const [mocker, github, assets, gitlab] = mockFetch([
32
+ 'https://api.github.com',
33
+ 'https://uploads.github.com',
34
+ 'https://gitlab.com/api/v4'
35
+ ]);
34
36
 
35
- const npmMajorVersion = semver.major(process.env.npm_config_user_agent.match(/npm\/([^ ]+)/)[1]);
37
+ const npmMajorVersion = semver.major(process.env.npm_config_user_agent.match(/npm\/([^ ]+)/)[1]);
36
38
 
37
- const testConfig = {
38
- ci: true,
39
- config: false
40
- };
39
+ const testConfig = {
40
+ ci: true,
41
+ config: false
42
+ };
41
43
 
42
- const log = sandbox.createStubInstance(Log);
43
- const spinner = sandbox.createStubInstance(Spinner);
44
- spinner.show.callsFake(({ enabled = true, task }) => (enabled ? task() : noop));
44
+ const log = new LogStub();
45
+ const spinner = new SpinnerStub();
45
46
 
46
- const getContainer = options => {
47
- const config = new Config(Object.assign({}, testConfig, options));
48
- const shell = new ShellStub({ container: { log, config } });
49
- return {
50
- log,
51
- spinner,
52
- config,
53
- shell
47
+ const getContainer = options => {
48
+ const config = new Config(Object.assign({}, testConfig, options));
49
+ const shell = new ShellStub({ container: { log, config } });
50
+ return { log, spinner, config, shell };
54
51
  };
55
- };
56
52
 
57
- test.before(t => {
58
- t.timeout(90 * 1000);
59
- });
53
+ before(() => {
54
+ mocker.mockGlobal();
55
+ });
60
56
 
61
- test.serial.beforeEach(t => {
62
- const bare = mkTmpDir();
63
- const target = mkTmpDir();
64
- process.chdir(bare);
65
- childProcess.execSync(`git init --bare .`, execOpts);
66
- childProcess.execSync(`git clone ${bare} ${target}`, execOpts);
67
- process.chdir(target);
68
- gitAdd('line', 'file', 'Add file');
69
- t.context = { bare, target };
70
- });
57
+ let bare;
58
+ let target;
59
+ beforeEach(async () => {
60
+ bare = mkTmpDir();
61
+ target = mkTmpDir();
62
+ process.chdir(bare);
63
+ childProcess.execSync(`git init --bare .`, execOpts);
64
+ childProcess.execSync(`git clone ${bare} ${target}`, execOpts);
65
+ process.chdir(target);
66
+ gitAdd('line', 'file', 'Add file');
67
+ });
71
68
 
72
- test.serial.afterEach(() => {
73
- sandbox.resetHistory();
74
- });
69
+ afterEach(() => {
70
+ mocker.clearAll();
71
+ log.resetCalls();
72
+ });
75
73
 
76
- test.serial('should run tasks without throwing errors', async t => {
77
- renameSync('.git', 'foo');
78
- const { name, latestVersion, version } = await runTasks({}, getContainer());
79
- t.true(log.obtrusive.firstCall.args[0].includes(`release ${name} (${latestVersion}...${version})`));
80
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
81
- });
74
+ after(() => {
75
+ mocker.unmockGlobal();
76
+ });
82
77
 
83
- test.serial('should run tasks without package.json', async t => {
84
- childProcess.execSync('git tag 1.0.0', execOpts);
85
- gitAdd('line', 'file', 'Add file');
86
- const { name } = await runTasks({}, getContainer({ increment: 'major', git: { commit: false } }));
87
- t.true(log.obtrusive.firstCall.args[0].includes(`release ${name} (1.0.0...2.0.0)`));
88
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
89
- t.is(log.warn.callCount, 0);
90
- {
78
+ test('should run tasks without throwing errors', async () => {
79
+ renameSync('.git', 'foo');
80
+ const { name, latestVersion, version } = await runTasks({}, getContainer());
81
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (${latestVersion}...${version})`));
82
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
83
+ });
84
+
85
+ test('should run tasks without package.json', async () => {
86
+ childProcess.execSync('git tag 1.0.0', execOpts);
87
+ gitAdd('line', 'file', 'Add file');
88
+ const { name } = await runTasks({}, getContainer({ increment: 'major', git: { commit: false } }));
89
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (1.0.0...2.0.0)`));
90
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
91
+ assert.equal(log.warn.mock.callCount(), 0);
91
92
  const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', {
92
93
  encoding: 'utf-8'
93
94
  });
94
- t.is(stdout.trim(), '2.0.0');
95
- }
96
- });
97
-
98
- test.serial('should disable plugins', async t => {
99
- gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
100
- childProcess.execSync('git tag 1.2.3', execOpts);
101
- gitAdd('line', 'file', 'Add file');
102
- const container = getContainer({ increment: 'minor', git: false, npm: false });
103
- const { latestVersion, version } = await runTasks({}, container);
104
- t.is(latestVersion, '0.0.0');
105
- t.is(version, '0.1.0');
106
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
107
- });
108
-
109
- test.serial('should run tasks with minimal config and without any warnings/errors', async t => {
110
- gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
111
- childProcess.execSync('git tag 1.2.3', execOpts);
112
- gitAdd('line', 'file', 'More file');
113
- await runTasks({}, getContainer({ increment: 'patch' }));
114
- t.true(log.obtrusive.firstCall.args[0].includes('release my-package (1.2.3...1.2.4)'));
115
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
116
- const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
117
- t.is(stdout.trim(), '1.2.4');
118
- });
95
+ assert.equal(stdout.trim(), '2.0.0');
96
+ });
119
97
 
120
- test.serial('should use pkg.version', async t => {
121
- gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
122
- await runTasks({}, getContainer({ increment: 'minor' }));
123
- t.true(log.obtrusive.firstCall.args[0].includes('release my-package (1.2.3...1.3.0)'));
124
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
125
- const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
126
- t.is(stdout.trim(), '1.3.0');
127
- });
98
+ test('should disable plugins', async () => {
99
+ gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
100
+ childProcess.execSync('git tag 1.2.3', execOpts);
101
+ gitAdd('line', 'file', 'Add file');
102
+ const container = getContainer({ increment: 'minor', git: false, npm: false });
103
+ const { latestVersion, version } = await runTasks({}, container);
104
+ assert.equal(latestVersion, '0.0.0');
105
+ assert.equal(version, '0.1.0');
106
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
107
+ });
128
108
 
129
- test.serial('should use pkg.version (in sub dir) w/o tagging repo', async t => {
130
- gitAdd('{"name":"root-package","version":"1.0.0"}', 'package.json', 'Add package.json');
131
- childProcess.execSync('git tag 1.0.0', execOpts);
132
- mkdirSync('my-package');
133
- process.chdir('my-package');
134
- gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
135
- const container = getContainer({ increment: 'minor', git: { tag: false } });
136
- const exec = sinon.spy(container.shell, 'exec');
137
- await runTasks({}, container);
138
- t.true(log.obtrusive.firstCall.args[0].endsWith('release my-package (1.2.3...1.3.0)'));
139
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
140
- const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
141
- t.is(stdout.trim(), '1.0.0');
142
- const npmArgs = getArgs(exec.args, 'npm');
143
- t.is(npmArgs[5], 'npm version 1.3.0 --no-git-tag-version');
144
- exec.restore();
145
- });
109
+ test('should run tasks with minimal config and without any warnings/errors', async () => {
110
+ gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
111
+ childProcess.execSync('git tag 1.2.3', execOpts);
112
+ gitAdd('line', 'file', 'More file');
113
+ await runTasks({}, getContainer({ increment: 'patch' }));
114
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes('release my-package (1.2.3...1.2.4)'));
115
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
116
+ const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
117
+ assert.equal(stdout.trim(), '1.2.4');
118
+ });
146
119
 
147
- test.serial('should ignore version in pkg.version and use git tag instead', async t => {
148
- gitAdd('{"name":"my-package","version":"0.0.0"}', 'package.json', 'Add package.json');
149
- childProcess.execSync('git tag 1.1.1', execOpts);
150
- gitAdd('line', 'file', 'More file');
151
- await runTasks({}, getContainer({ increment: 'minor', npm: { ignoreVersion: true } }));
152
- t.true(log.obtrusive.firstCall.args[0].includes('release my-package (1.1.1...1.2.0)'));
153
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
154
- const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
155
- t.is(stdout.trim(), '1.2.0');
156
- });
120
+ test('should use pkg.version', async () => {
121
+ gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
122
+ await runTasks({}, getContainer({ increment: 'minor' }));
123
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes('release my-package (1.2.3...1.3.0)'));
124
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
125
+ const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
126
+ assert.equal(stdout.trim(), '1.3.0');
127
+ });
157
128
 
158
- test.serial('should release all the things (basic)', async t => {
159
- const { bare, target } = t.context;
160
- const project = path.basename(bare);
161
- const pkgName = path.basename(target);
162
- const owner = path.basename(path.dirname(bare));
163
- gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
164
- childProcess.execSync('git tag 1.0.0', execOpts);
165
- const sha = gitAdd('line', 'file', 'More file');
166
-
167
- interceptGitHubAuthentication();
168
- interceptGitHubCollaborator({ owner, project });
169
- interceptGitHubCreate({
170
- owner,
171
- project,
172
- body: { tag_name: '1.0.1', name: 'Release 1.0.1', body: `* More file (${sha})`, prerelease: false }
129
+ test('should use pkg.version (in sub dir) w/o tagging repo', async t => {
130
+ gitAdd('{"name":"root-package","version":"1.0.0"}', 'package.json', 'Add package.json');
131
+ childProcess.execSync('git tag 1.0.0', execOpts);
132
+ mkdirSync('my-package');
133
+ process.chdir('my-package');
134
+ gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
135
+ const container = getContainer({ increment: 'minor', git: { tag: false } });
136
+ const exec = t.mock.method(container.shell, 'exec');
137
+ await runTasks({}, container);
138
+ assert(log.obtrusive.mock.calls[0].arguments[0].endsWith('release my-package (1.2.3...1.3.0)'));
139
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
140
+ const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
141
+ assert.equal(stdout.trim(), '1.0.0');
142
+ const npmArgs = getArgs(exec, 'npm');
143
+ assert.equal(npmArgs[5], 'npm version 1.3.0 --no-git-tag-version');
173
144
  });
174
145
 
175
- const container = getContainer({
176
- github: { release: true, pushRepo: `https://github.com/${owner}/${project}` },
177
- npm: { name: pkgName }
146
+ test('should ignore version in pkg.version and use git tag instead', async () => {
147
+ gitAdd('{"name":"my-package","version":"0.0.0"}', 'package.json', 'Add package.json');
148
+ childProcess.execSync('git tag 1.1.1', execOpts);
149
+ gitAdd('line', 'file', 'More file');
150
+ await runTasks({}, getContainer({ increment: 'minor', npm: { ignoreVersion: true } }));
151
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes('release my-package (1.1.1...1.2.0)'));
152
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
153
+ const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
154
+ assert.equal(stdout.trim(), '1.2.0');
178
155
  });
179
- const exec = sinon.spy(container.shell, 'exec');
180
156
 
181
- await runTasks({}, container);
157
+ test('should release all the things (basic)', async t => {
158
+ const project = path.basename(bare);
159
+ const pkgName = path.basename(target);
160
+ const owner = path.basename(path.dirname(bare));
161
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
162
+ childProcess.execSync('git tag 1.0.0', execOpts);
163
+ const sha = gitAdd('line', 'file', 'More file');
164
+
165
+ interceptGitHubAuthentication(github);
166
+ interceptGitHubCollaborator(github, { owner, project });
167
+ interceptGitHubCreate(github, {
168
+ owner,
169
+ project,
170
+ body: { tag_name: '1.0.1', name: 'Release 1.0.1', body: `* More file (${sha})`, prerelease: false }
171
+ });
182
172
 
183
- const npmArgs = getArgs(container.shell.exec.args, 'npm');
173
+ const container = getContainer({
174
+ github: { release: true, pushRepo: `https://github.com/${owner}/${project}` },
175
+ npm: { name: pkgName }
176
+ });
184
177
 
185
- t.deepEqual(npmArgs, [
186
- 'npm ping',
187
- 'npm whoami',
188
- `npm show ${pkgName}@latest version`,
189
- 'npm --version',
190
- `npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
191
- 'npm version 1.0.1 --no-git-tag-version',
192
- 'npm publish . --tag latest'
193
- ]);
178
+ const exec = t.mock.method(container.shell, 'exec');
194
179
 
195
- t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
196
- t.true(log.log.firstCall.args[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
197
- t.true(log.log.secondCall.args[0].endsWith(`https://github.com/${owner}/${project}/releases/tag/1.0.1`));
180
+ await runTasks({}, container);
198
181
 
199
- exec.restore();
200
- });
182
+ const npmArgs = getArgs(exec, 'npm');
201
183
 
202
- test.serial('should release with correct tag name', async t => {
203
- const { bare, target } = t.context;
204
- const project = path.basename(bare);
205
- const pkgName = path.basename(target);
206
- const owner = path.basename(path.dirname(bare));
207
- const stdout = childProcess.execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' });
208
- const branchName = stdout.trim();
209
- gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
210
- childProcess.execSync(`git tag ${pkgName}-${branchName}-1.0.0`, execOpts);
211
- const sha = gitAdd('line', 'file', 'More file');
212
-
213
- interceptGitHubCreate({
214
- owner,
215
- project,
216
- body: {
217
- tag_name: `${pkgName}-${branchName}-1.0.1`,
218
- name: 'Release 1.0.1',
219
- body: `* More file (${sha})`,
220
- prerelease: false
221
- }
222
- });
184
+ assert.deepEqual(npmArgs, [
185
+ 'npm ping',
186
+ 'npm whoami',
187
+ `npm show ${pkgName}@latest version`,
188
+ 'npm --version',
189
+ `npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
190
+ 'npm version 1.0.1 --no-git-tag-version',
191
+ 'npm publish . --tag latest'
192
+ ]);
223
193
 
224
- const container = getContainer({
225
- git: { tagName: '${npm.name}-${branchName}-${version}' },
226
- github: { release: true, skipChecks: true, pushRepo: `https://github.com/${owner}/${project}` }
194
+ assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
195
+ assert(log.log.mock.calls[0].arguments[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
196
+ assert(log.log.mock.calls[1].arguments[0].endsWith(`https://github.com/${owner}/${project}/releases/tag/1.0.1`));
227
197
  });
228
198
 
229
- const exec = sinon.spy(container.shell, 'exec');
199
+ test('should release with correct tag name', async t => {
200
+ const project = path.basename(bare);
201
+ const pkgName = path.basename(target);
202
+ const owner = path.basename(path.dirname(bare));
203
+ const stdout = childProcess.execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' });
204
+ const branchName = stdout.trim();
205
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
206
+ childProcess.execSync(`git tag ${pkgName}-${branchName}-1.0.0`, execOpts);
207
+ const sha = gitAdd('line', 'file', 'More file');
208
+
209
+ interceptGitHubCreate(github, {
210
+ owner,
211
+ project,
212
+ body: {
213
+ tag_name: `${pkgName}-${branchName}-1.0.1`,
214
+ name: 'Release 1.0.1',
215
+ body: `* More file (${sha})`,
216
+ prerelease: false
217
+ }
218
+ });
230
219
 
231
- await runTasks({}, container);
220
+ const container = getContainer({
221
+ git: { tagName: '${npm.name}-${branchName}-${version}' },
222
+ github: { release: true, skipChecks: true, pushRepo: `https://github.com/${owner}/${project}` }
223
+ });
232
224
 
233
- const gitArgs = getArgs(container.shell.exec.args, 'git');
225
+ const exec = t.mock.method(container.shell, 'exec');
234
226
 
235
- t.true(gitArgs.includes(`git tag --annotate --message Release 1.0.1 ${pkgName}-${branchName}-1.0.1`));
236
- t.true(
237
- log.log.secondCall.args[0].endsWith(
238
- `https://github.com/${owner}/${project}/releases/tag/${pkgName}-${branchName}-1.0.1`
239
- )
240
- );
227
+ await runTasks({}, container);
241
228
 
242
- exec.restore();
243
- });
229
+ const gitArgs = getArgs(exec, 'git');
244
230
 
245
- test.serial('should release all the things (pre-release, github, gitlab)', async t => {
246
- const { bare, target } = t.context;
247
- const project = path.basename(bare);
248
- const pkgName = path.basename(target);
249
- const owner = path.basename(path.dirname(bare));
250
- const url = `https://gitlab.com/${owner}/${project}`;
251
- gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
252
- childProcess.execSync('git tag v1.0.0', execOpts);
253
- const sha = gitAdd('line', 'file', 'More file');
254
- childProcess.execSync('git push --follow-tags', execOpts);
255
- const git = factory(Git);
256
- const ref = (await git.getBranchName()) ?? 'HEAD';
257
-
258
- interceptGitHubAuthentication();
259
- interceptGitHubCollaborator({ owner, project });
260
- interceptGitHubCreate({
261
- owner,
262
- project,
263
- body: {
264
- tag_name: 'v1.1.0-alpha.0',
265
- name: 'Release 1.1.0-alpha.0',
266
- body: `Notes for ${pkgName} [v1.1.0-alpha.0]: ${sha}`,
267
- prerelease: true
268
- }
231
+ assert(gitArgs.includes(`git tag --annotate --message Release 1.0.1 ${pkgName}-${branchName}-1.0.1`));
232
+ assert(
233
+ log.log.mock.calls[1].arguments[0].endsWith(`/${owner}/${project}/releases/tag/${pkgName}-${branchName}-1.0.1`)
234
+ );
269
235
  });
270
- interceptGitHubAsset({ owner, project, body: 'lineline' });
271
-
272
- interceptGitLabUser({ owner });
273
- interceptGitLabCollaborator({ owner, project });
274
- interceptGitLabAsset({ owner, project });
275
- interceptGitLabPublish({
276
- owner,
277
- project,
278
- body: {
279
- name: 'Release 1.1.0-alpha.0',
280
- ref,
281
- tag_name: 'v1.1.0-alpha.0',
282
- tag_message: `${owner} ${owner}/${project} ${project}`,
283
- description: `Notes for ${pkgName}: ${sha}`,
284
- assets: {
285
- links: [
286
- {
287
- name: 'file',
288
- url: `${url}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file`
289
- }
290
- ]
236
+
237
+ test('should release all the things (pre-release, github, gitlab)', async t => {
238
+ const project = path.basename(bare);
239
+ const pkgName = path.basename(target);
240
+ const owner = path.basename(path.dirname(bare));
241
+ const url = `https://gitlab.com/${owner}/${project}`;
242
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
243
+ childProcess.execSync('git tag v1.0.0', execOpts);
244
+ const sha = gitAdd('line', 'file', 'More file');
245
+ childProcess.execSync('git push --follow-tags', execOpts);
246
+ const git = factory(Git);
247
+ const ref = (await git.getBranchName()) ?? 'HEAD';
248
+
249
+ interceptGitHubAuthentication(github);
250
+ interceptGitHubCollaborator(github, { owner, project });
251
+ interceptGitHubAsset(assets, { owner, project, body: 'lineline' });
252
+ interceptGitHubCreate(github, {
253
+ owner,
254
+ project,
255
+ body: {
256
+ tag_name: 'v1.1.0-alpha.0',
257
+ name: 'Release 1.1.0-alpha.0',
258
+ body: `Notes for ${pkgName} [v1.1.0-alpha.0]: ${sha}`,
259
+ prerelease: true
291
260
  }
292
- }
293
- });
261
+ });
294
262
 
295
- const container = getContainer({
296
- increment: 'minor',
297
- preRelease: 'alpha',
298
- git: {
299
- changelog: 'git log --pretty=format:%h ${latestTag}...HEAD',
300
- commitMessage: 'Release ${version} for ${name} (from ${latestVersion})',
301
- tagAnnotation: '${repo.owner} ${repo.repository} ${repo.project}'
302
- },
303
- github: {
304
- release: true,
305
- pushRepo: `https://github.com/${owner}/${project}`,
306
- releaseNotes: 'echo Notes for ${name} [v${version}]: ${changelog}',
307
- assets: ['file']
308
- },
309
- gitlab: {
310
- release: true,
311
- pushRepo: url,
312
- releaseNotes: 'echo Notes for ${name}: ${changelog}',
313
- assets: ['file']
314
- },
315
- npm: { name: pkgName }
316
- });
263
+ interceptGitLabUser(gitlab, { owner });
264
+ interceptGitLabCollaborator(gitlab, { owner, project });
265
+ interceptGitLabAsset(gitlab, { owner, project });
266
+ interceptGitLabPublish(gitlab, {
267
+ owner,
268
+ project,
269
+ body: {
270
+ name: 'Release 1.1.0-alpha.0',
271
+ ref,
272
+ tag_name: 'v1.1.0-alpha.0',
273
+ tag_message: `${owner} ${owner}/${project} ${project}`,
274
+ description: `Notes for ${pkgName}: ${sha}`,
275
+ assets: {
276
+ links: [
277
+ {
278
+ name: 'file',
279
+ url: `${url}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file`
280
+ }
281
+ ]
282
+ }
283
+ }
284
+ });
317
285
 
318
- const exec = sinon.spy(container.shell, 'exec');
286
+ const container = getContainer({
287
+ increment: 'minor',
288
+ preRelease: 'alpha',
289
+ git: {
290
+ changelog: 'git log --pretty=format:%h ${latestTag}...HEAD',
291
+ commitMessage: 'Release ${version} for ${name} (from ${latestVersion})',
292
+ tagAnnotation: '${repo.owner} ${repo.repository} ${repo.project}'
293
+ },
294
+ github: {
295
+ release: true,
296
+ pushRepo: `https://github.com/${owner}/${project}`,
297
+ releaseNotes: 'echo Notes for ${name} [v${version}]: ${changelog}',
298
+ assets: ['file']
299
+ },
300
+ gitlab: {
301
+ release: true,
302
+ pushRepo: url,
303
+ releaseNotes: 'echo Notes for ${name}: ${changelog}',
304
+ assets: ['file']
305
+ },
306
+ npm: { name: pkgName }
307
+ });
319
308
 
320
- await runTasks({}, container);
309
+ const exec = t.mock.method(container.shell, 'exec');
321
310
 
322
- const npmArgs = getArgs(container.shell.exec.args, 'npm');
323
- t.deepEqual(npmArgs, [
324
- 'npm ping',
325
- 'npm whoami',
326
- `npm show ${pkgName}@latest version`,
327
- 'npm --version',
328
- `npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
329
- 'npm version 1.1.0-alpha.0 --no-git-tag-version',
330
- 'npm publish . --tag alpha'
331
- ]);
311
+ process.env['GITLAB_TOKEN'] = '123';
332
312
 
333
- const commitMessage = childProcess.execSync('git log --oneline --format=%B -n 1 HEAD', {
334
- encoding: 'utf-8'
335
- });
336
- t.is(commitMessage.trim(), `Release 1.1.0-alpha.0 for ${pkgName} (from 1.0.0)`);
313
+ await runTasks({}, container);
337
314
 
338
- const tagName = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
339
- t.is(tagName.trim(), 'v1.1.0-alpha.0');
315
+ const npmArgs = getArgs(exec, 'npm');
340
316
 
341
- const tagAnnotation = childProcess.execSync('git for-each-ref refs/tags/v1.1.0-alpha.0 --format="%(contents)"', {
342
- encoding: 'utf-8'
343
- });
344
- t.is(tagAnnotation.trim(), `${owner} ${owner}/${project} ${project}`);
317
+ assert.deepEqual(npmArgs, [
318
+ 'npm ping',
319
+ 'npm whoami',
320
+ `npm show ${pkgName}@latest version`,
321
+ 'npm --version',
322
+ `npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
323
+ 'npm version 1.1.0-alpha.0 --no-git-tag-version',
324
+ 'npm publish . --tag alpha'
325
+ ]);
345
326
 
346
- t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...1.1.0-alpha.0)`));
347
- t.true(log.log.firstCall.args[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
348
- t.true(log.log.secondCall.args[0].endsWith(`https://github.com/${owner}/${project}/releases/tag/v1.1.0-alpha.0`));
349
- t.true(log.log.thirdCall.args[0].endsWith(`${project}/-/releases/v1.1.0-alpha.0`));
350
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
327
+ const commitMessage = childProcess.execSync('git log --oneline --format=%B -n 1 HEAD', {
328
+ encoding: 'utf-8'
329
+ });
330
+ assert.equal(commitMessage.trim(), `Release 1.1.0-alpha.0 for ${pkgName} (from 1.0.0)`);
351
331
 
352
- exec.restore();
353
- });
332
+ const tagName = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
333
+ assert.equal(tagName.trim(), 'v1.1.0-alpha.0');
354
334
 
355
- test.serial('should publish pre-release without pre-id with different npm.tag', async t => {
356
- const { target } = t.context;
357
- const pkgName = path.basename(target);
358
- gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
359
- childProcess.execSync('git tag v1.0.0', execOpts);
360
-
361
- const container = getContainer({ increment: 'major', preRelease: true, npm: { name: pkgName, tag: 'next' } });
362
- const exec = sinon.spy(container.shell, 'exec');
363
-
364
- await runTasks({}, container);
365
-
366
- const npmArgs = getArgs(container.shell.exec.args, 'npm');
367
- t.deepEqual(npmArgs, [
368
- 'npm ping',
369
- 'npm whoami',
370
- `npm show ${pkgName}@latest version`,
371
- 'npm --version',
372
- `npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
373
- 'npm version 2.0.0-0 --no-git-tag-version',
374
- 'npm publish . --tag next'
375
- ]);
335
+ const tagAnnotation = childProcess.execSync('git for-each-ref refs/tags/v1.1.0-alpha.0 --format="%(contents)"', {
336
+ encoding: 'utf-8'
337
+ });
338
+ assert.equal(tagAnnotation.trim(), `${owner} ${owner}/${project} ${project}`);
376
339
 
377
- const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
378
- t.is(stdout.trim(), 'v2.0.0-0');
379
- t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...2.0.0-0)`));
380
- t.true(log.log.firstCall.args[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
381
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
340
+ assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...1.1.0-alpha.0)`));
341
+ assert(log.log.mock.calls[0].arguments[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
342
+ assert(log.log.mock.calls[1].arguments[0].endsWith(`/${owner}/${project}/releases/tag/v1.1.0-alpha.0`));
343
+ assert(log.log.mock.calls[2].arguments[0].endsWith(`/${project}/-/releases/v1.1.0-alpha.0`));
344
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
345
+ });
382
346
 
383
- exec.restore();
384
- });
347
+ test('should publish pre-release without pre-id with different npm.tag', async t => {
348
+ const pkgName = path.basename(target);
349
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
350
+ childProcess.execSync('git tag v1.0.0', execOpts);
385
351
 
386
- test.serial('should handle private package correctly, bump lockfile', async t => {
387
- const { target } = t.context;
388
- const pkgName = path.basename(target);
389
- gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package.json', 'Add package.json');
390
- gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package-lock.json', 'Add package-lock.json');
352
+ const container = getContainer({ increment: 'major', preRelease: true, npm: { name: pkgName, tag: 'next' } });
353
+ const exec = t.mock.method(container.shell, 'exec');
391
354
 
392
- const container = getContainer({ npm: { name: pkgName, private: true } });
393
- const exec = sinon.spy(container.shell, 'exec');
355
+ await runTasks({}, container);
394
356
 
395
- await runTasks({}, container);
357
+ const npmArgs = getArgs(exec, 'npm');
358
+ assert.deepEqual(npmArgs, [
359
+ 'npm ping',
360
+ 'npm whoami',
361
+ `npm show ${pkgName}@latest version`,
362
+ 'npm --version',
363
+ `npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
364
+ 'npm version 2.0.0-0 --no-git-tag-version',
365
+ 'npm publish . --tag next'
366
+ ]);
396
367
 
397
- const npmArgs = getArgs(container.shell.exec.args, 'npm');
398
- t.deepEqual(npmArgs, ['npm version 1.0.1 --no-git-tag-version']);
399
- t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
400
- t.is(log.warn.length, 0);
401
- t.regex(log.log.firstCall.args[0], /Done \(in [0-9]+s\.\)/);
368
+ const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
369
+ assert.equal(stdout.trim(), 'v2.0.0-0');
370
+ assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...2.0.0-0)`));
371
+ assert(log.log.mock.calls[0].arguments[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
372
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
373
+ });
402
374
 
403
- exec.restore();
404
- });
375
+ test('should handle private package correctly, bump lockfile', async t => {
376
+ const pkgName = path.basename(target);
377
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package.json', 'Add package.json');
378
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package-lock.json', 'Add package-lock.json');
405
379
 
406
- test.serial('should initially publish non-private scoped npm package privately', async t => {
407
- const { target } = t.context;
408
- const pkgName = path.basename(target);
409
- gitAdd(`{"name":"@scope/${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
380
+ const container = getContainer({ npm: { name: pkgName, private: true } });
381
+ const exec = t.mock.method(container.shell, 'exec');
382
+
383
+ await runTasks({}, container);
410
384
 
411
- const container = getContainer({ npm: { name: pkgName } });
385
+ const npmArgs = getArgs(exec, 'npm');
386
+ assert.deepEqual(npmArgs, ['npm version 1.0.1 --no-git-tag-version']);
387
+ assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
388
+ assert.equal(log.warn.length, 0);
389
+ assert.match(log.log.mock.calls[0].arguments[0], /Done \(in [0-9]+s\.\)/);
390
+ });
412
391
 
413
- const exec = sinon.stub(container.shell, 'exec').callThrough();
414
- exec.withArgs(`npm show @scope/${pkgName}@latest version`).rejects();
392
+ test('should initially publish non-private scoped npm package privately', async t => {
393
+ const pkgName = path.basename(target);
394
+ gitAdd(`{"name":"@scope/${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
415
395
 
416
- await runTasks({}, container);
396
+ const container = getContainer({ npm: { name: pkgName } });
417
397
 
418
- const npmArgs = getArgs(container.shell.exec.args, 'npm');
419
- t.is(npmArgs[6], 'npm publish . --tag latest');
420
- exec.restore();
421
- });
398
+ const original = container.shell.exec.bind(container.shell);
399
+ const exec = t.mock.method(container.shell, 'exec', (...args) => {
400
+ if (args[0] === `npm show @scope/${pkgName}@latest version`) return Promise.reject();
401
+ return original(...args);
402
+ });
422
403
 
423
- test.serial('should use pkg.publishConfig.registry', async t => {
424
- const { target } = t.context;
425
- const pkgName = path.basename(target);
426
- const registry = 'https://my-registry.example.org';
404
+ await runTasks({}, container);
427
405
 
428
- gitAdd(
429
- JSON.stringify({
430
- name: pkgName,
431
- version: '1.2.3',
432
- publishConfig: { registry }
433
- }),
434
- 'package.json',
435
- 'Add package.json'
436
- );
406
+ const npmArgs = getArgs(exec, 'npm');
407
+ assert.equal(npmArgs[6], 'npm publish . --tag latest');
408
+ });
437
409
 
438
- const container = getContainer();
410
+ test('should use pkg.publishConfig.registry', async t => {
411
+ const pkgName = path.basename(target);
412
+ const registry = 'https://my-registry.example.org';
439
413
 
440
- const exec = sinon.spy(container.shell, 'exec');
414
+ gitAdd(
415
+ JSON.stringify({
416
+ name: pkgName,
417
+ version: '1.2.3',
418
+ publishConfig: { registry }
419
+ }),
420
+ 'package.json',
421
+ 'Add package.json'
422
+ );
441
423
 
442
- await runTasks({}, container);
424
+ const container = getContainer();
443
425
 
444
- const npmArgs = getArgs(exec.args, 'npm');
445
- t.is(npmArgs[0], `npm ping --registry ${registry}`);
446
- t.is(npmArgs[1], `npm whoami --registry ${registry}`);
447
- t.true(container.log.log.firstCall.args[0].endsWith(`${registry}/package/${pkgName}`));
426
+ const exec = t.mock.method(container.shell, 'exec');
448
427
 
449
- exec.restore();
450
- });
428
+ await runTasks({}, container);
451
429
 
452
- test.serial('should propagate errors', async t => {
453
- const config = {
454
- hooks: {
455
- 'before:init': 'some-failing-command'
456
- }
457
- };
458
- const container = getContainer(config);
430
+ const npmArgs = getArgs(exec, 'npm');
431
+ assert.equal(npmArgs[0], `npm ping --registry ${registry}`);
432
+ assert.equal(npmArgs[1], `npm whoami --registry ${registry}`);
433
+ assert(container.log.log.mock.calls[0].arguments[0].endsWith(`${registry}/package/${pkgName}`));
434
+ });
459
435
 
460
- await t.throwsAsync(runTasks({}, container), { message: /some-failing-command/ });
436
+ test('should propagate errors', async () => {
437
+ const config = {
438
+ hooks: {
439
+ 'before:init': 'some-failing-command'
440
+ }
441
+ };
442
+ const container = getContainer(config);
461
443
 
462
- t.is(log.error.callCount, 1);
463
- });
444
+ await assert.rejects(runTasks({}, container), { message: /some-failing-command/ });
464
445
 
465
- test.serial('should use custom changelog command with context', async t => {
466
- const { bare } = t.context;
467
- const project = path.basename(bare);
468
- const owner = path.basename(path.dirname(bare));
469
- childProcess.execSync('git tag v1.0.0', execOpts);
470
- gitAdd('line', 'file', 'More file');
471
-
472
- interceptGitHubAuthentication();
473
- interceptGitHubCollaborator({ owner, project });
474
- interceptGitHubCreate({
475
- owner,
476
- project,
477
- body: {
478
- tag_name: 'v1.1.0',
479
- name: 'Release 1.1.0',
480
- body: 'custom-changelog-generator --from=v1.0.0 --to=v1.1.0',
481
- draft: false,
482
- prerelease: false
483
- }
446
+ assert.equal(log.error.mock.callCount(), 1);
484
447
  });
485
448
 
486
- const container = getContainer({
487
- increment: 'minor',
488
- github: {
489
- release: true,
490
- releaseNotes: 'echo custom-changelog-generator --from=${latestTag} --to=${tagName}',
491
- pushRepo: `https://github.com/${owner}/${project}`
492
- }
493
- });
449
+ test('should use custom changelog command with context', async t => {
450
+ const project = path.basename(bare);
451
+ const owner = path.basename(path.dirname(bare));
452
+ childProcess.execSync('git tag v1.0.0', execOpts);
453
+ gitAdd('line', 'file', 'More file');
454
+
455
+ interceptGitHubAuthentication(github);
456
+ interceptGitHubCollaborator(github, { owner, project });
457
+ interceptGitHubCreate(github, {
458
+ owner,
459
+ project,
460
+ body: {
461
+ tag_name: 'v1.1.0',
462
+ name: 'Release 1.1.0',
463
+ body: 'custom-changelog-generator --from=v1.0.0 --to=v1.1.0',
464
+ draft: false,
465
+ prerelease: false
466
+ }
467
+ });
494
468
 
495
- const exec = sinon.spy(container.shell, 'execStringCommand');
469
+ const container = getContainer({
470
+ increment: 'minor',
471
+ github: {
472
+ release: true,
473
+ releaseNotes: 'echo custom-changelog-generator --from=${latestTag} --to=${tagName}',
474
+ pushRepo: `https://github.com/${owner}/${project}`
475
+ }
476
+ });
496
477
 
497
- await runTasks({}, container);
478
+ const exec = t.mock.method(container.shell, 'execStringCommand');
498
479
 
499
- const command = exec.args.find(([command]) => command.includes('custom-changelog-generator'));
480
+ await runTasks({}, container);
500
481
 
501
- t.is(command[0], 'echo custom-changelog-generator --from=v1.0.0 --to=v1.1.0');
482
+ const command = exec.mock.calls
483
+ .map(call => call.arguments)
484
+ .find(([command]) => command.includes('custom-changelog-generator'));
502
485
 
503
- exec.restore();
504
- });
486
+ assert.equal(command[0], 'echo custom-changelog-generator --from=v1.0.0 --to=v1.1.0');
487
+ });
505
488
 
506
- {
507
- test.serial('should run all hooks', async t => {
489
+ test('should run all hooks', async t => {
508
490
  gitAdd(`{"name":"hooked","version":"1.0.0","type":"module"}`, 'package.json', 'Add package.json');
509
491
  childProcess.execSync(`npm install ${rootDir}`, execOpts);
510
492
  const plugin = "import { Plugin } from 'release-it'; class MyPlugin extends Plugin {}; export default MyPlugin;";
@@ -524,13 +506,13 @@ test.serial('should use custom changelog command with context', async t => {
524
506
  git: { requireCleanWorkingDir: false },
525
507
  hooks
526
508
  });
527
- const exec = sinon.spy(container.shell, 'execFormattedCommand');
509
+ const exec = t.mock.method(container.shell, 'execFormattedCommand');
528
510
 
529
511
  await runTasks({}, container);
530
512
 
531
- const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
513
+ const commands = getArgs(exec, 'echo');
532
514
 
533
- t.deepEqual(commands, [
515
+ assert.deepEqual(commands, [
534
516
  'echo before:init',
535
517
  'echo before:my-plugin:init',
536
518
  'echo after:my-plugin:init',
@@ -592,7 +574,5 @@ test.serial('should use custom changelog command with context', async t => {
592
574
  'echo after:my-plugin:afterRelease',
593
575
  'echo after:afterRelease'
594
576
  ]);
595
-
596
- exec.restore();
597
577
  });
598
- }
578
+ });