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/npm.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { patchFs } from 'fs-monkey';
|
|
3
|
+
import test from 'ava';
|
|
4
|
+
import sinon from 'sinon';
|
|
5
|
+
import { vol } from 'memfs';
|
|
6
|
+
import npm from '../lib/plugin/npm/npm.js';
|
|
7
|
+
import { factory, runTasks } from './util/index.js';
|
|
8
|
+
import { getArgs } from './util/helpers.js';
|
|
9
|
+
|
|
10
|
+
const mockFs = volume => {
|
|
11
|
+
vol.fromJSON(volume);
|
|
12
|
+
const unpatch = patchFs(vol);
|
|
13
|
+
return () => {
|
|
14
|
+
vol.reset();
|
|
15
|
+
unpatch();
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
test('should return npm package url', t => {
|
|
20
|
+
const options = { npm: { name: 'my-cool-package' } };
|
|
21
|
+
const npmClient = factory(npm, { options });
|
|
22
|
+
t.is(npmClient.getPackageUrl(), 'https://www.npmjs.com/package/my-cool-package');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should return npm package url (custom registry)', t => {
|
|
26
|
+
const options = { npm: { name: 'my-cool-package', publishConfig: { registry: 'https://registry.example.org/' } } };
|
|
27
|
+
const npmClient = factory(npm, { options });
|
|
28
|
+
t.is(npmClient.getPackageUrl(), 'https://registry.example.org/package/my-cool-package');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should return npm package url (custom publicPath)', t => {
|
|
32
|
+
const options = { npm: { name: 'my-cool-package', publishConfig: { publicPath: '/custom/public-path' } } };
|
|
33
|
+
const npmClient = factory(npm, { options });
|
|
34
|
+
t.is(npmClient.getPackageUrl(), 'https://www.npmjs.com/custom/public-path/my-cool-package');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should return npm package url (custom registry and publicPath)', t => {
|
|
38
|
+
const options = {
|
|
39
|
+
npm: {
|
|
40
|
+
name: 'my-cool-package',
|
|
41
|
+
publishConfig: { registry: 'https://registry.example.org/', publicPath: '/custom/public-path' }
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const npmClient = factory(npm, { options });
|
|
45
|
+
t.is(npmClient.getPackageUrl(), 'https://registry.example.org/custom/public-path/my-cool-package');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should return default tag', async t => {
|
|
49
|
+
const npmClient = factory(npm);
|
|
50
|
+
const tag = await npmClient.resolveTag();
|
|
51
|
+
t.is(tag, 'latest');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should resolve default tag for pre-release', async t => {
|
|
55
|
+
const npmClient = factory(npm);
|
|
56
|
+
const stub = sinon.stub(npmClient, 'getRegistryPreReleaseTags').resolves([]);
|
|
57
|
+
const tag = await npmClient.resolveTag('1.0.0-0');
|
|
58
|
+
t.is(tag, 'next');
|
|
59
|
+
stub.restore();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should guess tag from registry for pre-release', async t => {
|
|
63
|
+
const npmClient = factory(npm);
|
|
64
|
+
const stub = sinon.stub(npmClient, 'getRegistryPreReleaseTags').resolves(['alpha']);
|
|
65
|
+
const tag = await npmClient.resolveTag('1.0.0-0');
|
|
66
|
+
t.is(tag, 'alpha');
|
|
67
|
+
stub.restore();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should derive tag from pre-release version', async t => {
|
|
71
|
+
const npmClient = factory(npm);
|
|
72
|
+
const tag = await npmClient.resolveTag('1.0.2-alpha.3');
|
|
73
|
+
t.is(tag, 'alpha');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should use provided (default) tag even for pre-release', async t => {
|
|
77
|
+
const options = { npm: { tag: 'latest' } };
|
|
78
|
+
const npmClient = factory(npm, { options });
|
|
79
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
80
|
+
await npmClient.bump('1.0.0-next.0');
|
|
81
|
+
t.is(npmClient.getContext('tag'), 'latest');
|
|
82
|
+
exec.restore();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should throw when `npm version` fails', async t => {
|
|
86
|
+
const npmClient = factory(npm);
|
|
87
|
+
const exec = sinon
|
|
88
|
+
.stub(npmClient.shell, 'exec')
|
|
89
|
+
.rejects(new Error('npm ERR! Version not changed, might want --allow-same-version'));
|
|
90
|
+
|
|
91
|
+
await t.throwsAsync(npmClient.bump('1.0.0-next.0'), { message: /Version not changed/ });
|
|
92
|
+
|
|
93
|
+
exec.restore();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should return first pre-release tag from package in registry when resolving tag without pre-id', async t => {
|
|
97
|
+
const npmClient = factory(npm);
|
|
98
|
+
const response = {
|
|
99
|
+
latest: '1.4.1',
|
|
100
|
+
alpha: '2.0.0-alpha.1',
|
|
101
|
+
beta: '2.0.0-beta.3'
|
|
102
|
+
};
|
|
103
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves(JSON.stringify(response));
|
|
104
|
+
t.is(await npmClient.resolveTag('2.0.0-5'), 'alpha');
|
|
105
|
+
exec.restore();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should return default pre-release tag when resolving tag without pre-id', async t => {
|
|
109
|
+
const npmClient = factory(npm);
|
|
110
|
+
const response = {
|
|
111
|
+
latest: '1.4.1'
|
|
112
|
+
};
|
|
113
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves(JSON.stringify(response));
|
|
114
|
+
t.is(await npmClient.resolveTag('2.0.0-0'), 'next');
|
|
115
|
+
exec.restore();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should handle erroneous output when resolving tag without pre-id', async t => {
|
|
119
|
+
const npmClient = factory(npm);
|
|
120
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves('');
|
|
121
|
+
t.is(await npmClient.resolveTag('2.0.0-0'), 'next');
|
|
122
|
+
exec.restore();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('should handle errored request when resolving tag without pre-id', async t => {
|
|
126
|
+
const npmClient = factory(npm);
|
|
127
|
+
const exec = sinon.stub(npmClient.shell, 'exec').rejects();
|
|
128
|
+
t.is(await npmClient.resolveTag('2.0.0-0'), 'next');
|
|
129
|
+
exec.restore();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should add registry to commands when specified', async t => {
|
|
133
|
+
const npmClient = factory(npm);
|
|
134
|
+
npmClient.setContext({ publishConfig: { registry: 'registry.example.org' } });
|
|
135
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
136
|
+
exec.withArgs('npm whoami --registry registry.example.org').resolves('john');
|
|
137
|
+
exec
|
|
138
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) release-it --registry registry.example.org/)
|
|
139
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
140
|
+
await runTasks(npmClient);
|
|
141
|
+
t.is(exec.args[0][0], 'npm ping --registry registry.example.org');
|
|
142
|
+
t.is(exec.args[1][0], 'npm whoami --registry registry.example.org');
|
|
143
|
+
t.regex(exec.args[2][0], /npm show release-it@[a-z]+ version --registry registry\.example\.org/);
|
|
144
|
+
exec.restore();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should not throw when executing tasks', async t => {
|
|
148
|
+
const npmClient = factory(npm);
|
|
149
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
150
|
+
exec.withArgs('npm whoami').resolves('john');
|
|
151
|
+
exec
|
|
152
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) release-it/)
|
|
153
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
154
|
+
await t.notThrowsAsync(runTasks(npmClient));
|
|
155
|
+
exec.restore();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should throw if npm is down', async t => {
|
|
159
|
+
const npmClient = factory(npm);
|
|
160
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
161
|
+
exec.withArgs('npm ping').rejects();
|
|
162
|
+
await t.throwsAsync(runTasks(npmClient), { message: /^Unable to reach npm registry/ });
|
|
163
|
+
exec.restore();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('should not throw if npm returns 400/404 for unsupported ping/whoami/access', async t => {
|
|
167
|
+
const npmClient = factory(npm);
|
|
168
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
169
|
+
const pingError = "npm ERR! code E404\nnpm ERR! 404 Package '--ping' not found : ping";
|
|
170
|
+
const whoamiError = "npm ERR! code E404\nnpm ERR! 404 Package '--whoami' not found : whoami";
|
|
171
|
+
const accessError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/collaborators';
|
|
172
|
+
exec.withArgs('npm ping').rejects(new Error(pingError));
|
|
173
|
+
exec.withArgs('npm whoami').rejects(new Error(whoamiError));
|
|
174
|
+
exec.withArgs('npm access').rejects(new Error(accessError));
|
|
175
|
+
await runTasks(npmClient);
|
|
176
|
+
t.is(exec.lastCall.args[0].trim(), 'npm publish . --tag latest');
|
|
177
|
+
exec.restore();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('should not throw if npm returns 400 for unsupported ping/whoami/access', async t => {
|
|
181
|
+
const npmClient = factory(npm);
|
|
182
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
183
|
+
const pingError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/ping?write=true';
|
|
184
|
+
const whoamiError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/whoami';
|
|
185
|
+
const accessError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/collaborators';
|
|
186
|
+
exec.withArgs('npm ping').rejects(new Error(pingError));
|
|
187
|
+
exec.withArgs('npm whoami').rejects(new Error(whoamiError));
|
|
188
|
+
exec.withArgs('npm access').rejects(new Error(accessError));
|
|
189
|
+
await runTasks(npmClient);
|
|
190
|
+
t.is(exec.lastCall.args[0].trim(), 'npm publish . --tag latest');
|
|
191
|
+
exec.restore();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should not throw if npm returns 404 for unsupported ping', async t => {
|
|
195
|
+
const npmClient = factory(npm);
|
|
196
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
197
|
+
const pingError = 'npm ERR! <title>404 - No content for path /-/ping</title>';
|
|
198
|
+
exec.withArgs('npm ping').rejects(new Error(pingError));
|
|
199
|
+
exec.withArgs('npm whoami').resolves('john');
|
|
200
|
+
exec
|
|
201
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) release-it/)
|
|
202
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
203
|
+
await runTasks(npmClient);
|
|
204
|
+
t.is(exec.lastCall.args[0].trim(), 'npm publish . --tag latest');
|
|
205
|
+
exec.restore();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('should throw if user is not authenticated', async t => {
|
|
209
|
+
const npmClient = factory(npm);
|
|
210
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
211
|
+
exec.withArgs('npm whoami').rejects();
|
|
212
|
+
await t.throwsAsync(runTasks(npmClient), { message: /^Not authenticated with npm/ });
|
|
213
|
+
exec.restore();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('should throw if user is not a collaborator (v9)', async t => {
|
|
217
|
+
const npmClient = factory(npm);
|
|
218
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
219
|
+
exec.withArgs('npm whoami').resolves('ada');
|
|
220
|
+
exec.withArgs('npm --version').resolves('9.2.0');
|
|
221
|
+
exec.withArgs('npm access list collaborators --json release-it').resolves(JSON.stringify({ john: ['write'] }));
|
|
222
|
+
await t.throwsAsync(runTasks(npmClient), { message: /^User ada is not a collaborator for release-it/ });
|
|
223
|
+
exec.restore();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('should throw if user is not a collaborator (v8)', async t => {
|
|
227
|
+
const npmClient = factory(npm);
|
|
228
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
229
|
+
exec.withArgs('npm whoami').resolves('ada');
|
|
230
|
+
exec.withArgs('npm --version').resolves('8.2.0');
|
|
231
|
+
exec
|
|
232
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) release-it/)
|
|
233
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
234
|
+
await t.throwsAsync(runTasks(npmClient), { message: /^User ada is not a collaborator for release-it/ });
|
|
235
|
+
exec.restore();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('should not throw if user is not a collaborator on a new package', async t => {
|
|
239
|
+
const npmClient = factory(npm);
|
|
240
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
241
|
+
exec.withArgs('npm whoami').resolves('ada');
|
|
242
|
+
exec
|
|
243
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) release-it/)
|
|
244
|
+
.rejects(
|
|
245
|
+
new Error(
|
|
246
|
+
'npm ERR! code E404\nnpm ERR! 404 Not Found - GET https://registry.npmjs.org/-/package/release-it/collaborators?format=cli - File not found'
|
|
247
|
+
)
|
|
248
|
+
);
|
|
249
|
+
await t.notThrowsAsync(runTasks(npmClient));
|
|
250
|
+
exec.restore();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('should publish a new private scoped package as npm would', async t => {
|
|
254
|
+
const options = { npm: { tag: 'beta' } };
|
|
255
|
+
const npmClient = factory(npm, { options });
|
|
256
|
+
npmClient.setContext({ name: '@scoped/pkg' });
|
|
257
|
+
const exec = sinon.spy(npmClient.shell, 'exec');
|
|
258
|
+
await npmClient.publish();
|
|
259
|
+
t.is(exec.lastCall.args[0].trim(), 'npm publish . --tag beta');
|
|
260
|
+
exec.restore();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('should not publish private package', async t => {
|
|
264
|
+
const npmClient = factory(npm);
|
|
265
|
+
npmClient.setContext({ name: 'pkg', private: true });
|
|
266
|
+
const exec = sinon.spy(npmClient.shell, 'exec');
|
|
267
|
+
await npmClient.publish();
|
|
268
|
+
const publish = exec.args.filter(arg => arg[0].startsWith('npm publish'));
|
|
269
|
+
t.is(publish.length, 0);
|
|
270
|
+
t.regex(npmClient.log.warn.lastCall.args[0], /package is private/);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('should handle 2FA and publish with OTP', async t => {
|
|
274
|
+
const npmClient = factory(npm);
|
|
275
|
+
npmClient.setContext({ name: 'pkg' });
|
|
276
|
+
|
|
277
|
+
const exec = sinon.stub(npmClient.shell, 'exec');
|
|
278
|
+
|
|
279
|
+
exec.onFirstCall().rejects(new Error('Initial error with one-time pass.'));
|
|
280
|
+
exec.onSecondCall().rejects(new Error('The provided one-time pass is incorrect.'));
|
|
281
|
+
exec.onThirdCall().resolves();
|
|
282
|
+
|
|
283
|
+
await npmClient.publish({
|
|
284
|
+
otpCallback: () =>
|
|
285
|
+
npmClient.publish({
|
|
286
|
+
otp: '123',
|
|
287
|
+
otpCallback: () => npmClient.publish({ otp: '123456' })
|
|
288
|
+
})
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
t.is(exec.callCount, 3);
|
|
292
|
+
t.is(exec.firstCall.args[0].trim(), 'npm publish . --tag latest');
|
|
293
|
+
t.is(exec.secondCall.args[0].trim(), 'npm publish . --tag latest --otp 123');
|
|
294
|
+
t.is(exec.thirdCall.args[0].trim(), 'npm publish . --tag latest --otp 123456');
|
|
295
|
+
|
|
296
|
+
t.is(npmClient.log.warn.callCount, 1);
|
|
297
|
+
t.is(npmClient.log.warn.firstCall.args[0], 'The provided OTP is incorrect or has expired.');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('should publish', async t => {
|
|
301
|
+
const npmClient = factory(npm);
|
|
302
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
303
|
+
exec.withArgs('npm whoami').resolves('john');
|
|
304
|
+
exec
|
|
305
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) release-it/)
|
|
306
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
307
|
+
await runTasks(npmClient);
|
|
308
|
+
t.is(exec.lastCall.args[0].trim(), 'npm publish . --tag latest');
|
|
309
|
+
exec.restore();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('should use extra publish arguments ', async t => {
|
|
313
|
+
const options = { npm: { skipChecks: true, publishArgs: '--registry=http://my-internal-registry.local' } };
|
|
314
|
+
const npmClient = factory(npm, { options });
|
|
315
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
316
|
+
await runTasks(npmClient);
|
|
317
|
+
t.is(exec.lastCall.args[0].trim(), 'npm publish . --tag latest --registry=http://my-internal-registry.local');
|
|
318
|
+
exec.restore();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('should skip checks', async t => {
|
|
322
|
+
const options = { npm: { skipChecks: true } };
|
|
323
|
+
const npmClient = factory(npm, { options });
|
|
324
|
+
await t.notThrowsAsync(npmClient.init());
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('should publish to a different/scoped registry', async t => {
|
|
328
|
+
const resetFs = mockFs({
|
|
329
|
+
[path.resolve('package.json')]: JSON.stringify({
|
|
330
|
+
name: '@my-scope/my-pkg',
|
|
331
|
+
version: '1.0.0',
|
|
332
|
+
publishConfig: {
|
|
333
|
+
access: 'public',
|
|
334
|
+
'@my-scope:registry': 'https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/'
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
});
|
|
338
|
+
const options = { npm };
|
|
339
|
+
const npmClient = factory(npm, { options });
|
|
340
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
341
|
+
exec
|
|
342
|
+
.withArgs('npm whoami --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/')
|
|
343
|
+
.resolves('john');
|
|
344
|
+
exec
|
|
345
|
+
.withArgs(
|
|
346
|
+
/npm access (list collaborators --json|ls-collaborators) @my-scope\/my-pkg --registry https:\/\/gitlab\.com\/api\/v4\/projects\/my-scope%2Fmy-pkg\/packages\/npm\//
|
|
347
|
+
)
|
|
348
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
349
|
+
|
|
350
|
+
await runTasks(npmClient);
|
|
351
|
+
|
|
352
|
+
t.deepEqual(getArgs(exec.args, 'npm'), [
|
|
353
|
+
'npm ping --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/',
|
|
354
|
+
'npm whoami --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/',
|
|
355
|
+
'npm show @my-scope/my-pkg@latest version --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/',
|
|
356
|
+
'npm --version',
|
|
357
|
+
'npm version 1.0.1 --no-git-tag-version',
|
|
358
|
+
'npm publish . --tag latest --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/'
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
exec.restore();
|
|
362
|
+
resetFs();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('should not publish when `npm version` fails', async t => {
|
|
366
|
+
const resetFs = mockFs({
|
|
367
|
+
[path.resolve('package.json')]: JSON.stringify({
|
|
368
|
+
name: '@my-scope/my-pkg',
|
|
369
|
+
version: '1.0.0'
|
|
370
|
+
})
|
|
371
|
+
});
|
|
372
|
+
const options = { npm };
|
|
373
|
+
const npmClient = factory(npm, { options });
|
|
374
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
375
|
+
exec.withArgs('npm whoami').resolves('john');
|
|
376
|
+
exec
|
|
377
|
+
.withArgs(/npm access (list collaborators --json|ls-collaborators) @my-scope\/my-pkg/)
|
|
378
|
+
.resolves(JSON.stringify({ john: ['write'] }));
|
|
379
|
+
exec
|
|
380
|
+
.withArgs('npm version 1.0.1 --no-git-tag-version')
|
|
381
|
+
.rejects('npm ERR! Version not changed, might want --allow-same-version');
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
await runTasks(npmClient);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
t.regex(error.toString(), /Version not changed/);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
t.deepEqual(getArgs(exec.args, 'npm'), [
|
|
390
|
+
'npm ping',
|
|
391
|
+
'npm whoami',
|
|
392
|
+
'npm show @my-scope/my-pkg@latest version',
|
|
393
|
+
'npm --version',
|
|
394
|
+
'npm version 1.0.1 --no-git-tag-version'
|
|
395
|
+
]);
|
|
396
|
+
|
|
397
|
+
exec.restore();
|
|
398
|
+
resetFs();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('should add allow-same-version argument', async t => {
|
|
402
|
+
const options = { npm: { skipChecks: true, allowSameVersion: true } };
|
|
403
|
+
const npmClient = factory(npm, { options });
|
|
404
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
405
|
+
await runTasks(npmClient);
|
|
406
|
+
const version = exec.args.filter(arg => arg[0].startsWith('npm version'));
|
|
407
|
+
t.regex(version[0][0], / --allow-same-version/);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('should add version arguments', async t => {
|
|
411
|
+
const options = { npm: { skipChecks: true, versionArgs: ['--workspaces-update=false', '--allow-same-version'] } };
|
|
412
|
+
const npmClient = factory(npm, { options });
|
|
413
|
+
const exec = sinon.stub(npmClient.shell, 'exec').resolves();
|
|
414
|
+
await runTasks(npmClient);
|
|
415
|
+
const version = exec.args.filter(arg => arg[0].startsWith('npm version'));
|
|
416
|
+
t.regex(version[0][0], / --workspaces-update=false --allow-same-version/);
|
|
417
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import { getPluginName } from '../lib/plugin/factory.js';
|
|
3
|
+
|
|
4
|
+
test('pluginName can return correct name for variants', t => {
|
|
5
|
+
t.is(getPluginName('plain-plugin'), 'plain-plugin');
|
|
6
|
+
t.is(getPluginName('@some/scoped-plugin'), '@some/scoped-plugin');
|
|
7
|
+
t.is(getPluginName('@some/nested/scoped-plugin'), '@some/nested/scoped-plugin');
|
|
8
|
+
t.is(getPluginName('./relative-plugin.cjs'), 'relative-plugin');
|
|
9
|
+
});
|
package/test/plugins.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import test from 'ava';
|
|
3
|
+
import sh from 'shelljs';
|
|
4
|
+
import sinon from 'sinon';
|
|
5
|
+
import Log from '../lib/log.js';
|
|
6
|
+
import Spinner from '../lib/spinner.js';
|
|
7
|
+
import Config from '../lib/config.js';
|
|
8
|
+
import { parseGitUrl } from '../lib/util.js';
|
|
9
|
+
import runTasks from '../lib/index.js';
|
|
10
|
+
import MyPlugin from './stub/plugin.js';
|
|
11
|
+
import ReplacePlugin from './stub/plugin-replace.js';
|
|
12
|
+
import ContextPlugin from './stub/plugin-context.js';
|
|
13
|
+
import { mkTmpDir } from './util/helpers.js';
|
|
14
|
+
import ShellStub from './stub/shell.js';
|
|
15
|
+
|
|
16
|
+
const noop = Promise.resolve();
|
|
17
|
+
|
|
18
|
+
const sandbox = sinon.createSandbox();
|
|
19
|
+
|
|
20
|
+
const testConfig = {
|
|
21
|
+
ci: true,
|
|
22
|
+
config: false
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const log = sandbox.createStubInstance(Log);
|
|
26
|
+
const spinner = sandbox.createStubInstance(Spinner);
|
|
27
|
+
spinner.show.callsFake(({ enabled = true, task }) => (enabled ? task() : noop));
|
|
28
|
+
|
|
29
|
+
const getContainer = options => {
|
|
30
|
+
const config = new Config(Object.assign({}, testConfig, options));
|
|
31
|
+
const shell = new ShellStub({ container: { log, config } });
|
|
32
|
+
return {
|
|
33
|
+
log,
|
|
34
|
+
spinner,
|
|
35
|
+
config,
|
|
36
|
+
shell
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
test.before(t => {
|
|
41
|
+
t.timeout(60 * 1000);
|
|
42
|
+
sh.exec('npm link');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test.serial.beforeEach(t => {
|
|
46
|
+
const dir = mkTmpDir();
|
|
47
|
+
sh.pushd('-q', dir);
|
|
48
|
+
t.context = { dir };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.serial.afterEach(() => {
|
|
52
|
+
sandbox.resetHistory();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test.serial('should instantiate plugins and execute all release-cycle methods', async t => {
|
|
56
|
+
const { dir } = t.context;
|
|
57
|
+
|
|
58
|
+
const pluginDir = mkTmpDir();
|
|
59
|
+
sh.pushd('-q', pluginDir);
|
|
60
|
+
sh.ShellString(JSON.stringify({ name: 'my-plugin', version: '1.0.0', type: 'module' })).toEnd(
|
|
61
|
+
join(pluginDir, 'package.json')
|
|
62
|
+
);
|
|
63
|
+
sh.exec(`npm link release-it`);
|
|
64
|
+
const content = "import { Plugin } from 'release-it'; " + MyPlugin.toString() + '; export default MyPlugin;';
|
|
65
|
+
sh.ShellString(content).toEnd(join(pluginDir, 'index.js'));
|
|
66
|
+
|
|
67
|
+
sh.pushd('-q', dir);
|
|
68
|
+
sh.mkdir('-p', 'my/plugin');
|
|
69
|
+
sh.pushd('-q', 'my/plugin');
|
|
70
|
+
sh.ShellString(content).toEnd(join(dir, 'my', 'plugin', 'index.js'));
|
|
71
|
+
|
|
72
|
+
sh.pushd('-q', dir);
|
|
73
|
+
sh.ShellString(JSON.stringify({ name: 'project', version: '1.0.0', type: 'module' })).toEnd(
|
|
74
|
+
join(dir, 'package.json')
|
|
75
|
+
);
|
|
76
|
+
sh.exec(`npm install ${pluginDir}`);
|
|
77
|
+
sh.exec(`npm link release-it`);
|
|
78
|
+
|
|
79
|
+
const config = {
|
|
80
|
+
plugins: {
|
|
81
|
+
'my-plugin': {
|
|
82
|
+
name: 'foo'
|
|
83
|
+
},
|
|
84
|
+
'./my/plugin/index.js': [
|
|
85
|
+
'named-plugin',
|
|
86
|
+
{
|
|
87
|
+
name: 'bar'
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const container = getContainer(config);
|
|
93
|
+
|
|
94
|
+
const result = await runTasks({}, container);
|
|
95
|
+
|
|
96
|
+
t.deepEqual(container.log.info.args, [
|
|
97
|
+
['my-plugin:foo:init'],
|
|
98
|
+
['named-plugin:bar:init'],
|
|
99
|
+
['my-plugin:foo:getName'],
|
|
100
|
+
['my-plugin:foo:getLatestVersion'],
|
|
101
|
+
['my-plugin:foo:getIncrement'],
|
|
102
|
+
['my-plugin:foo:getIncrementedVersionCI'],
|
|
103
|
+
['named-plugin:bar:getIncrementedVersionCI'],
|
|
104
|
+
['my-plugin:foo:beforeBump'],
|
|
105
|
+
['named-plugin:bar:beforeBump'],
|
|
106
|
+
['my-plugin:foo:bump:1.3.0'],
|
|
107
|
+
['named-plugin:bar:bump:1.3.0'],
|
|
108
|
+
['my-plugin:foo:beforeRelease'],
|
|
109
|
+
['named-plugin:bar:beforeRelease'],
|
|
110
|
+
['my-plugin:foo:release'],
|
|
111
|
+
['named-plugin:bar:release'],
|
|
112
|
+
['my-plugin:foo:afterRelease'],
|
|
113
|
+
['named-plugin:bar:afterRelease']
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
t.deepEqual(result, {
|
|
117
|
+
changelog: undefined,
|
|
118
|
+
name: 'new-project-name',
|
|
119
|
+
latestVersion: '1.2.3',
|
|
120
|
+
version: '1.3.0'
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test.serial('should instantiate plugins and execute all release-cycle methods for scoped plugins', async t => {
|
|
125
|
+
const { dir } = t.context;
|
|
126
|
+
|
|
127
|
+
const pluginDir = mkTmpDir();
|
|
128
|
+
sh.pushd('-q', pluginDir);
|
|
129
|
+
sh.ShellString(JSON.stringify({ name: '@scoped/my-plugin', version: '1.0.0', type: 'module' })).toEnd(
|
|
130
|
+
join(pluginDir, 'package.json')
|
|
131
|
+
);
|
|
132
|
+
sh.exec(`npm link release-it`);
|
|
133
|
+
const content = "import { Plugin } from 'release-it'; " + MyPlugin.toString() + '; export default MyPlugin;';
|
|
134
|
+
sh.ShellString(content).toEnd(join(pluginDir, 'index.js'));
|
|
135
|
+
|
|
136
|
+
sh.pushd('-q', dir);
|
|
137
|
+
sh.ShellString(JSON.stringify({ name: 'project', version: '1.0.0', type: 'module' })).toEnd(
|
|
138
|
+
join(dir, 'package.json')
|
|
139
|
+
);
|
|
140
|
+
sh.exec(`npm install ${pluginDir}`);
|
|
141
|
+
sh.exec(`npm link release-it`);
|
|
142
|
+
|
|
143
|
+
const config = {
|
|
144
|
+
plugins: {
|
|
145
|
+
'@scoped/my-plugin': {
|
|
146
|
+
name: 'foo'
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const container = getContainer(config);
|
|
151
|
+
|
|
152
|
+
const result = await runTasks({}, container);
|
|
153
|
+
|
|
154
|
+
t.deepEqual(container.log.info.args, [
|
|
155
|
+
['@scoped/my-plugin:foo:init'],
|
|
156
|
+
['@scoped/my-plugin:foo:getName'],
|
|
157
|
+
['@scoped/my-plugin:foo:getLatestVersion'],
|
|
158
|
+
['@scoped/my-plugin:foo:getIncrement'],
|
|
159
|
+
['@scoped/my-plugin:foo:getIncrementedVersionCI'],
|
|
160
|
+
['@scoped/my-plugin:foo:beforeBump'],
|
|
161
|
+
['@scoped/my-plugin:foo:bump:1.3.0'],
|
|
162
|
+
['@scoped/my-plugin:foo:beforeRelease'],
|
|
163
|
+
['@scoped/my-plugin:foo:release'],
|
|
164
|
+
['@scoped/my-plugin:foo:afterRelease']
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
t.deepEqual(result, {
|
|
168
|
+
changelog: undefined,
|
|
169
|
+
name: 'new-project-name',
|
|
170
|
+
latestVersion: '1.2.3',
|
|
171
|
+
version: '1.3.0'
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test.serial('should disable core plugins', async t => {
|
|
176
|
+
const { dir } = t.context;
|
|
177
|
+
sh.ShellString(JSON.stringify({ name: 'project', version: '1.0.0' })).toEnd(join(dir, 'package.json'));
|
|
178
|
+
const content =
|
|
179
|
+
"import { Plugin } from 'release-it'; " + ReplacePlugin.toString() + '; export default ReplacePlugin;';
|
|
180
|
+
sh.ShellString(content).toEnd(join(dir, 'replace-plugin.mjs'));
|
|
181
|
+
sh.exec(`npm link release-it`);
|
|
182
|
+
|
|
183
|
+
const config = {
|
|
184
|
+
plugins: {
|
|
185
|
+
'./replace-plugin.mjs': {}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const container = getContainer(config);
|
|
189
|
+
|
|
190
|
+
const result = await runTasks({}, container);
|
|
191
|
+
|
|
192
|
+
t.deepEqual(result, {
|
|
193
|
+
changelog: undefined,
|
|
194
|
+
name: undefined,
|
|
195
|
+
latestVersion: '0.0.0',
|
|
196
|
+
version: undefined
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test.serial('should expose context to execute commands', async t => {
|
|
201
|
+
const { dir } = t.context;
|
|
202
|
+
sh.ShellString(JSON.stringify({ name: 'pkg-name', version: '1.0.0', type: 'module' })).toEnd(
|
|
203
|
+
join(dir, 'package.json')
|
|
204
|
+
);
|
|
205
|
+
const content =
|
|
206
|
+
"import { Plugin } from 'release-it'; " + ContextPlugin.toString() + '; export default ContextPlugin;';
|
|
207
|
+
sh.ShellString(content).toEnd(join(dir, 'context-plugin.js'));
|
|
208
|
+
sh.exec(`npm link release-it`);
|
|
209
|
+
|
|
210
|
+
const repo = parseGitUrl('https://github.com/user/pkg');
|
|
211
|
+
|
|
212
|
+
const container = getContainer({ plugins: { './context-plugin.js': {} } });
|
|
213
|
+
const exec = sinon.spy(container.shell, 'execFormattedCommand');
|
|
214
|
+
|
|
215
|
+
container.config.setContext({ repo });
|
|
216
|
+
container.config.setContext({ tagName: '1.0.1' });
|
|
217
|
+
|
|
218
|
+
await runTasks({}, container);
|
|
219
|
+
|
|
220
|
+
const pluginExecArgs = exec.args
|
|
221
|
+
.map(args => args[0])
|
|
222
|
+
.filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
|
|
223
|
+
|
|
224
|
+
t.deepEqual(pluginExecArgs, [
|
|
225
|
+
'echo false',
|
|
226
|
+
'echo false',
|
|
227
|
+
`echo pkg-name user 1.0.0 1.0.1`,
|
|
228
|
+
`echo pkg-name user 1.0.0 1.0.1`,
|
|
229
|
+
`echo user pkg user/pkg 1.0.1`,
|
|
230
|
+
`echo user pkg user/pkg 1.0.1`,
|
|
231
|
+
`echo user pkg user/pkg 1.0.1`,
|
|
232
|
+
`echo user pkg user/pkg 1.0.1`,
|
|
233
|
+
`echo pkg 1.0.0 1.0.1 1.0.1`,
|
|
234
|
+
`echo pkg 1.0.0 1.0.1 1.0.1`,
|
|
235
|
+
`echo pkg 1.0.0 1.0.1 1.0.1`,
|
|
236
|
+
`echo pkg 1.0.0 1.0.1 1.0.1`
|
|
237
|
+
]);
|
|
238
|
+
});
|