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