release-it 15.2.0 → 15.4.1

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/README.md CHANGED
@@ -110,6 +110,8 @@ Use `--dry-run` to show the interactivity and the commands it _would_ execute.
110
110
 
111
111
  To print the next version without releasing anything, add the `--release-version` flag.
112
112
 
113
+ To print the changelog without releasing anything, add the `--changelog` flag.
114
+
113
115
  ## Configuration
114
116
 
115
117
  Out of the box, release-it has sane defaults, and [plenty of options](./config/release-it.json) to configure it. Most
@@ -275,6 +277,7 @@ latestVersion
275
277
  changelog
276
278
  name
277
279
  repo.remote, repo.protocol, repo.host, repo.owner, repo.repository, repo.project
280
+ branchName
278
281
  ```
279
282
 
280
283
  All variables are available in all hooks. The only exception is that the additional variables listed above are not yet
package/lib/cli.js CHANGED
@@ -20,6 +20,7 @@ const helpText = `Release It! v${pkg.version}
20
20
  --only-version Prompt only for version, no further interaction
21
21
  -v --version Print release-it version number
22
22
  --release-version Print version number to be released
23
+ --changelog Print changelog for the version to be released
23
24
  -V --verbose Verbose output (user hooks output)
24
25
  -VV Extra verbose output (also internal commands output)
25
26
 
package/lib/config.js CHANGED
@@ -109,7 +109,7 @@ class Config {
109
109
  }
110
110
 
111
111
  get isCI() {
112
- return Boolean(this.options.ci) || this.isReleaseVersion;
112
+ return Boolean(this.options.ci) || this.isReleaseVersion || this.isChangelog;
113
113
  }
114
114
 
115
115
  get isPromptOnlyVersion() {
@@ -119,6 +119,10 @@ class Config {
119
119
  get isReleaseVersion() {
120
120
  return Boolean(this.options['release-version']);
121
121
  }
122
+
123
+ get isChangelog() {
124
+ return Boolean(this.options['changelog']);
125
+ }
122
126
  }
123
127
 
124
128
  export default Config;
package/lib/index.js CHANGED
@@ -78,7 +78,7 @@ const runTasks = async (opts, di) => {
78
78
  const action = config.isIncrement ? 'release' : 'update';
79
79
  const suffix = version && config.isIncrement ? `${latestVersion}...${version}` : `currently at ${latestVersion}`;
80
80
 
81
- if (!config.isReleaseVersion) {
81
+ if (!config.isReleaseVersion && !config.isChangelog) {
82
82
  log.obtrusive(`🚀 Let's ${action} ${name} (${suffix})`);
83
83
 
84
84
  log.preview({ title: 'changelog', text: changelog });
@@ -93,6 +93,11 @@ const runTasks = async (opts, di) => {
93
93
  process.exit(0);
94
94
  }
95
95
 
96
+ if (config.isChangelog) {
97
+ console.log(changelog);
98
+ process.exit(0);
99
+ }
100
+
96
101
  if (version) {
97
102
  config.setContext(parseVersion(version));
98
103
 
@@ -7,14 +7,18 @@ const changelogFallback = 'git log --pretty=format:"* %s (%h)"';
7
7
 
8
8
  class GitBase extends Plugin {
9
9
  async init() {
10
- this.remoteUrl = await this.getRemoteUrl();
11
- await this.fetch();
12
- const repo = parseGitUrl(this.remoteUrl);
13
- const latestTag = await this.getLatestTagName(repo);
10
+ const remoteUrl = await this.getRemoteUrl();
11
+ await this.fetch(remoteUrl);
12
+
13
+ const branchName = await this.getBranchName();
14
+ const repo = parseGitUrl(remoteUrl);
15
+ this.setContext({ remoteUrl, branchName, repo });
16
+ this.config.setContext({ remoteUrl, branchName, repo });
17
+
18
+ const latestTag = await this.getLatestTagName();
14
19
  const secondLatestTag = !this.config.isIncrement ? await this.getSecondLatestTagName(latestTag) : null;
15
20
  const tagTemplate = this.options.tagName || ((latestTag || '').match(/^v/) ? 'v${version}' : '${version}');
16
- this.setContext({ repo });
17
- this.config.setContext({ tagTemplate, latestTag, secondLatestTag });
21
+ this.config.setContext({ latestTag, secondLatestTag, tagTemplate });
18
22
  }
19
23
 
20
24
  getName() {
@@ -79,15 +83,15 @@ class GitBase extends Plugin {
79
83
  return this.exec(`git config --get branch.${branch}.remote`, { options }).catch(() => null);
80
84
  }
81
85
 
82
- fetch() {
86
+ fetch(remoteUrl) {
83
87
  return this.exec('git fetch').catch(err => {
84
88
  this.debug(err);
85
- throw new Error(`Unable to fetch from ${this.remoteUrl}${EOL}${err.message}`);
89
+ throw new Error(`Unable to fetch from ${remoteUrl}${EOL}${err.message}`);
86
90
  });
87
91
  }
88
92
 
89
- getLatestTagName(repo) {
90
- const context = Object.assign({ repo }, this.getContext(), { version: '*' });
93
+ getLatestTagName() {
94
+ const context = Object.assign({}, this.config.getContext(), { version: '*' });
91
95
  const match = format(this.options.tagMatch || this.options.tagName || '${version}', context);
92
96
  return this.exec(`git describe --tags --match=${match} --abbrev=0`, { options }).then(
93
97
  stdout => stdout || null,
@@ -39,7 +39,8 @@ class Git extends GitBase {
39
39
 
40
40
  await super.init();
41
41
 
42
- if (this.options.push && !this.remoteUrl) {
42
+ const remoteUrl = this.getContext('remoteUrl');
43
+ if (this.options.push && !remoteUrl) {
43
44
  throw e(`Could not get remote Git url.${EOL}Please add a remote repository.`, docs);
44
45
  }
45
46
  if (this.options.requireUpstream && !(await this.hasUpstreamBranch())) {
@@ -48,7 +49,6 @@ class Git extends GitBase {
48
49
  if (this.options.requireCommits && (await this.getCommitsSinceLatestTag()) === 0) {
49
50
  throw e(`There are no commits since the latest tag.`, docs);
50
51
  }
51
- this.config.setContext({ repo: this.getContext('repo') });
52
52
  }
53
53
 
54
54
  rollback() {
@@ -29,6 +29,7 @@ class npm extends Plugin {
29
29
  async init() {
30
30
  const { name, version: latestVersion, private: isPrivate, publishConfig } = readJSON(path.resolve(MANIFEST_PATH));
31
31
  this.setContext({ name, latestVersion, private: isPrivate, publishConfig });
32
+ this.config.setContext({ npm: { name } });
32
33
 
33
34
  const { publish, skipChecks } = this.options;
34
35
 
package/lib/util.js CHANGED
@@ -48,7 +48,10 @@ const rejectAfter = (ms, error) =>
48
48
 
49
49
  const parseGitUrl = remoteUrl => {
50
50
  if (!remoteUrl) return { host: null, owner: null, project: null, protocol: null, remote: null, repository: null };
51
- const normalizedUrl = (remoteUrl || '').replace(/^[A-Z]:/, '').replace(/\\/g, '/'); // Hacky workaround for file protocol in Windows
51
+ const normalizedUrl = (remoteUrl || '')
52
+ .replace(/^[A-Z]:\\\\/, 'file://') // Assume file protocol for Windows drive letters
53
+ .replace(/^\//, 'file://') // Assume file protocol if only /path is given
54
+ .replace(/\\+/g, '/'); // Replace forward with backslashes
52
55
  const parsedUrl = gitUrlParse(normalizedUrl);
53
56
  const { resource: host, name: project, protocol, href: remote } = parsedUrl;
54
57
  const owner = protocol === 'file' ? _.last(parsedUrl.owner.split('/')) : parsedUrl.owner; // Fix owner for file protocol
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "15.2.0",
3
+ "version": "15.4.1",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -60,21 +60,21 @@
60
60
  "license": "MIT",
61
61
  "dependencies": {
62
62
  "@iarna/toml": "2.2.5",
63
- "@octokit/rest": "19.0.3",
63
+ "@octokit/rest": "19.0.4",
64
64
  "async-retry": "1.3.3",
65
65
  "chalk": "5.0.1",
66
66
  "cosmiconfig": "7.0.1",
67
67
  "execa": "6.1.0",
68
68
  "form-data": "4.0.0",
69
- "git-url-parse": "12.0.0",
69
+ "git-url-parse": "13.0.0",
70
70
  "globby": "13.1.2",
71
- "got": "12.2.0",
72
- "inquirer": "9.0.2",
71
+ "got": "12.3.1",
72
+ "inquirer": "9.1.0",
73
73
  "is-ci": "3.0.1",
74
74
  "lodash": "4.17.21",
75
75
  "mime-types": "2.1.35",
76
76
  "new-github-release-url": "2.0.0",
77
- "node-fetch": "3.2.9",
77
+ "node-fetch": "3.2.10",
78
78
  "open": "8.4.0",
79
79
  "ora": "6.1.2",
80
80
  "os-name": "5.0.1",
@@ -85,17 +85,17 @@
85
85
  "update-notifier": "6.0.2",
86
86
  "url-join": "5.0.0",
87
87
  "wildcard-match": "5.1.2",
88
- "yargs-parser": "21.0.1"
88
+ "yargs-parser": "21.1.1"
89
89
  },
90
90
  "devDependencies": {
91
- "@octokit/request-error": "3.0.0",
92
- "ava": "4.3.1",
93
- "eslint": "8.20.0",
91
+ "@octokit/request-error": "3.0.1",
92
+ "ava": "4.3.3",
93
+ "eslint": "8.23.0",
94
94
  "eslint-config-prettier": "8.5.0",
95
95
  "eslint-plugin-ava": "13.2.0",
96
96
  "eslint-plugin-import": "2.26.0",
97
97
  "eslint-plugin-prettier": "4.2.1",
98
- "mock-fs": "5.1.2",
98
+ "mock-fs": "5.1.4",
99
99
  "mock-stdio": "1.0.3",
100
100
  "nock": "13.2.9",
101
101
  "nyc": "15.1.0",
package/test/git.js CHANGED
@@ -274,7 +274,7 @@ test.serial('should reset files', async t => {
274
274
 
275
275
  test.serial('should roll back when cancelled', async t => {
276
276
  sh.exec('git init');
277
- sh.exec(`git remote add origin foo`);
277
+ sh.exec(`git remote add origin file://foo`);
278
278
  const version = '1.2.3';
279
279
  gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
280
280
  const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true, tagName: 'v${version}' } };
@@ -294,8 +294,8 @@ test.serial('should roll back when cancelled', async t => {
294
294
  await gitClient.tag();
295
295
  await gitClient.rollbackOnce();
296
296
 
297
- t.is(exec.args[10][0], 'git tag --delete v1.2.4');
298
- t.is(exec.args[11][0], 'git reset --hard HEAD~1');
297
+ t.is(exec.args[11][0], 'git tag --delete v1.2.4');
298
+ t.is(exec.args[12][0], 'git reset --hard HEAD~1');
299
299
  });
300
300
 
301
301
  test.serial('should not touch existing history when rolling back', async t => {
package/test/github.js CHANGED
@@ -357,7 +357,7 @@ test('should not call octokit client in dry run', async t => {
357
357
  await runTasks(github);
358
358
 
359
359
  t.is(spy.get.callCount, 0);
360
- t.is(github.log.exec.args[0][0], 'octokit repos.createRelease "R 1.0.1" (v1.0.1)');
360
+ t.is(github.log.exec.args[1][0], 'octokit repos.createRelease "R 1.0.1" (v1.0.1)');
361
361
  t.is(github.log.exec.lastCall.args[0], 'octokit repos.uploadReleaseAssets');
362
362
  const { isReleased, releaseUrl } = github.getContext();
363
363
  t.true(isReleased);
package/test/gitlab.js CHANGED
@@ -231,8 +231,8 @@ test('should not make requests in dry run', async t => {
231
231
 
232
232
  const { isReleased, releaseUrl } = gitlab.getContext();
233
233
  t.is(spy.get.callCount, 0);
234
- t.is(gitlab.log.exec.args[1][0], 'gitlab releases#uploadAssets');
235
- t.is(gitlab.log.exec.args[2][0], 'gitlab releases#createRelease "R" (1.0.1)');
234
+ t.is(gitlab.log.exec.args[2][0], 'gitlab releases#uploadAssets');
235
+ t.is(gitlab.log.exec.args[3][0], 'gitlab releases#createRelease "R" (1.0.1)');
236
236
  t.true(isReleased);
237
237
  t.is(releaseUrl, `${pushRepo}/-/releases`);
238
238
  spy.get.restore();
package/test/tasks.js CHANGED
@@ -186,6 +186,49 @@ test.serial('should release all the things (basic)', async t => {
186
186
  exec.restore();
187
187
  });
188
188
 
189
+ test.serial('should release with correct tag name', async t => {
190
+ const { bare, target } = t.context;
191
+ const project = path.basename(bare);
192
+ const pkgName = path.basename(target);
193
+ const owner = path.basename(path.dirname(bare));
194
+ const { stdout } = sh.exec('git rev-parse --abbrev-ref HEAD');
195
+ const branchName = stdout.trim();
196
+ gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
197
+ sh.exec(`git tag ${pkgName}-${branchName}-1.0.0`);
198
+ const sha = gitAdd('line', 'file', 'More file');
199
+
200
+ interceptGitHubCreate({
201
+ owner,
202
+ project,
203
+ body: {
204
+ tag_name: `${pkgName}-${branchName}-1.0.1`,
205
+ name: 'Release 1.0.1',
206
+ body: `* More file (${sha})`,
207
+ prerelease: false
208
+ }
209
+ });
210
+
211
+ const container = getContainer({
212
+ git: { tagName: '${npm.name}-${branchName}-${version}' },
213
+ github: { release: true, skipChecks: true, pushRepo: `https://github.com/${owner}/${project}` }
214
+ });
215
+
216
+ const exec = sinon.spy(container.shell, 'exec');
217
+
218
+ await runTasks({}, container);
219
+
220
+ const gitArgs = getArgs(container.shell.exec.args, 'git');
221
+
222
+ t.true(gitArgs.includes(`git tag --annotate --message Release 1.0.1 ${pkgName}-${branchName}-1.0.1`));
223
+ t.true(
224
+ log.log.secondCall.args[0].endsWith(
225
+ `https://github.com/${owner}/${project}/releases/tag/${pkgName}-${branchName}-1.0.1`
226
+ )
227
+ );
228
+
229
+ exec.restore();
230
+ });
231
+
189
232
  test.serial('should release all the things (pre-release, github, gitlab)', async t => {
190
233
  const { bare, target } = t.context;
191
234
  const project = path.basename(bare);
@@ -19,6 +19,9 @@ const gitAdd = (content, file, message) => {
19
19
  };
20
20
 
21
21
  const getArgs = (args, prefix) =>
22
- args.filter(args => typeof args[0] === 'string' && args[0].startsWith(prefix)).map(args => args[0].trim());
22
+ args
23
+ .map(args => (typeof args[0] !== 'string' ? args[0].join(' ') : args[0]))
24
+ .filter(cmd => cmd.startsWith(prefix))
25
+ .map(cmd => cmd.trim());
23
26
 
24
27
  export { mkTmpDir, readFile, gitAdd, getArgs };
package/test/utils.js CHANGED
@@ -47,12 +47,39 @@ test('parseGitUrl', t => {
47
47
  repository: 'org/sub-group/repo-in-sub-group'
48
48
  });
49
49
 
50
+ t.deepEqual(parseGitUrl('git@github.com:org/example.com.git'), {
51
+ host: 'github.com',
52
+ owner: 'org',
53
+ project: 'example.com',
54
+ protocol: 'ssh',
55
+ remote: 'git@github.com:org/example.com.git',
56
+ repository: 'org/example.com'
57
+ });
58
+
59
+ t.deepEqual(parseGitUrl('file://Users/john/doe/owner/project'), {
60
+ host: 'users',
61
+ owner: 'owner',
62
+ project: 'project',
63
+ protocol: 'file',
64
+ remote: 'file://users/john/doe/owner/project',
65
+ repository: 'owner/project'
66
+ });
67
+
50
68
  t.deepEqual(parseGitUrl('/Users/john/doe/owner/project'), {
51
- host: '',
69
+ host: 'users',
70
+ owner: 'owner',
71
+ project: 'project',
72
+ protocol: 'file',
73
+ remote: 'file://users/john/doe/owner/project',
74
+ repository: 'owner/project'
75
+ });
76
+
77
+ t.deepEqual(parseGitUrl('C:\\\\Users\\john\\doe\\owner\\project'), {
78
+ host: 'users',
52
79
  owner: 'owner',
53
80
  project: 'project',
54
81
  protocol: 'file',
55
- remote: '/Users/john/doe/owner/project',
82
+ remote: 'file://users/john/doe/owner/project',
56
83
  repository: 'owner/project'
57
84
  });
58
85
  });