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

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