release-it 19.2.4 → 20.0.0-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/lib/log.js CHANGED
@@ -24,7 +24,7 @@ class Logger {
24
24
  }
25
25
 
26
26
  info(...args) {
27
- console.error(styleText('grey', args.join(' ')));
27
+ console.error(styleText('gray', args.join(' ')));
28
28
  }
29
29
 
30
30
  warn(...args) {
@@ -22,7 +22,7 @@ const plugins = {
22
22
  };
23
23
 
24
24
  const load = async pluginName => {
25
- let plugin = null;
25
+ let plugin;
26
26
  try {
27
27
  const module = await import(pluginName);
28
28
  plugin = module.default;
@@ -209,6 +209,13 @@ class Git extends GitBase {
209
209
  this.disableRollback();
210
210
  return push;
211
211
  } catch (error) {
212
+ if (this.config.getContext('isReleased')) {
213
+ this.disableRollback();
214
+ this.log.error(error.message || error);
215
+ this.log.warn('The package might have been released, but git push failed.');
216
+ return;
217
+ }
218
+
212
219
  try {
213
220
  await this.rollbackTagPush();
214
221
  } catch (tagError) {
@@ -453,7 +453,7 @@ class GitHub extends Release {
453
453
  const { commit: template, excludeMatches = [] } = releaseNotes;
454
454
  const commits = await this.getCommits();
455
455
 
456
- if (this.options.commit) commits.pop();
456
+ if (this.options.commit || !this.config.isIncrement) commits.pop();
457
457
 
458
458
  return commits
459
459
  .map(commit => {
@@ -273,6 +273,7 @@ class npm extends Plugin {
273
273
  })
274
274
  .then(() => {
275
275
  this.setContext({ isReleased: true });
276
+ this.config.setContext({ isReleased: true });
276
277
  })
277
278
  .catch(err => {
278
279
  this.debug(err);
package/lib/prompt.js CHANGED
@@ -1,8 +1,10 @@
1
- import inquirer from 'inquirer';
1
+ import { confirm, input, select } from '@inquirer/prompts';
2
+
3
+ const types = { confirm, input, list: select };
2
4
 
3
5
  class Prompt {
4
6
  constructor({ container }) {
5
- this.createPrompt = (container.inquirer || inquirer).prompt;
7
+ this.createPrompt = container.createPrompt;
6
8
  this.prompts = {};
7
9
  }
8
10
 
@@ -15,18 +17,23 @@ class Prompt {
15
17
  if (!enabled) return false;
16
18
 
17
19
  const prompt = this.prompts[namespace][promptName];
18
- const options = Object.assign({}, prompt, {
19
- name: promptName,
20
- message: prompt.message(context),
21
- choices: 'choices' in prompt && prompt.choices(context),
22
- transformer: 'transformer' in prompt ? prompt.transformer(context) : undefined
23
- });
20
+ const options = {
21
+ message: prompt.message(context)
22
+ };
23
+
24
+ if ('default' in prompt) options.default = prompt.default;
25
+ if ('choices' in prompt) options.choices = prompt.choices(context);
26
+ if ('transformer' in prompt) options.transformer = prompt.transformer(context);
27
+ if ('validate' in prompt) options.validate = prompt.validate;
28
+ if ('pageSize' in prompt) options.pageSize = prompt.pageSize;
24
29
 
25
- const answers = await this.createPrompt([options]);
30
+ const answer = await (this.createPrompt
31
+ ? this.createPrompt(prompt.type, options)
32
+ : types[prompt.type](options));
26
33
 
27
- const doExecute = prompt.type === 'confirm' ? answers[promptName] : true;
34
+ const doExecute = prompt.type === 'confirm' ? answer : true;
28
35
 
29
- return doExecute && task ? await task(answers[promptName]) : false;
36
+ return doExecute && task ? await task(answer) : false;
30
37
  }
31
38
  }
32
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "19.2.4",
3
+ "version": "20.0.0-0",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -78,46 +78,46 @@
78
78
  },
79
79
  "license": "MIT",
80
80
  "dependencies": {
81
+ "@inquirer/prompts": "8.3.2",
81
82
  "@nodeutils/defaults-deep": "1.1.0",
82
83
  "@octokit/rest": "22.0.1",
83
84
  "@phun-ky/typeof": "2.0.3",
84
85
  "async-retry": "1.3.3",
85
86
  "c12": "3.3.3",
86
- "ci-info": "^4.3.1",
87
- "eta": "4.5.0",
87
+ "ci-info": "^4.4.0",
88
+ "eta": "4.5.1",
88
89
  "git-url-parse": "16.1.0",
89
- "inquirer": "12.11.1",
90
90
  "issue-parser": "7.0.1",
91
91
  "lodash.merge": "4.6.2",
92
92
  "mime-types": "3.0.2",
93
93
  "new-github-release-url": "2.0.0",
94
- "open": "10.2.0",
95
- "ora": "9.0.0",
96
- "os-name": "6.1.0",
97
- "proxy-agent": "6.5.0",
98
- "semver": "7.7.3",
94
+ "open": "11.0.0",
95
+ "ora": "9.3.0",
96
+ "os-name": "7.0.0",
97
+ "proxy-agent": "7.0.0",
98
+ "semver": "7.7.4",
99
99
  "tinyglobby": "0.2.15",
100
- "undici": "6.23.0",
100
+ "undici": "7.24.5",
101
101
  "url-join": "5.0.0",
102
102
  "wildcard-match": "5.1.4",
103
- "yargs-parser": "21.1.1"
103
+ "yargs-parser": "22.0.0"
104
104
  },
105
105
  "devDependencies": {
106
- "@eslint/js": "9.39.2",
106
+ "@eslint/js": "10.0.1",
107
107
  "@octokit/request-error": "7.1.0",
108
108
  "@types/node": "24.10.9",
109
- "eslint": "9.39.2",
110
- "eslint-plugin-import-x": "4.16.1",
111
- "globals": "16.5.0",
112
- "installed-check": "9.3.0",
113
- "knip": "5.82.1",
109
+ "eslint": "10.1.0",
110
+ "eslint-plugin-import-x": "4.16.2",
111
+ "globals": "17.4.0",
112
+ "installed-check": "10.0.1",
113
+ "knip": "6.0.5",
114
114
  "mentoss": "0.13.0",
115
115
  "mock-stdio": "1.0.3",
116
- "prettier": "3.8.0",
116
+ "prettier": "3.8.1",
117
117
  "remark-cli": "12.0.1",
118
- "remark-preset-webpro": "2.1.0",
119
- "tar": "7.5.4",
120
- "typescript": "5.9.3"
118
+ "remark-preset-webpro": "2.1.1",
119
+ "tar": "7.5.13",
120
+ "typescript": "6.0.2"
121
121
  },
122
122
  "overrides": {
123
123
  "pac-resolver": "7.0.1",
@@ -125,7 +125,7 @@
125
125
  "glob": "13.0.0"
126
126
  },
127
127
  "engines": {
128
- "node": "^20.12.0 || >=22.0.0"
128
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
129
129
  },
130
130
  "remarkConfig": {
131
131
  "plugins": [
package/test/git.js CHANGED
@@ -348,6 +348,39 @@ describe('git', () => {
348
348
  assert.equal(exec.mock.calls[15].arguments[0], 'git push origin --delete v1.2.4');
349
349
  });
350
350
 
351
+ test('should skip rollback when push fails but package is already published', async t => {
352
+ childProcess.execSync('git init', execOpts);
353
+ childProcess.execSync(`git remote add origin file://foo`, execOpts);
354
+ sh.exec(`git remote update`, execOpts);
355
+ const version = '1.2.3';
356
+ gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
357
+ const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true, tagName: 'v${version}' } };
358
+ const gitClient = await factory(Git, { options });
359
+ const exec = t.mock.method(gitClient.shell, 'execFormattedCommand');
360
+ const warn = t.mock.method(gitClient.log, 'warn');
361
+ sh.exec(`git push`, execOpts);
362
+ sh.exec(`git checkout HEAD~1`, execOpts);
363
+ gitAdd('line', 'file', 'Add file');
364
+
365
+ await gitClient.init();
366
+
367
+ childProcess.execSync('npm --no-git-tag-version version patch', execOpts);
368
+
369
+ gitClient.bump('1.2.4');
370
+ await gitClient.beforeRelease();
371
+ await gitClient.stage('package.json');
372
+ await gitClient.commit({ message: 'Add this' });
373
+ await gitClient.tag();
374
+
375
+ gitClient.config.setContext({ isReleased: true });
376
+
377
+ await gitClient.push();
378
+
379
+ const execCalls = exec.mock.calls.map(c => c.arguments[0]);
380
+ assert.equal(execCalls.includes('git push origin --delete v1.2.4'), false);
381
+ assert(warn.mock.calls.some(c => /git push failed/.test(c.arguments[0])));
382
+ });
383
+
351
384
  test('should not touch existing history when rolling back', async t => {
352
385
  childProcess.execSync('git init', execOpts);
353
386
  const version = '1.2.3';
package/test/log.js CHANGED
@@ -24,6 +24,14 @@ describe('log', () => {
24
24
  assert.equal(stripVTControlCharacters(stderr), 'ERROR foo\n');
25
25
  });
26
26
 
27
+ test('should print info', () => {
28
+ const log = new Log();
29
+ mockStdIo.start();
30
+ log.info('foo');
31
+ const { stderr } = mockStdIo.end();
32
+ assert.equal(stripVTControlCharacters(stderr), 'foo\n');
33
+ });
34
+
27
35
  test('should print a warning', () => {
28
36
  const log = new Log();
29
37
  mockStdIo.start();
package/test/prompt.js CHANGED
@@ -10,33 +10,28 @@ import { factory } from './util/index.js';
10
10
 
11
11
  const prompts = { git, github, gitlab, npm };
12
12
 
13
- const yes = ([options]) => Promise.resolve({ [options.name]: true });
14
- const no = ([options]) => Promise.resolve({ [options.name]: false });
13
+ const yes = () => Promise.resolve(true);
14
+ const no = () => Promise.resolve(false);
15
15
 
16
16
  test('should not create prompt if disabled', async t => {
17
17
  const task = t.mock.fn();
18
- const promptMock = t.mock.fn(yes);
19
- const inquirer = { prompt: promptMock };
20
- const prompt = await factory(Prompt, { container: { inquirer } });
18
+ const createPrompt = t.mock.fn(yes);
19
+ const prompt = await factory(Prompt, { container: { createPrompt } });
21
20
  prompt.register(prompts.git);
22
21
  await prompt.show({ enabled: false, prompt: 'push', task });
23
- assert.equal(promptMock.mock.callCount(), 0);
22
+ assert.equal(createPrompt.mock.callCount(), 0);
24
23
  assert.equal(task.mock.callCount(), 0);
25
24
  });
26
25
 
27
26
  test('should create prompt', async t => {
28
- const promptMock = t.mock.fn(yes);
29
- const inquirer = { prompt: promptMock };
30
- const prompt = await factory(Prompt, { container: { inquirer } });
27
+ const createPrompt = t.mock.fn(yes);
28
+ const prompt = await factory(Prompt, { container: { createPrompt } });
31
29
  prompt.register(prompts.git);
32
30
  await prompt.show({ prompt: 'push' });
33
- assert.equal(promptMock.mock.callCount(), 1);
34
- assert.deepEqual(promptMock.mock.calls[0].arguments[0][0], {
35
- type: 'confirm',
31
+ assert.equal(createPrompt.mock.callCount(), 1);
32
+ assert.equal(createPrompt.mock.calls[0].arguments[0], 'confirm');
33
+ assert.deepEqual(createPrompt.mock.calls[0].arguments[1], {
36
34
  message: 'Push?',
37
- name: 'push',
38
- choices: false,
39
- transformer: undefined,
40
35
  default: true
41
36
  });
42
37
  });
@@ -51,7 +46,7 @@ test('should create prompt', async t => {
51
46
  ['npm', 'otp', 'Please enter OTP for npm:']
52
47
  ].map(async ([namespace, prompt, message]) => {
53
48
  test(`should create prompt and render template message (${namespace}.${prompt})`, async t => {
54
- const promptMock = t.mock.fn(yes);
49
+ const createPrompt = t.mock.fn(yes);
55
50
  const config = new Config({
56
51
  isPreRelease: true,
57
52
  git: { tagName: 'v${version}' },
@@ -59,34 +54,31 @@ test('should create prompt', async t => {
59
54
  });
60
55
  await config.init();
61
56
  config.setContext({ version: '1.0.0', tagName: '1.0.0' });
62
- const inquirer = { prompt: promptMock };
63
- const p = await factory(Prompt, { container: { inquirer } });
57
+ const p = await factory(Prompt, { container: { createPrompt } });
64
58
  p.register(prompts[namespace], namespace);
65
59
  await p.show({ namespace, prompt, context: config.getContext() });
66
- assert.equal(promptMock.mock.callCount(), 1);
67
- assert.equal(promptMock.mock.calls[0].arguments[0][0].message, message);
60
+ assert.equal(createPrompt.mock.callCount(), 1);
61
+ assert.equal(createPrompt.mock.calls[0].arguments[1].message, message);
68
62
  });
69
63
  });
70
64
 
71
65
  test('should execute task after positive answer', async t => {
72
66
  const task = t.mock.fn();
73
- const promptMock = t.mock.fn(yes);
74
- const inquirer = { prompt: promptMock };
75
- const prompt = await factory(Prompt, { container: { inquirer } });
67
+ const createPrompt = t.mock.fn(yes);
68
+ const prompt = await factory(Prompt, { container: { createPrompt } });
76
69
  prompt.register(prompts.git);
77
70
  await prompt.show({ prompt: 'push', task });
78
- assert.equal(promptMock.mock.callCount(), 1);
71
+ assert.equal(createPrompt.mock.callCount(), 1);
79
72
  assert.equal(task.mock.callCount(), 1);
80
73
  assert.equal(task.mock.calls[0].arguments[0], true);
81
74
  });
82
75
 
83
76
  test('should not execute task after negative answer', async t => {
84
77
  const task = t.mock.fn();
85
- const promptMock = t.mock.fn(no);
86
- const inquirer = { prompt: promptMock };
87
- const prompt = await factory(Prompt, { container: { inquirer } });
78
+ const createPrompt = t.mock.fn(no);
79
+ const prompt = await factory(Prompt, { container: { createPrompt } });
88
80
  prompt.register(prompts.git);
89
81
  await prompt.show({ prompt: 'push', task });
90
- assert.equal(promptMock.mock.callCount(), 1);
82
+ assert.equal(createPrompt.mock.callCount(), 1);
91
83
  assert.equal(task.mock.callCount(), 0);
92
84
  });
@@ -25,7 +25,7 @@ describe('tasks.interactive', () => {
25
25
 
26
26
  afterEach(() => {
27
27
  mocker.clearAll();
28
- prompt.mock.resetCalls();
28
+ createPrompt.mock.resetCalls();
29
29
  log.resetCalls();
30
30
  });
31
31
 
@@ -54,17 +54,18 @@ describe('tasks.interactive', () => {
54
54
  const log = new LogStub();
55
55
  const spinner = new SpinnerStub();
56
56
 
57
- const prompt = mock.fn(([options]) => {
58
- const answer = options.type === 'list' ? options.choices[0].value : options.name === 'version' ? '0.0.1' : true;
59
- return { [options.name]: answer };
57
+ const createPrompt = mock.fn((type, options) => {
58
+ if (type === 'list') return options.choices[0].value;
59
+ if (type === 'input') return '0.0.1';
60
+ return true;
60
61
  });
61
62
 
62
- const defaultInquirer = { prompt };
63
+ const defaultCreatePrompt = createPrompt;
63
64
 
64
- const getContainer = (options, inquirer = defaultInquirer) => {
65
+ const getContainer = (options, promptFn = defaultCreatePrompt) => {
65
66
  const config = new Config(Object.assign({}, testConfig, options));
66
67
  const shell = new ShellStub({ container: { log, config } });
67
- const prompt = new Prompt({ container: { inquirer } });
68
+ const prompt = new Prompt({ container: { createPrompt: promptFn } });
68
69
  return { log, spinner, config, shell, prompt };
69
70
  };
70
71
 
@@ -181,8 +182,7 @@ describe('tasks.interactive', () => {
181
182
  childProcess.execSync('git tag 1.0.0', execOpts);
182
183
 
183
184
  const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
184
- const prompt = mock.fn(([options]) => ({ [options.name]: false }));
185
- const inquirer = { prompt };
185
+ const rejectAll = mock.fn(() => false);
186
186
 
187
187
  const container = getContainer(
188
188
  {
@@ -192,7 +192,7 @@ describe('tasks.interactive', () => {
192
192
  gitlab: { release: true, skipChecks: true },
193
193
  npm: { publish: true, skipChecks: true }
194
194
  },
195
- inquirer
195
+ rejectAll
196
196
  );
197
197
 
198
198
  const exec = t.mock.method(container.shell, 'execFormattedCommand');
@@ -268,7 +268,7 @@ describe('tasks.interactive', () => {
268
268
  test('should show only version prompt', async () => {
269
269
  const config = { ci: false, 'only-version': true };
270
270
  await runTasks({}, getContainer(config));
271
- assert.equal(prompt.mock.callCount(), 1);
272
- assert.equal(prompt.mock.calls[0].arguments[0][0].name, 'incrementList');
271
+ assert.equal(createPrompt.mock.callCount(), 1);
272
+ assert.equal(createPrompt.mock.calls[0].arguments[0], 'list');
273
273
  });
274
274
  });
package/test/tasks.js CHANGED
@@ -34,7 +34,7 @@ describe('tasks', () => {
34
34
  'https://gitlab.com/api/v4'
35
35
  ]);
36
36
 
37
- const npmMajorVersion = semver.major(process.env.npm_config_user_agent.match(/npm\/([^ ]+)/)[1]);
37
+ const npmMajorVersion = semver.major(process.env.npm_config_user_agent?.match(/npm\/([^ ]+)/)?.[1] ?? '10.0.0');
38
38
 
39
39
  const testConfig = {
40
40
  ci: true,