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