release-it 20.1.0 → 20.2.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.
@@ -215,6 +215,16 @@ class npm extends Plugin {
215
215
  return urlJoin(baseUrl, publicPath, this.getName());
216
216
  }
217
217
 
218
+ getStagedPackagesUrl() {
219
+ const name = this.getName();
220
+ const entity = name.startsWith('@') ? name.slice(1).split('/')[0] : this.getContext('username');
221
+ return entity ? `https://www.npmjs.com/settings/${entity}/staged-packages` : 'https://www.npmjs.com';
222
+ }
223
+
224
+ logWouldStage() {
225
+ this.log.log(`📦 Would stage (dry-run). Approve at ${this.getStagedPackagesUrl()} after a real run.`);
226
+ }
227
+
218
228
  getRegistry() {
219
229
  const { publishConfig } = this.getContext();
220
230
  const registries = publishConfig
@@ -278,13 +288,19 @@ class npm extends Plugin {
278
288
  ...fixArgs(publishArgs)
279
289
  ].filter(Boolean);
280
290
  const publishCommand = stage ? ['stage', 'publish'] : ['publish'];
281
- const isInteractive = !this.config.isCI || Boolean(this.config.isPromptOnlyVersion);
291
+ const isInteractive = !stage && (!this.config.isCI || Boolean(this.config.isPromptOnlyVersion));
282
292
  return this.exec([publishPackageManager, ...publishCommand, ...args], {
283
293
  options: { ...getOptions(), interactive: isInteractive }
284
294
  })
285
- .then(() => {
295
+ .then(output => {
286
296
  if (stage) {
287
- this.setContext({ isStaged: true });
297
+ if (this.config.isDryRun) {
298
+ this.logWouldStage();
299
+ } else {
300
+ const id = output?.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)?.[0];
301
+ const approve = `${publishPackageManager} stage approve${id ? ` ${id}` : ''}`;
302
+ this.log.log(`📦 Staged, not yet published. Approve at ${this.getStagedPackagesUrl()} (or \`${approve}\`).`);
303
+ }
288
304
  } else {
289
305
  this.setContext({ isReleased: true });
290
306
  this.config.setContext({ isReleased: true });
@@ -293,6 +309,7 @@ class npm extends Plugin {
293
309
  .catch(err => {
294
310
  this.debug(err);
295
311
  if (this.config.isDryRun && /publish over the previously published version/.test(err)) {
312
+ if (stage) this.logWouldStage();
296
313
  return Promise.resolve();
297
314
  }
298
315
 
@@ -309,14 +326,9 @@ class npm extends Plugin {
309
326
  }
310
327
 
311
328
  afterRelease() {
312
- const { isReleased, isStaged } = this.getContext();
329
+ const { isReleased } = this.getContext();
313
330
  if (isReleased) {
314
331
  this.log.log(`🔗 ${this.getPackageUrl()}`);
315
- } else if (isStaged) {
316
- const pm = this.options.publishPackageManager || 'npm';
317
- this.log.log(
318
- `📦 Staged for publishing. Approve with \`${pm} stage approve\` (see \`${pm} stage list\`) or on npmjs.com. Requires 2FA.`
319
- );
320
332
  }
321
333
  }
322
334
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "20.1.0",
3
+ "version": "20.2.0",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
5
  "keywords": [
6
6
  "build",
package/test/npm.js CHANGED
@@ -377,6 +377,47 @@ describe('npm', async () => {
377
377
  assert.deepEqual(exec.mock.calls.at(-1).arguments[0], ['pnpm', 'stage', 'publish', '.', '--tag', 'latest']);
378
378
  });
379
379
 
380
+ test('should print the staged-packages approval URL after stage publish', async t => {
381
+ const unscoped = await factory(npm, { options: { npm: { stage: true } } });
382
+ unscoped.setContext({ name: 'pkg', username: 'webpro' });
383
+ t.mock.method(unscoped.shell, 'exec', () => Promise.resolve());
384
+ await unscoped.publish();
385
+ assert.match(unscoped.log.log.mock.calls.map(c => c.arguments[0]).join('\n'), /settings\/webpro\/staged-packages/);
386
+
387
+ const scoped = await factory(npm, { options: { npm: { stage: true } } });
388
+ scoped.setContext({ name: '@release-it/x', username: 'webpro' });
389
+ t.mock.method(scoped.shell, 'exec', () => Promise.resolve());
390
+ await scoped.publish();
391
+ assert.match(scoped.log.log.mock.calls.map(c => c.arguments[0]).join('\n'), /settings\/release-it\/staged-packages/);
392
+ });
393
+
394
+ test('should surface the stage id from publish output in the approval message', async t => {
395
+ const npmClient = await factory(npm, { options: { npm: { stage: true } } });
396
+ npmClient.setContext({ name: 'pkg', username: 'webpro' });
397
+ const id = '71289309-c232-432b-a2d4-32a14fa08177';
398
+ t.mock.method(npmClient.shell, 'exec', () => Promise.resolve(`+ pkg@1.0.0 (staged with id ${id})`));
399
+ await npmClient.publish();
400
+ const logged = npmClient.log.log.mock.calls.map(c => c.arguments[0]).join('\n');
401
+ assert.match(logged, new RegExp(`npm stage approve ${id}`));
402
+ });
403
+
404
+ test('should describe a staged dry-run, whether npm resolves or rejects the un-bumped version', async t => {
405
+ const execs = [
406
+ () => Promise.resolve('staged with id 71289309-c232-432b-a2d4-32a14fa08177'),
407
+ () => Promise.reject(new Error('npm error You cannot publish over the previously published versions: 1.0.0.'))
408
+ ];
409
+ for (const exec of execs) {
410
+ const npmClient = await factory(npm, { options: { 'dry-run': true, npm: { stage: true } } });
411
+ npmClient.setContext({ name: 'pkg', username: 'webpro' });
412
+ t.mock.method(npmClient.shell, 'exec', exec);
413
+ await npmClient.publish();
414
+ const logged = npmClient.log.log.mock.calls.map(c => c.arguments[0]).join('\n');
415
+ assert.match(logged, /Would stage \(dry-run\)/);
416
+ assert.match(logged, /settings\/webpro\/staged-packages/);
417
+ assert.doesNotMatch(logged, /stage approve 71289309/);
418
+ }
419
+ });
420
+
380
421
  test('should skip checks', async () => {
381
422
  const options = { npm: { skipChecks: true } };
382
423
  const npmClient = await factory(npm, { options });