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.
@@ -1,216 +1,269 @@
1
1
  import path from 'node:path';
2
2
  import { renameSync } from 'node:fs';
3
3
  import childProcess from 'node:child_process';
4
- import test from 'ava';
5
- import sinon from 'sinon';
6
- import Log from '../lib/log.js';
7
- import Spinner from '../lib/spinner.js';
4
+ import test, { afterEach, after, before, beforeEach, describe, mock } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { MockServer, FetchMocker } from 'mentoss';
8
7
  import Prompt from '../lib/prompt.js';
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';
12
11
  import { execOpts } from '../lib/util.js';
13
- import { mkTmpDir, gitAdd } from './util/helpers.js';
12
+ import { mkTmpDir, gitAdd, getArgs } from './util/helpers.js';
14
13
  import ShellStub from './stub/shell.js';
15
14
  import { interceptPublish as interceptGitLabPublish } from './stub/gitlab.js';
16
15
  import { interceptCreate as interceptGitHubCreate } from './stub/github.js';
17
- import { factory } from './util/index.js';
16
+ import { factory, LogStub, SpinnerStub } from './util/index.js';
18
17
 
19
- const noop = Promise.resolve();
18
+ describe('tasks.interactive', () => {
19
+ const github = new MockServer('https://api.github.com');
20
+ const gitlab = new MockServer('https://gitlab.com/api/v4');
21
+ const githubusercontent = new MockServer('https://raw.githubusercontent.com');
20
22
 
21
- const sandbox = sinon.createSandbox();
23
+ const mocker = new FetchMocker({
24
+ servers: [github, gitlab, githubusercontent]
25
+ });
22
26
 
23
- const testConfig = {
24
- ci: false,
25
- config: false
26
- };
27
+ before(() => {
28
+ mocker.mockGlobal();
29
+ });
27
30
 
28
- const log = sandbox.createStubInstance(Log);
29
- const spinner = sandbox.createStubInstance(Spinner);
30
- spinner.show.callsFake(({ enabled = true, task }) => (enabled ? task() : noop));
31
+ afterEach(() => {
32
+ mocker.clearAll();
33
+ prompt.mock.resetCalls();
34
+ log.resetCalls();
35
+ });
31
36
 
32
- const defaultInquirer = {
33
- prompt: sandbox.stub().callsFake(([options]) => {
34
- const answer = options.type === 'list' ? options.choices[0].value : options.name === 'version' ? '0.0.1' : true;
35
- return { [options.name]: answer };
36
- })
37
- };
38
-
39
- const getContainer = (options, inquirer = defaultInquirer) => {
40
- const config = new Config(Object.assign({}, testConfig, options));
41
- const shell = new ShellStub({ container: { log, config } });
42
- const prompt = new Prompt({ container: { inquirer } });
43
- return {
44
- log,
45
- spinner,
46
- config,
47
- shell,
48
- prompt
37
+ after(() => {
38
+ mocker.unmockGlobal();
39
+ });
40
+
41
+ const testConfig = {
42
+ ci: false,
43
+ config: false
49
44
  };
50
- };
51
-
52
- const getHooks = plugins => {
53
- const hooks = {};
54
- ['before', 'after'].forEach(prefix => {
55
- plugins.forEach(ns => {
56
- ['init', 'beforeBump', 'bump', 'beforeRelease', 'release', 'afterRelease'].forEach(lifecycle => {
57
- hooks[`${prefix}:${lifecycle}`] = `echo ${prefix}:${lifecycle}`;
58
- hooks[`${prefix}:${ns}:${lifecycle}`] = `echo ${prefix}:${ns}:${lifecycle}`;
45
+
46
+ const getHooks = plugins => {
47
+ const hooks = {};
48
+ ['before', 'after'].forEach(prefix => {
49
+ plugins.forEach(ns => {
50
+ ['init', 'beforeBump', 'bump', 'beforeRelease', 'release', 'afterRelease'].forEach(lifecycle => {
51
+ hooks[`${prefix}:${lifecycle}`] = `echo ${prefix}:${lifecycle}`;
52
+ hooks[`${prefix}:${ns}:${lifecycle}`] = `echo ${prefix}:${ns}:${lifecycle}`;
53
+ });
59
54
  });
60
55
  });
61
- });
62
- return hooks;
63
- };
56
+ return hooks;
57
+ };
64
58
 
65
- test.before(t => {
66
- t.timeout(90 * 1000);
67
- });
59
+ const log = new LogStub();
60
+ const spinner = new SpinnerStub();
68
61
 
69
- test.serial.beforeEach(t => {
70
- const bare = mkTmpDir();
71
- const target = mkTmpDir();
72
- process.chdir(bare);
73
- childProcess.execSync(`git init --bare .`, execOpts);
74
- childProcess.execSync(`git clone ${bare} ${target}`, execOpts);
75
- process.chdir(target);
76
- gitAdd('line', 'file', 'Add file');
77
- t.context = { bare, target };
78
- });
62
+ const prompt = mock.fn(([options]) => {
63
+ const answer = options.type === 'list' ? options.choices[0].value : options.name === 'version' ? '0.0.1' : true;
64
+ return { [options.name]: answer };
65
+ });
79
66
 
80
- test.serial.afterEach(() => {
81
- sandbox.resetHistory();
82
- });
67
+ const defaultInquirer = { prompt };
83
68
 
84
- test.serial('should run tasks without throwing errors', async t => {
85
- renameSync('.git', 'foo');
86
- const { name, latestVersion, version } = await runTasks({}, getContainer());
87
- t.is(version, '0.0.1');
88
- t.true(log.obtrusive.firstCall.args[0].includes(`release ${name} (currently at ${latestVersion})`));
89
- t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
90
- });
69
+ const getContainer = (options, inquirer = defaultInquirer) => {
70
+ const config = new Config(Object.assign({}, testConfig, options));
71
+ const shell = new ShellStub({ container: { log, config } });
72
+ const prompt = new Prompt({ container: { inquirer } });
73
+ return { log, spinner, config, shell, prompt };
74
+ };
91
75
 
92
- test.serial('should not run hooks for disabled release-cycle methods', async t => {
93
- const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
76
+ let bare;
77
+ let target;
78
+ beforeEach(async () => {
79
+ bare = mkTmpDir();
80
+ target = mkTmpDir();
81
+ process.chdir(bare);
82
+ childProcess.execSync(`git init --bare .`, execOpts);
83
+ childProcess.execSync(`git clone ${bare} ${target}`, execOpts);
84
+ process.chdir(target);
85
+ gitAdd('line', 'file', 'Add file');
86
+ });
94
87
 
95
- const container = getContainer({
96
- hooks,
97
- git: { push: false },
98
- github: { release: false },
99
- gitlab: { release: false },
100
- npm: { publish: false }
88
+ test('should run tasks without throwing errors', async () => {
89
+ renameSync('.git', 'foo');
90
+ const { name, latestVersion, version } = await runTasks({}, getContainer());
91
+ assert.equal(version, '0.0.1');
92
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (currently at ${latestVersion})`));
93
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
101
94
  });
102
95
 
103
- const exec = sandbox.spy(container.shell, 'execFormattedCommand');
96
+ test('should run tasks using extended configuration', async t => {
97
+ renameSync('.git', 'foo');
104
98
 
105
- await runTasks({}, container);
99
+ const validationExtendedConfiguration = "echo 'extended_configuration'";
106
100
 
107
- const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
101
+ githubusercontent.get('/release-it/release-it-configuration/HEAD/.release-it.json', {
102
+ status: 200,
103
+ body: {
104
+ hooks: {
105
+ 'before:init': validationExtendedConfiguration
106
+ }
107
+ }
108
+ });
108
109
 
109
- t.true(commands.includes('echo before:init'));
110
- t.true(commands.includes('echo after:afterRelease'));
110
+ const config = {
111
+ $schema: 'https://unpkg.com/release-it@18/schema/release-it.json',
112
+ extends: 'github:release-it/release-it-configuration'
113
+ };
111
114
 
112
- t.false(commands.includes('echo after:git:release'));
113
- t.false(commands.includes('echo after:github:release'));
114
- t.false(commands.includes('echo after:gitlab:release'));
115
- t.false(commands.includes('echo after:npm:release'));
116
- });
115
+ const container = getContainer(config);
117
116
 
118
- test.serial('should not run hooks for cancelled release-cycle methods', async t => {
119
- const { target } = t.context;
120
- const pkgName = path.basename(target);
121
- gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
122
- childProcess.execSync('git tag 1.0.0', execOpts);
117
+ const exec = t.mock.method(container.shell, 'execFormattedCommand');
123
118
 
124
- const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
125
- const inquirer = { prompt: sandbox.stub().callsFake(([options]) => ({ [options.name]: false })) };
119
+ const { name, latestVersion, version } = await runTasks({}, container);
126
120
 
127
- const container = getContainer(
128
- {
129
- increment: 'minor',
130
- hooks,
131
- github: { release: true, skipChecks: true },
132
- gitlab: { release: true, skipChecks: true },
133
- npm: { publish: true, skipChecks: true }
134
- },
135
- inquirer
136
- );
121
+ const commands = getArgs(exec, 'echo');
137
122
 
138
- const exec = sandbox.stub(container.shell, 'execFormattedCommand').callThrough();
123
+ assert(commands.includes(validationExtendedConfiguration));
139
124
 
140
- await runTasks({}, container);
125
+ assert.equal(version, '0.0.1');
126
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (currently at ${latestVersion})`));
127
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
128
+ });
141
129
 
142
- const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
130
+ test('should run tasks not using extended configuration as it is not a string', async () => {
131
+ renameSync('.git', 'foo');
143
132
 
144
- t.true(commands.includes('echo before:init'));
145
- t.true(commands.includes('echo after:afterRelease'));
146
- t.true(commands.includes('echo after:git:bump'));
147
- t.true(commands.includes('echo after:npm:bump'));
133
+ const config = {
134
+ $schema: 'https://unpkg.com/release-it@18/schema/release-it.json',
135
+ extends: false
136
+ };
148
137
 
149
- t.false(commands.includes('echo after:git:release'));
150
- t.false(commands.includes('echo after:github:release'));
151
- t.false(commands.includes('echo after:gitlab:release'));
152
- t.false(commands.includes('echo after:npm:release'));
138
+ const container = getContainer(config);
153
139
 
154
- exec.restore();
155
- });
140
+ const { name, latestVersion, version } = await runTasks({}, container);
156
141
 
157
- test.serial('should run "after:*:release" plugin hooks', async t => {
158
- const { bare, target } = t.context;
159
- const project = path.basename(bare);
160
- const pkgName = path.basename(target);
161
- const owner = path.basename(path.dirname(bare));
162
- gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
163
- childProcess.execSync('git tag 1.0.0', execOpts);
164
- const sha = gitAdd('line', 'file', 'More file');
165
-
166
- const git = factory(Git);
167
- const ref = (await git.getBranchName()) ?? 'HEAD';
168
-
169
- interceptGitHubCreate({
170
- owner,
171
- project,
172
- body: { tag_name: '1.1.0', name: 'Release 1.1.0', body: `* More file (${sha})` }
142
+ assert.equal(version, '0.0.1');
143
+ assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (currently at ${latestVersion})`));
144
+ assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
173
145
  });
174
146
 
175
- interceptGitLabPublish({
176
- owner,
177
- project,
178
- body: {
179
- name: 'Release 1.1.0',
180
- ref,
181
- tag_name: '1.1.0',
182
- tag_message: 'Release 1.1.0',
183
- description: `* More file (${sha})`
184
- }
185
- });
147
+ test('should not run hooks for disabled release-cycle methods', async t => {
148
+ const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
149
+
150
+ const container = getContainer({
151
+ hooks,
152
+ git: { push: false },
153
+ github: { release: false },
154
+ gitlab: { release: false },
155
+ npm: { publish: false }
156
+ });
186
157
 
187
- const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
158
+ const exec = t.mock.method(container.shell, 'execFormattedCommand');
188
159
 
189
- const container = getContainer({
190
- increment: 'minor',
191
- hooks,
192
- github: { release: true, pushRepo: `https://github.com/${owner}/${project}`, skipChecks: true },
193
- gitlab: { release: true, pushRepo: `https://gitlab.com/${owner}/${project}`, skipChecks: true },
194
- npm: { name: pkgName, skipChecks: true }
160
+ await runTasks({}, container);
161
+
162
+ const commands = getArgs(exec, 'echo');
163
+
164
+ assert(commands.includes('echo before:init'));
165
+ assert(commands.includes('echo after:afterRelease'));
166
+
167
+ assert(!commands.includes('echo after:git:release'));
168
+ assert(!commands.includes('echo after:github:release'));
169
+ assert(!commands.includes('echo after:gitlab:release'));
170
+ assert(!commands.includes('echo after:npm:release'));
195
171
  });
196
172
 
197
- const exec = sandbox.spy(container.shell, 'execFormattedCommand');
173
+ test('should not run hooks for cancelled release-cycle methods', async t => {
174
+ const pkgName = path.basename(target);
175
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
176
+ childProcess.execSync('git tag 1.0.0', execOpts);
177
+
178
+ const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
179
+ const prompt = mock.fn(([options]) => ({ [options.name]: false }));
180
+ const inquirer = { prompt };
181
+
182
+ const container = getContainer(
183
+ {
184
+ increment: 'minor',
185
+ hooks,
186
+ github: { release: true, skipChecks: true },
187
+ gitlab: { release: true, skipChecks: true },
188
+ npm: { publish: true, skipChecks: true }
189
+ },
190
+ inquirer
191
+ );
192
+
193
+ const exec = t.mock.method(container.shell, 'execFormattedCommand');
194
+
195
+ await runTasks({}, container);
196
+
197
+ const commands = getArgs(exec, 'echo');
198
+
199
+ assert(commands.includes('echo before:init'));
200
+ assert(commands.includes('echo after:afterRelease'));
201
+ assert(commands.includes('echo after:git:bump'));
202
+ assert(commands.includes('echo after:npm:bump'));
203
+
204
+ assert(!commands.includes('echo after:git:release'));
205
+ assert(!commands.includes('echo after:github:release'));
206
+ assert(!commands.includes('echo after:gitlab:release'));
207
+ assert(!commands.includes('echo after:npm:release'));
208
+ });
198
209
 
199
- await runTasks({}, container);
210
+ test('should run "after:*:release" plugin hooks', async t => {
211
+ const project = path.basename(bare);
212
+ const pkgName = path.basename(target);
213
+ const owner = path.basename(path.dirname(bare));
214
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
215
+ childProcess.execSync('git tag 1.0.0', execOpts);
216
+ const sha = gitAdd('line', 'file', 'More file');
217
+
218
+ const git = factory(Git);
219
+ const ref = (await git.getBranchName()) ?? 'HEAD';
220
+
221
+ interceptGitHubCreate(github, {
222
+ owner,
223
+ project,
224
+ body: { tag_name: '1.1.0', name: 'Release 1.1.0', body: `* More file (${sha})` }
225
+ });
200
226
 
201
- const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
227
+ interceptGitLabPublish(gitlab, {
228
+ owner,
229
+ project,
230
+ body: {
231
+ name: 'Release 1.1.0',
232
+ ref,
233
+ tag_name: '1.1.0',
234
+ tag_message: 'Release 1.1.0',
235
+ description: `* More file (${sha})`
236
+ }
237
+ });
202
238
 
203
- t.true(commands.includes('echo after:git:bump'));
204
- t.true(commands.includes('echo after:npm:bump'));
205
- t.true(commands.includes('echo after:git:release'));
206
- t.true(commands.includes('echo after:github:release'));
207
- t.true(commands.includes('echo after:gitlab:release'));
208
- t.true(commands.includes('echo after:npm:release'));
209
- });
239
+ const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
240
+
241
+ const container = getContainer({
242
+ increment: 'minor',
243
+ hooks,
244
+ github: { release: true, pushRepo: `https://github.com/${owner}/${project}`, skipChecks: true },
245
+ gitlab: { release: true, pushRepo: `https://gitlab.com/${owner}/${project}`, skipChecks: true },
246
+ npm: { name: pkgName, skipChecks: true }
247
+ });
248
+
249
+ const exec = t.mock.method(container.shell, 'execFormattedCommand');
210
250
 
211
- test.serial('should show only version prompt', async t => {
212
- const config = { ci: false, 'only-version': true };
213
- await runTasks({}, getContainer(config));
214
- t.true(defaultInquirer.prompt.calledOnce);
215
- t.is(defaultInquirer.prompt.firstCall.args[0][0].name, 'incrementList');
251
+ await runTasks({}, container);
252
+
253
+ const commands = getArgs(exec, 'echo');
254
+
255
+ assert(commands.includes('echo after:git:bump'));
256
+ assert(commands.includes('echo after:npm:bump'));
257
+ assert(commands.includes('echo after:git:release'));
258
+ assert(commands.includes('echo after:github:release'));
259
+ assert(commands.includes('echo after:gitlab:release'));
260
+ assert(commands.includes('echo after:npm:release'));
261
+ });
262
+
263
+ test('should show only version prompt', async () => {
264
+ const config = { ci: false, 'only-version': true };
265
+ await runTasks({}, getContainer(config));
266
+ assert.equal(prompt.mock.callCount(), 1);
267
+ assert.equal(prompt.mock.calls[0].arguments[0][0].name, 'incrementList');
268
+ });
216
269
  });