release-it 0.0.0-pl.0
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/LICENSE +21 -0
- package/README.md +421 -0
- package/bin/release-it.js +42 -0
- package/config/release-it.json +70 -0
- package/lib/cli.js +44 -0
- package/lib/config.js +139 -0
- package/lib/index.js +152 -0
- package/lib/log.js +69 -0
- package/lib/plugin/GitBase.js +125 -0
- package/lib/plugin/GitRelease.js +58 -0
- package/lib/plugin/Plugin.js +73 -0
- package/lib/plugin/factory.js +89 -0
- package/lib/plugin/git/Git.js +220 -0
- package/lib/plugin/git/prompts.js +19 -0
- package/lib/plugin/github/GitHub.js +403 -0
- package/lib/plugin/github/prompts.js +16 -0
- package/lib/plugin/github/util.js +39 -0
- package/lib/plugin/gitlab/GitLab.js +277 -0
- package/lib/plugin/gitlab/prompts.js +9 -0
- package/lib/plugin/npm/npm.js +281 -0
- package/lib/plugin/npm/prompts.js +12 -0
- package/lib/plugin/version/Version.js +129 -0
- package/lib/prompt.js +33 -0
- package/lib/shell.js +91 -0
- package/lib/spinner.js +29 -0
- package/lib/util.js +109 -0
- package/package.json +122 -0
- package/test/cli.js +20 -0
- package/test/config.js +144 -0
- package/test/git.init.js +250 -0
- package/test/git.js +358 -0
- package/test/github.js +487 -0
- package/test/gitlab.js +252 -0
- package/test/log.js +143 -0
- package/test/npm.js +417 -0
- package/test/plugin-name.js +9 -0
- package/test/plugins.js +238 -0
- package/test/prompt.js +97 -0
- package/test/resources/file-v2.0.1.txt +1 -0
- package/test/resources/file-v2.0.2.txt +1 -0
- package/test/resources/file1 +1 -0
- package/test/shell.js +74 -0
- package/test/spinner.js +58 -0
- package/test/stub/config/default/.release-it.json +5 -0
- package/test/stub/config/invalid-config-rc +1 -0
- package/test/stub/config/invalid-config-txt +2 -0
- package/test/stub/config/merge/.release-it.json +5 -0
- package/test/stub/config/merge/package.json +7 -0
- package/test/stub/config/toml/.release-it.toml +2 -0
- package/test/stub/config/yaml/.release-it.yaml +2 -0
- package/test/stub/config/yml/.release-it.yml +2 -0
- package/test/stub/github.js +130 -0
- package/test/stub/gitlab.js +44 -0
- package/test/stub/plugin-context.js +36 -0
- package/test/stub/plugin-replace.js +9 -0
- package/test/stub/plugin.js +39 -0
- package/test/stub/shell.js +24 -0
- package/test/tasks.interactive.js +208 -0
- package/test/tasks.js +585 -0
- package/test/util/helpers.js +32 -0
- package/test/util/index.js +78 -0
- package/test/util/setup.js +5 -0
- package/test/utils.js +97 -0
- package/test/version.js +173 -0
package/test/tasks.js
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import test from 'ava';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import sh from 'shelljs';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import sinon from 'sinon';
|
|
7
|
+
import Log from '../lib/log.js';
|
|
8
|
+
import Spinner from '../lib/spinner.js';
|
|
9
|
+
import Config from '../lib/config.js';
|
|
10
|
+
import runTasks from '../lib/index.js';
|
|
11
|
+
import { mkTmpDir, gitAdd, getArgs } from './util/helpers.js';
|
|
12
|
+
import ShellStub from './stub/shell.js';
|
|
13
|
+
import {
|
|
14
|
+
interceptUser as interceptGitLabUser,
|
|
15
|
+
interceptCollaborator as interceptGitLabCollaborator,
|
|
16
|
+
interceptPublish as interceptGitLabPublish,
|
|
17
|
+
interceptAsset as interceptGitLabAsset
|
|
18
|
+
} from './stub/gitlab.js';
|
|
19
|
+
import {
|
|
20
|
+
interceptAuthentication as interceptGitHubAuthentication,
|
|
21
|
+
interceptCollaborator as interceptGitHubCollaborator,
|
|
22
|
+
interceptCreate as interceptGitHubCreate,
|
|
23
|
+
interceptAsset as interceptGitHubAsset
|
|
24
|
+
} from './stub/github.js';
|
|
25
|
+
|
|
26
|
+
const rootDir = new URL('..', import.meta.url);
|
|
27
|
+
|
|
28
|
+
const noop = Promise.resolve();
|
|
29
|
+
|
|
30
|
+
const sandbox = sinon.createSandbox();
|
|
31
|
+
|
|
32
|
+
const npmMajorVersion = semver.major(process.env.npm_config_user_agent.match(/npm\/([^ ]+)/)[1]);
|
|
33
|
+
|
|
34
|
+
const testConfig = {
|
|
35
|
+
ci: true,
|
|
36
|
+
config: false
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const log = sandbox.createStubInstance(Log);
|
|
40
|
+
const spinner = sandbox.createStubInstance(Spinner);
|
|
41
|
+
spinner.show.callsFake(({ enabled = true, task }) => (enabled ? task() : noop));
|
|
42
|
+
|
|
43
|
+
const getContainer = options => {
|
|
44
|
+
const config = new Config(Object.assign({}, testConfig, options));
|
|
45
|
+
const shell = new ShellStub({ container: { log, config } });
|
|
46
|
+
return {
|
|
47
|
+
log,
|
|
48
|
+
spinner,
|
|
49
|
+
config,
|
|
50
|
+
shell
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
test.before(t => {
|
|
55
|
+
t.timeout(90 * 1000);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test.serial.beforeEach(t => {
|
|
59
|
+
const bare = mkTmpDir();
|
|
60
|
+
const target = mkTmpDir();
|
|
61
|
+
sh.pushd('-q', bare);
|
|
62
|
+
sh.exec(`git init --bare .`);
|
|
63
|
+
sh.exec(`git clone ${bare} ${target}`);
|
|
64
|
+
sh.pushd('-q', target);
|
|
65
|
+
gitAdd('line', 'file', 'Add file');
|
|
66
|
+
t.context = { bare, target };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test.serial.afterEach(() => {
|
|
70
|
+
sandbox.resetHistory();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test.serial('should run tasks without throwing errors', async t => {
|
|
74
|
+
sh.mv('.git', 'foo');
|
|
75
|
+
const { name, latestVersion, version } = await runTasks({}, getContainer());
|
|
76
|
+
t.true(log.obtrusive.firstCall.args[0].includes(`release ${name} (${latestVersion}...${version})`));
|
|
77
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test.serial('should run tasks without package.json', async t => {
|
|
81
|
+
sh.exec('git tag 1.0.0');
|
|
82
|
+
gitAdd('line', 'file', 'Add file');
|
|
83
|
+
const { name } = await runTasks({}, getContainer({ increment: 'major', git: { commit: false } }));
|
|
84
|
+
t.true(log.obtrusive.firstCall.args[0].includes(`release ${name} (1.0.0...2.0.0)`));
|
|
85
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
86
|
+
t.is(log.warn.callCount, 0);
|
|
87
|
+
{
|
|
88
|
+
const { stdout } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
89
|
+
t.is(stdout.trim(), '2.0.0');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test.serial('should disable plugins', async t => {
|
|
94
|
+
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
|
95
|
+
sh.exec('git tag 1.2.3');
|
|
96
|
+
gitAdd('line', 'file', 'Add file');
|
|
97
|
+
const container = getContainer({ increment: 'minor', git: false, npm: false });
|
|
98
|
+
const { latestVersion, version } = await runTasks({}, container);
|
|
99
|
+
t.is(latestVersion, '0.0.0');
|
|
100
|
+
t.is(version, '0.1.0');
|
|
101
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test.serial('should run tasks with minimal config and without any warnings/errors', async t => {
|
|
105
|
+
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
|
106
|
+
sh.exec('git tag 1.2.3');
|
|
107
|
+
gitAdd('line', 'file', 'More file');
|
|
108
|
+
await runTasks({}, getContainer({ increment: 'patch' }));
|
|
109
|
+
t.true(log.obtrusive.firstCall.args[0].includes('release my-package (1.2.3...1.2.4)'));
|
|
110
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
111
|
+
const { stdout } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
112
|
+
t.is(stdout.trim(), '1.2.4');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test.serial('should use pkg.version', async t => {
|
|
116
|
+
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
|
117
|
+
await runTasks({}, getContainer({ increment: 'minor' }));
|
|
118
|
+
t.true(log.obtrusive.firstCall.args[0].includes('release my-package (1.2.3...1.3.0)'));
|
|
119
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
120
|
+
const { stdout } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
121
|
+
t.is(stdout.trim(), '1.3.0');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test.serial('should use pkg.version (in sub dir) w/o tagging repo', async t => {
|
|
125
|
+
gitAdd('{"name":"root-package","version":"1.0.0"}', 'package.json', 'Add package.json');
|
|
126
|
+
sh.exec('git tag 1.0.0');
|
|
127
|
+
sh.mkdir('my-package');
|
|
128
|
+
sh.pushd('-q', 'my-package');
|
|
129
|
+
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
|
130
|
+
const container = getContainer({ increment: 'minor', git: { tag: false } });
|
|
131
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
132
|
+
await runTasks({}, container);
|
|
133
|
+
t.true(log.obtrusive.firstCall.args[0].endsWith('release my-package (1.2.3...1.3.0)'));
|
|
134
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
135
|
+
const { stdout } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
136
|
+
t.is(stdout.trim(), '1.0.0');
|
|
137
|
+
const npmArgs = getArgs(exec.args, 'npm');
|
|
138
|
+
t.is(npmArgs[5], 'npm version 1.3.0 --no-git-tag-version');
|
|
139
|
+
exec.restore();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test.serial('should ignore version in pkg.version and use git tag instead', async t => {
|
|
143
|
+
gitAdd('{"name":"my-package","version":"0.0.0"}', 'package.json', 'Add package.json');
|
|
144
|
+
sh.exec('git tag 1.1.1');
|
|
145
|
+
gitAdd('line', 'file', 'More file');
|
|
146
|
+
await runTasks({}, getContainer({ increment: 'minor', npm: { ignoreVersion: true } }));
|
|
147
|
+
t.true(log.obtrusive.firstCall.args[0].includes('release my-package (1.1.1...1.2.0)'));
|
|
148
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
149
|
+
const { stdout } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
150
|
+
t.is(stdout.trim(), '1.2.0');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test.serial('should release all the things (basic)', async t => {
|
|
154
|
+
const { bare, target } = t.context;
|
|
155
|
+
const project = path.basename(bare);
|
|
156
|
+
const pkgName = path.basename(target);
|
|
157
|
+
const owner = path.basename(path.dirname(bare));
|
|
158
|
+
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
|
159
|
+
sh.exec('git tag 1.0.0');
|
|
160
|
+
const sha = gitAdd('line', 'file', 'More file');
|
|
161
|
+
|
|
162
|
+
interceptGitHubAuthentication();
|
|
163
|
+
interceptGitHubCollaborator({ owner, project });
|
|
164
|
+
interceptGitHubCreate({
|
|
165
|
+
owner,
|
|
166
|
+
project,
|
|
167
|
+
body: { tag_name: '1.0.1', name: 'Release 1.0.1', body: `* More file (${sha})`, prerelease: false }
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const container = getContainer({
|
|
171
|
+
github: { release: true, pushRepo: `https://github.com/${owner}/${project}` },
|
|
172
|
+
npm: { name: pkgName }
|
|
173
|
+
});
|
|
174
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
175
|
+
|
|
176
|
+
await runTasks({}, container);
|
|
177
|
+
|
|
178
|
+
const npmArgs = getArgs(container.shell.exec.args, 'npm');
|
|
179
|
+
|
|
180
|
+
t.deepEqual(npmArgs, [
|
|
181
|
+
'npm ping',
|
|
182
|
+
'npm whoami',
|
|
183
|
+
`npm show ${pkgName}@latest version`,
|
|
184
|
+
'npm --version',
|
|
185
|
+
`npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
|
|
186
|
+
'npm version 1.0.1 --no-git-tag-version',
|
|
187
|
+
'npm publish . --tag latest'
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
|
|
191
|
+
t.true(log.log.firstCall.args[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
|
|
192
|
+
t.true(log.log.secondCall.args[0].endsWith(`https://github.com/${owner}/${project}/releases/tag/1.0.1`));
|
|
193
|
+
|
|
194
|
+
exec.restore();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test.serial('should release with correct tag name', async t => {
|
|
198
|
+
const { bare, target } = t.context;
|
|
199
|
+
const project = path.basename(bare);
|
|
200
|
+
const pkgName = path.basename(target);
|
|
201
|
+
const owner = path.basename(path.dirname(bare));
|
|
202
|
+
const { stdout } = sh.exec('git rev-parse --abbrev-ref HEAD');
|
|
203
|
+
const branchName = stdout.trim();
|
|
204
|
+
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
|
205
|
+
sh.exec(`git tag ${pkgName}-${branchName}-1.0.0`);
|
|
206
|
+
const sha = gitAdd('line', 'file', 'More file');
|
|
207
|
+
|
|
208
|
+
interceptGitHubCreate({
|
|
209
|
+
owner,
|
|
210
|
+
project,
|
|
211
|
+
body: {
|
|
212
|
+
tag_name: `${pkgName}-${branchName}-1.0.1`,
|
|
213
|
+
name: 'Release 1.0.1',
|
|
214
|
+
body: `* More file (${sha})`,
|
|
215
|
+
prerelease: false
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const container = getContainer({
|
|
220
|
+
git: { tagName: '${npm.name}-${branchName}-${version}' },
|
|
221
|
+
github: { release: true, skipChecks: true, pushRepo: `https://github.com/${owner}/${project}` }
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
225
|
+
|
|
226
|
+
await runTasks({}, container);
|
|
227
|
+
|
|
228
|
+
const gitArgs = getArgs(container.shell.exec.args, 'git');
|
|
229
|
+
|
|
230
|
+
t.true(gitArgs.includes(`git tag --annotate --message Release 1.0.1 ${pkgName}-${branchName}-1.0.1`));
|
|
231
|
+
t.true(
|
|
232
|
+
log.log.secondCall.args[0].endsWith(
|
|
233
|
+
`https://github.com/${owner}/${project}/releases/tag/${pkgName}-${branchName}-1.0.1`
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
exec.restore();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test.serial('should release all the things (pre-release, github, gitlab)', async t => {
|
|
241
|
+
const { bare, target } = t.context;
|
|
242
|
+
const project = path.basename(bare);
|
|
243
|
+
const pkgName = path.basename(target);
|
|
244
|
+
const owner = path.basename(path.dirname(bare));
|
|
245
|
+
const url = `https://gitlab.com/${owner}/${project}`;
|
|
246
|
+
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
|
247
|
+
sh.exec('git tag v1.0.0');
|
|
248
|
+
const sha = gitAdd('line', 'file', 'More file');
|
|
249
|
+
sh.exec('git push --follow-tags');
|
|
250
|
+
|
|
251
|
+
interceptGitHubAuthentication();
|
|
252
|
+
interceptGitHubCollaborator({ owner, project });
|
|
253
|
+
interceptGitHubCreate({
|
|
254
|
+
owner,
|
|
255
|
+
project,
|
|
256
|
+
body: {
|
|
257
|
+
tag_name: 'v1.1.0-alpha.0',
|
|
258
|
+
name: 'Release 1.1.0-alpha.0',
|
|
259
|
+
body: `Notes for ${pkgName} [v1.1.0-alpha.0]: ${sha}`,
|
|
260
|
+
prerelease: true
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
interceptGitHubAsset({ owner, project, body: 'lineline' });
|
|
264
|
+
|
|
265
|
+
interceptGitLabUser({ owner });
|
|
266
|
+
interceptGitLabCollaborator({ owner, project });
|
|
267
|
+
interceptGitLabAsset({ owner, project });
|
|
268
|
+
interceptGitLabPublish({
|
|
269
|
+
owner,
|
|
270
|
+
project,
|
|
271
|
+
body: {
|
|
272
|
+
name: 'Release 1.1.0-alpha.0',
|
|
273
|
+
tag_name: 'v1.1.0-alpha.0',
|
|
274
|
+
description: `Notes for ${pkgName}: ${sha}`,
|
|
275
|
+
assets: {
|
|
276
|
+
links: [
|
|
277
|
+
{
|
|
278
|
+
name: 'file',
|
|
279
|
+
url: `${url}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file`
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const container = getContainer({
|
|
287
|
+
increment: 'minor',
|
|
288
|
+
preRelease: 'alpha',
|
|
289
|
+
git: {
|
|
290
|
+
changelog: 'git log --pretty=format:%h ${latestTag}...HEAD',
|
|
291
|
+
commitMessage: 'Release ${version} for ${name} (from ${latestVersion})',
|
|
292
|
+
tagAnnotation: '${repo.owner} ${repo.repository} ${repo.project}'
|
|
293
|
+
},
|
|
294
|
+
github: {
|
|
295
|
+
release: true,
|
|
296
|
+
pushRepo: `https://github.com/${owner}/${project}`,
|
|
297
|
+
releaseNotes: 'echo Notes for ${name} [v${version}]: ${changelog}',
|
|
298
|
+
assets: ['file']
|
|
299
|
+
},
|
|
300
|
+
gitlab: {
|
|
301
|
+
release: true,
|
|
302
|
+
pushRepo: url,
|
|
303
|
+
releaseNotes: 'echo Notes for ${name}: ${changelog}',
|
|
304
|
+
assets: ['file']
|
|
305
|
+
},
|
|
306
|
+
npm: { name: pkgName }
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
310
|
+
|
|
311
|
+
await runTasks({}, container);
|
|
312
|
+
|
|
313
|
+
const npmArgs = getArgs(container.shell.exec.args, 'npm');
|
|
314
|
+
t.deepEqual(npmArgs, [
|
|
315
|
+
'npm ping',
|
|
316
|
+
'npm whoami',
|
|
317
|
+
`npm show ${pkgName}@latest version`,
|
|
318
|
+
'npm --version',
|
|
319
|
+
`npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
|
|
320
|
+
'npm version 1.1.0-alpha.0 --no-git-tag-version',
|
|
321
|
+
'npm publish . --tag alpha'
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
const { stdout: commitMessage } = sh.exec('git log --oneline --format=%B -n 1 HEAD');
|
|
325
|
+
t.is(commitMessage.trim(), `Release 1.1.0-alpha.0 for ${pkgName} (from 1.0.0)`);
|
|
326
|
+
|
|
327
|
+
const { stdout: tagName } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
328
|
+
t.is(tagName.trim(), 'v1.1.0-alpha.0');
|
|
329
|
+
|
|
330
|
+
const { stdout: tagAnnotation } = sh.exec('git for-each-ref refs/tags/v1.1.0-alpha.0 --format="%(contents)"');
|
|
331
|
+
t.is(tagAnnotation.trim(), `${owner} ${owner}/${project} ${project}`);
|
|
332
|
+
|
|
333
|
+
t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...1.1.0-alpha.0)`));
|
|
334
|
+
t.true(log.log.firstCall.args[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
|
|
335
|
+
t.true(log.log.secondCall.args[0].endsWith(`https://github.com/${owner}/${project}/releases/tag/v1.1.0-alpha.0`));
|
|
336
|
+
t.true(log.log.thirdCall.args[0].endsWith(`${project}/-/releases`));
|
|
337
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
338
|
+
|
|
339
|
+
exec.restore();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test.serial('should publish pre-release without pre-id with different npm.tag', async t => {
|
|
343
|
+
const { target } = t.context;
|
|
344
|
+
const pkgName = path.basename(target);
|
|
345
|
+
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
|
346
|
+
sh.exec('git tag v1.0.0');
|
|
347
|
+
|
|
348
|
+
const container = getContainer({ increment: 'major', preRelease: true, npm: { name: pkgName, tag: 'next' } });
|
|
349
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
350
|
+
|
|
351
|
+
await runTasks({}, container);
|
|
352
|
+
|
|
353
|
+
const npmArgs = getArgs(container.shell.exec.args, 'npm');
|
|
354
|
+
t.deepEqual(npmArgs, [
|
|
355
|
+
'npm ping',
|
|
356
|
+
'npm whoami',
|
|
357
|
+
`npm show ${pkgName}@latest version`,
|
|
358
|
+
'npm --version',
|
|
359
|
+
`npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
|
|
360
|
+
'npm version 2.0.0-0 --no-git-tag-version',
|
|
361
|
+
'npm publish . --tag next'
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
const { stdout } = sh.exec('git describe --tags --match=* --abbrev=0');
|
|
365
|
+
t.is(stdout.trim(), 'v2.0.0-0');
|
|
366
|
+
t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...2.0.0-0)`));
|
|
367
|
+
t.true(log.log.firstCall.args[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
|
|
368
|
+
t.regex(log.log.lastCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
369
|
+
|
|
370
|
+
exec.restore();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test.serial('should handle private package correctly, bump lockfile', async t => {
|
|
374
|
+
const { target } = t.context;
|
|
375
|
+
const pkgName = path.basename(target);
|
|
376
|
+
gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package.json', 'Add package.json');
|
|
377
|
+
gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package-lock.json', 'Add package-lock.json');
|
|
378
|
+
|
|
379
|
+
const container = getContainer({ npm: { name: pkgName, private: true } });
|
|
380
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
381
|
+
|
|
382
|
+
await runTasks({}, container);
|
|
383
|
+
|
|
384
|
+
const npmArgs = getArgs(container.shell.exec.args, 'npm');
|
|
385
|
+
t.deepEqual(npmArgs, ['npm version 1.0.1 --no-git-tag-version']);
|
|
386
|
+
t.true(log.obtrusive.firstCall.args[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
|
|
387
|
+
t.is(log.warn.length, 0);
|
|
388
|
+
t.regex(log.log.firstCall.args[0], /Done \(in [0-9]+s\.\)/);
|
|
389
|
+
|
|
390
|
+
exec.restore();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test.serial('should initially publish non-private scoped npm package privately', async t => {
|
|
394
|
+
const { target } = t.context;
|
|
395
|
+
const pkgName = path.basename(target);
|
|
396
|
+
gitAdd(`{"name":"@scope/${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
|
397
|
+
|
|
398
|
+
const container = getContainer({ npm: { name: pkgName } });
|
|
399
|
+
|
|
400
|
+
const exec = sinon.stub(container.shell, 'exec').callThrough();
|
|
401
|
+
exec.withArgs(`npm show @scope/${pkgName}@latest version`).rejects();
|
|
402
|
+
|
|
403
|
+
await runTasks({}, container);
|
|
404
|
+
|
|
405
|
+
const npmArgs = getArgs(container.shell.exec.args, 'npm');
|
|
406
|
+
t.is(npmArgs[6], 'npm publish . --tag latest');
|
|
407
|
+
exec.restore();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test.serial('should use pkg.publishConfig.registry', async t => {
|
|
411
|
+
const { target } = t.context;
|
|
412
|
+
const pkgName = path.basename(target);
|
|
413
|
+
const registry = 'https://my-registry.example.org';
|
|
414
|
+
|
|
415
|
+
gitAdd(
|
|
416
|
+
JSON.stringify({
|
|
417
|
+
name: pkgName,
|
|
418
|
+
version: '1.2.3',
|
|
419
|
+
publishConfig: { registry }
|
|
420
|
+
}),
|
|
421
|
+
'package.json',
|
|
422
|
+
'Add package.json'
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const container = getContainer();
|
|
426
|
+
|
|
427
|
+
const exec = sinon.spy(container.shell, 'exec');
|
|
428
|
+
|
|
429
|
+
await runTasks({}, container);
|
|
430
|
+
|
|
431
|
+
const npmArgs = getArgs(exec.args, 'npm');
|
|
432
|
+
t.is(npmArgs[0], `npm ping --registry ${registry}`);
|
|
433
|
+
t.is(npmArgs[1], `npm whoami --registry ${registry}`);
|
|
434
|
+
t.true(container.log.log.firstCall.args[0].endsWith(`${registry}/package/${pkgName}`));
|
|
435
|
+
|
|
436
|
+
exec.restore();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test.serial('should propagate errors', async t => {
|
|
440
|
+
const config = {
|
|
441
|
+
hooks: {
|
|
442
|
+
'before:init': 'some-failing-command'
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const container = getContainer(config);
|
|
446
|
+
|
|
447
|
+
await t.throwsAsync(runTasks({}, container), { message: /some-failing-command/ });
|
|
448
|
+
|
|
449
|
+
t.is(log.error.callCount, 1);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test.serial('should use custom changelog command with context', async t => {
|
|
453
|
+
const { bare } = t.context;
|
|
454
|
+
const project = path.basename(bare);
|
|
455
|
+
const owner = path.basename(path.dirname(bare));
|
|
456
|
+
sh.exec('git tag v1.0.0');
|
|
457
|
+
gitAdd('line', 'file', 'More file');
|
|
458
|
+
|
|
459
|
+
interceptGitHubAuthentication();
|
|
460
|
+
interceptGitHubCollaborator({ owner, project });
|
|
461
|
+
interceptGitHubCreate({
|
|
462
|
+
owner,
|
|
463
|
+
project,
|
|
464
|
+
body: {
|
|
465
|
+
tag_name: 'v1.1.0',
|
|
466
|
+
name: 'Release 1.1.0',
|
|
467
|
+
body: 'custom-changelog-generator --from=v1.0.0 --to=v1.1.0',
|
|
468
|
+
draft: false,
|
|
469
|
+
prerelease: false
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const container = getContainer({
|
|
474
|
+
increment: 'minor',
|
|
475
|
+
github: {
|
|
476
|
+
release: true,
|
|
477
|
+
releaseNotes: 'echo custom-changelog-generator --from=${latestTag} --to=${tagName}',
|
|
478
|
+
pushRepo: `https://github.com/${owner}/${project}`
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const exec = sinon.spy(container.shell, 'execStringCommand');
|
|
483
|
+
|
|
484
|
+
await runTasks({}, container);
|
|
485
|
+
|
|
486
|
+
const command = exec.args.find(([command]) => command.includes('custom-changelog-generator'));
|
|
487
|
+
|
|
488
|
+
t.is(command[0], 'echo custom-changelog-generator --from=v1.0.0 --to=v1.1.0');
|
|
489
|
+
|
|
490
|
+
exec.restore();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
{
|
|
494
|
+
test.serial('should run all hooks', async t => {
|
|
495
|
+
gitAdd(`{"name":"hooked","version":"1.0.0","type":"module"}`, 'package.json', 'Add package.json');
|
|
496
|
+
sh.exec(`npm install ${rootDir}`);
|
|
497
|
+
const plugin = "import { Plugin } from 'release-it'; class MyPlugin extends Plugin {}; export default MyPlugin;";
|
|
498
|
+
sh.ShellString(plugin).toEnd('my-plugin.js');
|
|
499
|
+
|
|
500
|
+
const hooks = {};
|
|
501
|
+
['before', 'after'].forEach(prefix => {
|
|
502
|
+
['version', 'git', 'npm', 'my-plugin'].forEach(ns => {
|
|
503
|
+
['init', 'beforeBump', 'bump', 'beforeRelease', 'release', 'afterRelease'].forEach(cycle => {
|
|
504
|
+
hooks[`${prefix}:${cycle}`] = `echo ${prefix}:${cycle}`;
|
|
505
|
+
hooks[`${prefix}:${ns}:${cycle}`] = `echo ${prefix}:${ns}:${cycle}`;
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
const container = getContainer({
|
|
510
|
+
plugins: { './my-plugin.js': {} },
|
|
511
|
+
git: { requireCleanWorkingDir: false },
|
|
512
|
+
hooks
|
|
513
|
+
});
|
|
514
|
+
const exec = sinon.spy(container.shell, 'execFormattedCommand');
|
|
515
|
+
|
|
516
|
+
await runTasks({}, container);
|
|
517
|
+
|
|
518
|
+
const commands = _.flatten(exec.args).filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
|
|
519
|
+
|
|
520
|
+
t.deepEqual(commands, [
|
|
521
|
+
'echo before:init',
|
|
522
|
+
'echo before:my-plugin:init',
|
|
523
|
+
'echo after:my-plugin:init',
|
|
524
|
+
'echo before:npm:init',
|
|
525
|
+
'echo after:npm:init',
|
|
526
|
+
'echo before:git:init',
|
|
527
|
+
'echo after:git:init',
|
|
528
|
+
'echo before:version:init',
|
|
529
|
+
'echo after:version:init',
|
|
530
|
+
'echo after:init',
|
|
531
|
+
'echo before:beforeBump',
|
|
532
|
+
'echo before:my-plugin:beforeBump',
|
|
533
|
+
'echo after:my-plugin:beforeBump',
|
|
534
|
+
'echo before:npm:beforeBump',
|
|
535
|
+
'echo after:npm:beforeBump',
|
|
536
|
+
'echo before:git:beforeBump',
|
|
537
|
+
'echo after:git:beforeBump',
|
|
538
|
+
'echo before:version:beforeBump',
|
|
539
|
+
'echo after:version:beforeBump',
|
|
540
|
+
'echo after:beforeBump',
|
|
541
|
+
'echo before:bump',
|
|
542
|
+
'echo before:my-plugin:bump',
|
|
543
|
+
'echo after:my-plugin:bump',
|
|
544
|
+
'echo before:npm:bump',
|
|
545
|
+
'echo after:npm:bump',
|
|
546
|
+
'echo before:git:bump',
|
|
547
|
+
'echo after:git:bump',
|
|
548
|
+
'echo before:version:bump',
|
|
549
|
+
'echo after:version:bump',
|
|
550
|
+
'echo after:bump',
|
|
551
|
+
'echo before:beforeRelease',
|
|
552
|
+
'echo before:my-plugin:beforeRelease',
|
|
553
|
+
'echo after:my-plugin:beforeRelease',
|
|
554
|
+
'echo before:npm:beforeRelease',
|
|
555
|
+
'echo after:npm:beforeRelease',
|
|
556
|
+
'echo before:git:beforeRelease',
|
|
557
|
+
'echo after:git:beforeRelease',
|
|
558
|
+
'echo before:version:beforeRelease',
|
|
559
|
+
'echo after:version:beforeRelease',
|
|
560
|
+
'echo after:beforeRelease',
|
|
561
|
+
'echo before:release',
|
|
562
|
+
'echo before:npm:release',
|
|
563
|
+
'echo after:npm:release',
|
|
564
|
+
'echo before:git:release',
|
|
565
|
+
'echo after:git:release',
|
|
566
|
+
'echo before:version:release',
|
|
567
|
+
'echo after:version:release',
|
|
568
|
+
'echo before:my-plugin:release',
|
|
569
|
+
'echo after:my-plugin:release',
|
|
570
|
+
'echo after:release',
|
|
571
|
+
'echo before:afterRelease',
|
|
572
|
+
'echo before:npm:afterRelease',
|
|
573
|
+
'echo after:npm:afterRelease',
|
|
574
|
+
'echo before:git:afterRelease',
|
|
575
|
+
'echo after:git:afterRelease',
|
|
576
|
+
'echo before:version:afterRelease',
|
|
577
|
+
'echo after:version:afterRelease',
|
|
578
|
+
'echo before:my-plugin:afterRelease',
|
|
579
|
+
'echo after:my-plugin:afterRelease',
|
|
580
|
+
'echo after:afterRelease'
|
|
581
|
+
]);
|
|
582
|
+
|
|
583
|
+
exec.restore();
|
|
584
|
+
});
|
|
585
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import sh from 'shelljs';
|
|
5
|
+
|
|
6
|
+
const mkTmpDir = () => {
|
|
7
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'release-it-'));
|
|
8
|
+
return dir;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const readFile = file => fs.promises.readFile(path.resolve(file), 'utf8');
|
|
12
|
+
|
|
13
|
+
const gitAdd = (content, filePath, message) => {
|
|
14
|
+
const pathSegments = filePath.split('/').filter(Boolean);
|
|
15
|
+
pathSegments.pop();
|
|
16
|
+
if (pathSegments.length) {
|
|
17
|
+
sh.mkdir('-p', pathSegments.join('/'));
|
|
18
|
+
}
|
|
19
|
+
sh.ShellString(content).toEnd(filePath);
|
|
20
|
+
sh.exec(`git add ${filePath}`);
|
|
21
|
+
const { stdout } = sh.exec(`git commit -m "${message}"`);
|
|
22
|
+
const match = stdout.match(/\[.+([a-z0-9]{7})\]/);
|
|
23
|
+
return match ? match[1] : null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getArgs = (args, prefix) =>
|
|
27
|
+
args
|
|
28
|
+
.map(args => (typeof args[0] !== 'string' ? args[0].join(' ') : args[0]))
|
|
29
|
+
.filter(cmd => cmd.startsWith(prefix))
|
|
30
|
+
.map(cmd => cmd.trim());
|
|
31
|
+
|
|
32
|
+
export { mkTmpDir, readFile, gitAdd, getArgs };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import { parseVersion } from '../../lib/util.js';
|
|
5
|
+
import Log from '../../lib/log.js';
|
|
6
|
+
import Config from '../../lib/config.js';
|
|
7
|
+
import ShellStub from '../stub/shell.js';
|
|
8
|
+
import Spinner from '../../lib/spinner.js';
|
|
9
|
+
import Prompt from '../../lib/prompt.js';
|
|
10
|
+
|
|
11
|
+
export let factory = (Definition, { namespace, options = {}, container = {} } = {}) => {
|
|
12
|
+
_.defaults(options, { ci: true, verbose: false, 'dry-run': false, debug: false });
|
|
13
|
+
|
|
14
|
+
const ns = namespace || Definition.name.toLowerCase();
|
|
15
|
+
|
|
16
|
+
container.config = container.config || new Config(Object.assign({ config: false }, options));
|
|
17
|
+
container.log = container.log || sinon.createStubInstance(Log);
|
|
18
|
+
|
|
19
|
+
const spinner = container.spinner || sinon.createStubInstance(Spinner);
|
|
20
|
+
spinner.show.callsFake(({ enabled = true, task }) => (enabled ? task() : () => {}));
|
|
21
|
+
container.spinner = spinner;
|
|
22
|
+
container.shell = container.shell || new ShellStub({ container });
|
|
23
|
+
container.prompt = container.prompt || new Prompt({ container });
|
|
24
|
+
container.shell.cache = { set: () => {}, has: () => false };
|
|
25
|
+
|
|
26
|
+
return new Definition({
|
|
27
|
+
namespace: ns,
|
|
28
|
+
options,
|
|
29
|
+
container
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getIncrement = plugin =>
|
|
34
|
+
plugin.getIncrement(plugin.options) || plugin.getContext('increment') || plugin.config.getContext('increment');
|
|
35
|
+
|
|
36
|
+
const getVersion = async (plugin, options) => {
|
|
37
|
+
const { latestVersion, increment } = options;
|
|
38
|
+
return (
|
|
39
|
+
(await plugin.getIncrementedVersionCI(options)) ||
|
|
40
|
+
(await plugin.getIncrementedVersion(options)) ||
|
|
41
|
+
(increment !== false ? semver.inc(latestVersion, increment || 'patch') : latestVersion)
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export let runTasks = async plugin => {
|
|
46
|
+
await plugin.init();
|
|
47
|
+
|
|
48
|
+
const name = (await plugin.getName()) || '__test__';
|
|
49
|
+
const latestVersion = (await plugin.getLatestVersion()) || '1.0.0';
|
|
50
|
+
const latestTag = plugin.config.getContext('latestTag');
|
|
51
|
+
const changelog = (await plugin.getChangelog(latestVersion)) || null;
|
|
52
|
+
const increment = getIncrement(plugin);
|
|
53
|
+
|
|
54
|
+
plugin.config.setContext({ name, latestVersion, latestTag, changelog });
|
|
55
|
+
|
|
56
|
+
const { preRelease } = plugin.config.options;
|
|
57
|
+
const isPreRelease = Boolean(preRelease);
|
|
58
|
+
const preReleaseId = typeof preRelease === 'string' ? preRelease : null;
|
|
59
|
+
const version = await getVersion(plugin, { latestVersion, increment, isPreRelease, preReleaseId });
|
|
60
|
+
|
|
61
|
+
plugin.config.setContext(parseVersion(version));
|
|
62
|
+
|
|
63
|
+
await plugin.beforeBump();
|
|
64
|
+
await plugin.bump(version);
|
|
65
|
+
|
|
66
|
+
const tagName = plugin.config.getContext('tagName') || version;
|
|
67
|
+
plugin.config.setContext({ tagName });
|
|
68
|
+
|
|
69
|
+
await plugin.beforeRelease();
|
|
70
|
+
await plugin.release();
|
|
71
|
+
await plugin.afterRelease();
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
latestVersion,
|
|
76
|
+
version
|
|
77
|
+
};
|
|
78
|
+
};
|