release-it 15.0.0-esm.0 → 15.0.0-esm.4

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
@@ -156,19 +156,18 @@ remote.
156
156
 
157
157
  ## GitHub Releases
158
158
 
159
- The "Releases" tab on GitHub projects links to a page to store the changelog cq. release notes. To add
159
+ GitHub projects can have releases attached to Git tags, containing release notes and assets. There are two ways to add
160
160
  [GitHub releases](https://help.github.com/articles/creating-releases) in your release-it flow:
161
161
 
162
- - Configure `github.release: true`
163
- - Obtain a [personal access token](https://github.com/settings/tokens/new?scopes=repo&description=release-it)
164
- (release-it only needs "repo" access; no "admin" or other scopes).
165
- - Make sure the token is [available as an environment variable](./docs/environment-variables.md).
162
+ 1. Automated (requires a `GITHUB_TOKEN`)
163
+ 2. Manual (using the GitHub web interface with pre-populated fields)
166
164
 
167
165
  → See [GitHub Releases](./docs/github-releases.md) for more details.
168
166
 
169
167
  ## GitLab Releases
170
168
 
171
- [GitLab releases](https://docs.gitlab.com/ce/user/project/releases/) work just like GitHub releases:
169
+ GitLab projects can have releases attached to Git tags, containing release notes and assets. To automate
170
+ [GitLab releases](https://docs.gitlab.com/ce/user/project/releases/):
172
171
 
173
172
  - Configure `gitlab.release: true`
174
173
  - Obtain a [personal access token](https://gitlab.com/profile/personal_access_tokens) (release-it only needs the "api"
@@ -210,11 +209,11 @@ pre-releases. An example pre-release version is `2.0.0-beta.0`.
210
209
 
211
210
  ## Update or re-run existing releases
212
211
 
213
- Use `--no-increment` to not increment the lastest version and update an existing tag/version.
212
+ Use `--no-increment` to not increment the last version, but update the last existing tag/version.
214
213
 
215
- This may be helpful in some cases where the version was already incremented. Here's a few example scenarios:
214
+ This may be helpful in cases where the version was already incremented. Here's a few example scenarios:
216
215
 
217
- - To update or publish a draft GitHub Release for an existing Git tag.
216
+ - To update or publish a (draft) GitHub Release for an existing Git tag.
218
217
  - Publishing to npm succeeded, but pushing the Git tag to the remote failed. Then use
219
218
  `release-it --no-increment --no-npm` to skip the `npm publish` and try pushing the same Git tag again.
220
219
 
@@ -322,17 +321,18 @@ While mostly used as a CLI tool, release-it can be used as a dependency to integ
322
321
  - [blockchain/blockchain-wallet-v4-frontend](https://github.com/blockchain/blockchain-wallet-v4-frontend)
323
322
  - [callstack/linaria](https://github.com/callstack/linaria)
324
323
  - [ember-cli/ember-cli](https://github.com/ember-cli/ember-cli)
325
- [react-native-paper](https://github.com/callstack/react-native-paper)
324
+ - [metalsmith/metalsmith](https://github.com/metalsmith/metalsmith)
325
+ - [react-native-paper](https://github.com/callstack/react-native-paper)
326
326
  - [js-cookie/js-cookie](https://github.com/js-cookie/js-cookie)
327
327
  - [mirumee/saleor](https://github.com/mirumee/saleor)
328
328
  - [mozilla/readability](https://github.com/mozilla/readability)
329
- - [satya164/react-native-tab-view](https://github.com/satya164/react-native-tab-view)
329
+ - [redis/node-redis](https://github.com/redis/node-redis)
330
330
  - [shipshapecode/shepherd](https://github.com/shipshapecode/shepherd)
331
331
  - [swagger-api/swagger-ui](https://github.com/swagger-api/swagger-ui) +
332
332
  [swagger-editor](https://github.com/swagger-api/swagger-editor)
333
333
  - [StevenBlack/hosts](https://github.com/StevenBlack/hosts)
334
- - [tabler](https://github.com/tabler/tabler) + [tabler-icons](https://github.com/tabler/tabler-icons)
335
- - [youzan/vant](https://github.com/youzan/vant/search?q=release-it)
334
+ - [tabler/tabler](https://github.com/tabler/tabler) + [tabler-icons](https://github.com/tabler/tabler-icons)
335
+ - [youzan/vant](https://github.com/youzan/vant)
336
336
  - [Repositories that depend on release-it](https://github.com/release-it/release-it/network/dependents)
337
337
  - GitHub search for [filename:.release-it.json](https://github.com/search?q=filename%3A.release-it.json)
338
338
 
package/bin/release-it.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import updater from 'update-notifier';
4
4
  import parseArgs from 'yargs-parser';
5
- import release from '../lib/index.js';
5
+ import release from '../lib/cli.js';
6
6
  import { readJSON } from '../lib/util.js';
7
7
 
8
8
  const pkg = readJSON(new URL('../package.json', import.meta.url));
@@ -12,6 +12,7 @@
12
12
  "commitArgs": [],
13
13
  "tag": true,
14
14
  "tagName": null,
15
+ "tagMatch": null,
15
16
  "tagAnnotation": "Release ${version}",
16
17
  "tagArgs": [],
17
18
  "push": true,
@@ -32,6 +33,7 @@
32
33
  "release": false,
33
34
  "releaseName": "Release ${version}",
34
35
  "releaseNotes": null,
36
+ "autoGenerate": false,
35
37
  "preRelease": false,
36
38
  "draft": false,
37
39
  "tokenRef": "GITHUB_TOKEN",
@@ -39,7 +41,8 @@
39
41
  "host": null,
40
42
  "timeout": 0,
41
43
  "proxy": null,
42
- "skipChecks": false
44
+ "skipChecks": false,
45
+ "web": false
43
46
  },
44
47
  "gitlab": {
45
48
  "release": false,
package/lib/cli.js CHANGED
@@ -1,5 +1,6 @@
1
- import { readJSON } from '../lib/util.js';
1
+ import { readJSON } from './util.js';
2
2
  import Log from './log.js';
3
+ import runTasks from './index.js';
3
4
 
4
5
  const pkg = readJSON(new URL('../package.json', import.meta.url));
5
6
 
@@ -27,3 +28,14 @@ For more details, please see https://github.com/release-it/release-it`;
27
28
  export let version = () => log.log(`v${pkg.version}`);
28
29
 
29
30
  export let help = () => log.log(helpText);
31
+
32
+ export default async options => {
33
+ if (options.version) {
34
+ version();
35
+ } else if (options.help) {
36
+ help();
37
+ } else {
38
+ return runTasks(options);
39
+ }
40
+ return Promise.resolve();
41
+ };
package/lib/config.js CHANGED
@@ -54,7 +54,6 @@ class Config {
54
54
 
55
55
  expandPreReleaseShorthand(options) {
56
56
  const { increment, preRelease, preReleaseId } = options;
57
- options.isUpdate = increment === false;
58
57
  options.version = {
59
58
  increment,
60
59
  isPreRelease: Boolean(preRelease),
@@ -97,8 +96,8 @@ class Config {
97
96
  return Boolean(this.options['dry-run']);
98
97
  }
99
98
 
100
- get isUpdate() {
101
- return this.options.increment === false;
99
+ get isIncrement() {
100
+ return this.options.increment !== false;
102
101
  }
103
102
 
104
103
  get isVerbose() {
package/lib/index.js CHANGED
@@ -1,16 +1,142 @@
1
- import { version, help } from './cli.js';
2
- import runTasks from './tasks.js';
1
+ import _ from 'lodash';
2
+ import { getPlugins } from './plugin/factory.js';
3
+ import Logger from './log.js';
4
+ import Config from './config.js';
5
+ import Shell from './shell.js';
6
+ import Prompt from './prompt.js';
7
+ import Spinner from './spinner.js';
8
+ import Metrics from './metrics.js';
9
+ import { reduceUntil, parseVersion } from './util.js';
10
+ import handleDeprecated from './deprecated.js';
3
11
  import Plugin from './plugin/Plugin.js';
4
12
 
5
- export default async options => {
6
- if (options.version) {
7
- version();
8
- } else if (options.help) {
9
- help();
10
- } else {
11
- return runTasks(options);
13
+ const runTasks = async (opts, di) => {
14
+ let container = {};
15
+
16
+ try {
17
+ Object.assign(container, di);
18
+ container.config = container.config || new Config(opts);
19
+
20
+ const { config } = container;
21
+ const { isCI, isVerbose, verbosityLevel, isDryRun } = config;
22
+
23
+ container.log = container.log || new Logger({ isCI, isVerbose, verbosityLevel, isDryRun });
24
+ container.spinner = container.spinner || new Spinner({ container, config });
25
+ container.prompt = container.prompt || new Prompt({ container: { config } });
26
+ container.metrics = new Metrics({ isEnabled: config.isCollectMetrics });
27
+ container.shell = container.shell || new Shell({ container });
28
+
29
+ const { log, metrics, shell, spinner } = container;
30
+
31
+ const options = handleDeprecated(config.getContext(), log);
32
+
33
+ metrics.trackEvent('start', options);
34
+
35
+ const { hooks } = options;
36
+
37
+ const runHook = async (...name) => {
38
+ const scripts = hooks[name.join(':')];
39
+ if (!scripts || !scripts.length) return;
40
+ const context = config.getContext();
41
+ const external = true;
42
+ for (const script of _.castArray(scripts)) {
43
+ const task = () => shell.exec(script, { external }, context);
44
+ await spinner.show({ task, label: script, context, external });
45
+ }
46
+ };
47
+
48
+ const runLifeCycleHook = async (plugin, name, ...args) => {
49
+ if (plugin === _.first(plugins)) await runHook('before', name);
50
+ await runHook('before', plugin.namespace, name);
51
+ const willHookRun = (await plugin[name](...args)) !== false;
52
+ if (willHookRun) {
53
+ await runHook('after', plugin.namespace, name);
54
+ }
55
+ if (plugin === _.last(plugins)) await runHook('after', name);
56
+ };
57
+
58
+ const [internal, external] = await getPlugins(config, container);
59
+ let plugins = [...external, ...internal];
60
+
61
+ for (const plugin of plugins) {
62
+ await runLifeCycleHook(plugin, 'init');
63
+ }
64
+
65
+ const { increment, isPreRelease, preReleaseId } = options.version;
66
+
67
+ const name = await reduceUntil(plugins, plugin => plugin.getName());
68
+ const latestVersion = (await reduceUntil(plugins, plugin => plugin.getLatestVersion())) || '0.0.0';
69
+ const changelog = await reduceUntil(plugins, plugin => plugin.getChangelog(latestVersion));
70
+
71
+ const incrementBase = { latestVersion, increment, isPreRelease, preReleaseId };
72
+
73
+ let version;
74
+ if (config.isIncrement) {
75
+ incrementBase.increment = await reduceUntil(plugins, plugin => plugin.getIncrement(incrementBase));
76
+ version = await reduceUntil(plugins, plugin => plugin.getIncrementedVersionCI(incrementBase));
77
+ } else {
78
+ version = latestVersion;
79
+ }
80
+
81
+ if (config.isReleaseVersion) {
82
+ log.log(version);
83
+ process.exit(0);
84
+ }
85
+
86
+ config.setContext({ name, latestVersion, version, changelog });
87
+
88
+ const action = config.isIncrement ? 'release' : 'update';
89
+ const suffix = version && config.isIncrement ? `${latestVersion}...${version}` : `currently at ${latestVersion}`;
90
+
91
+ log.obtrusive(`🚀 Let's ${action} ${name} (${suffix})`);
92
+
93
+ log.preview({ title: 'changelog', text: changelog });
94
+
95
+ if (config.isIncrement) {
96
+ version = version || (await reduceUntil(plugins, plugin => plugin.getIncrementedVersion(incrementBase)));
97
+ }
98
+
99
+ config.setContext(parseVersion(version));
100
+
101
+ if (config.isPromptOnlyVersion) {
102
+ config.setCI(true);
103
+ }
104
+
105
+ for (const hook of ['beforeBump', 'bump', 'beforeRelease']) {
106
+ for (const plugin of plugins) {
107
+ const args = hook === 'bump' ? [version] : [];
108
+ await runLifeCycleHook(plugin, hook, ...args);
109
+ }
110
+ }
111
+
112
+ plugins = [...internal, ...external];
113
+
114
+ for (const hook of ['release', 'afterRelease']) {
115
+ for (const plugin of plugins) {
116
+ await runLifeCycleHook(plugin, hook);
117
+ }
118
+ }
119
+
120
+ await metrics.trackEvent('end');
121
+
122
+ log.log(`🏁 Done (in ${Math.floor(process.uptime())}s.)`);
123
+
124
+ return {
125
+ name,
126
+ changelog,
127
+ latestVersion,
128
+ version
129
+ };
130
+ } catch (err) {
131
+ const { log, metrics } = container;
132
+ if (metrics) {
133
+ await metrics.trackException(err);
134
+ }
135
+ log ? log.error(err.message || err) : console.error(err); // eslint-disable-line no-console
136
+ throw err;
12
137
  }
13
- return Promise.resolve();
14
138
  };
15
139
 
140
+ export default runTasks;
141
+
16
142
  export { Plugin };
@@ -10,11 +10,11 @@ class GitBase extends Plugin {
10
10
  this.remoteUrl = await this.getRemoteUrl();
11
11
  await this.fetch();
12
12
  const repo = parseGitUrl(this.remoteUrl);
13
- const latestTagName = await this.getLatestTagName(repo);
14
- const secondLatestTagName = this.config.isUpdate ? await this.getSecondLatestTagName(latestTagName) : null;
15
- const tagTemplate = this.options.tagName || ((latestTagName || '').match(/^v/) ? 'v${version}' : '${version}');
16
- this.setContext({ repo, tagTemplate, latestTagName, secondLatestTagName });
17
- this.config.setContext({ latestTag: latestTagName });
13
+ const latestTag = await this.getLatestTagName(repo);
14
+ const secondLatestTag = !this.config.isIncrement ? await this.getSecondLatestTagName(latestTag) : null;
15
+ const tagTemplate = this.options.tagName || ((latestTag || '').match(/^v/) ? 'v${version}' : '${version}');
16
+ this.setContext({ repo });
17
+ this.config.setContext({ tagTemplate, latestTag, secondLatestTag });
18
18
  }
19
19
 
20
20
  getName() {
@@ -22,19 +22,20 @@ class GitBase extends Plugin {
22
22
  }
23
23
 
24
24
  getLatestVersion() {
25
- const latestTagName = this.getContext('latestTagName');
26
- return latestTagName ? latestTagName.replace(/^v/, '') : null;
25
+ const { tagTemplate, latestTag } = this.config.getContext();
26
+ const prefix = tagTemplate.replace(/\$\{version\}/, '');
27
+ return latestTag ? latestTag.replace(prefix, '').replace(/^v/, '') : null;
27
28
  }
28
29
 
29
30
  async getChangelog() {
30
- const { isUpdate, latestTagName, secondLatestTagName } = this.getContext();
31
- const context = { latestTag: latestTagName, from: latestTagName, to: 'HEAD' };
31
+ const { latestTag, secondLatestTag } = this.config.getContext();
32
+ const context = { latestTag, from: latestTag, to: 'HEAD' };
32
33
  const { changelog } = this.options;
33
34
  if (!changelog) return null;
34
35
 
35
- if (latestTagName && isUpdate) {
36
- context.from = secondLatestTagName;
37
- context.to = `${latestTagName}^1`;
36
+ if (latestTag && !this.config.isIncrement) {
37
+ context.from = secondLatestTag;
38
+ context.to = `${latestTag}^1`;
38
39
  }
39
40
 
40
41
  if (!context.from && changelog.includes('${from}')) {
@@ -45,10 +46,10 @@ class GitBase extends Plugin {
45
46
  }
46
47
 
47
48
  bump(version) {
48
- const { tagTemplate } = this.getContext();
49
+ const { tagTemplate } = this.config.getContext();
49
50
  const context = Object.assign(this.config.getContext(), { version });
50
51
  const tagName = format(tagTemplate, context) || version;
51
- this.setContext({ version, tagName });
52
+ this.setContext({ version });
52
53
  this.config.setContext({ tagName });
53
54
  }
54
55
 
@@ -87,7 +88,7 @@ class GitBase extends Plugin {
87
88
 
88
89
  getLatestTagName(repo) {
89
90
  const context = Object.assign({ repo }, this.getContext(), { version: '*' });
90
- const match = format(this.options.tagName || '${version}', context);
91
+ const match = format(this.options.tagMatch || this.options.tagName || '${version}', context);
91
92
  return this.exec(`git describe --tags --match=${match} --abbrev=0`, { options }).then(
92
93
  stdout => stdout || null,
93
94
  () => null
@@ -98,7 +99,7 @@ class GitBase extends Plugin {
98
99
  const sha = await this.exec(`git rev-list ${latestTag || '--skip=1'} --tags --max-count=1`, {
99
100
  options
100
101
  });
101
- return this.exec(`git describe --tags --abbrev=0 ${sha}`, { options }).catch(() => null);
102
+ return this.exec(`git describe --tags --abbrev=0 "${sha}^"`, { options }).catch(() => null);
102
103
  }
103
104
  }
104
105
 
@@ -1,3 +1,4 @@
1
+ import url from 'url';
1
2
  import path from 'path';
2
3
  import { createRequire } from 'module';
3
4
  import _ from 'lodash';
@@ -8,8 +9,6 @@ import GitLab from './gitlab/GitLab.js';
8
9
  import GitHub from './github/GitHub.js';
9
10
  import npm from './npm/npm.js';
10
11
 
11
- const require = createRequire(import.meta.url);
12
-
13
12
  const debug = _debug('release-it:plugins');
14
13
 
15
14
  const pluginNames = ['npm', 'git', 'github', 'gitlab', 'version'];
@@ -25,11 +24,18 @@ const plugins = {
25
24
  const load = async pluginName => {
26
25
  let plugin = null;
27
26
  try {
28
- const module = await import(require.resolve(pluginName));
27
+ const module = await import(pluginName);
29
28
  plugin = module.default;
30
29
  } catch (err) {
31
- const module = await import(require.resolve(path.resolve(pluginName)));
32
- plugin = module.default;
30
+ try {
31
+ const module = await import(path.join(process.cwd(), pluginName));
32
+ plugin = module.default;
33
+ } catch (err) {
34
+ // In some cases or tests we might need to support legacy `require.resolve`
35
+ const require = createRequire(process.cwd());
36
+ const module = await import(url.pathToFileURL(require.resolve(pluginName, { paths: [process.cwd()] })));
37
+ plugin = module.default;
38
+ }
33
39
  }
34
40
  return [path.parse(pluginName).name, plugin];
35
41
  };
@@ -1,6 +1,6 @@
1
1
  import { EOL } from 'os';
2
2
  import _ from 'lodash';
3
- import findUp from 'find-up';
3
+ import { execa } from 'execa';
4
4
  import { format, e } from '../../util.js';
5
5
  import GitBase from '../GitBase.js';
6
6
  import prompts from './prompts.js';
@@ -12,6 +12,12 @@ const fixArgs = args => (args ? (typeof args === 'string' ? args.split(' ') : ar
12
12
 
13
13
  const docs = 'https://git.io/release-it-git';
14
14
 
15
+ const isGitRepo = () =>
16
+ execa('git', ['rev-parse', '--git-dir']).then(
17
+ () => true,
18
+ () => false
19
+ );
20
+
15
21
  class Git extends GitBase {
16
22
  constructor(...args) {
17
23
  super(...args);
@@ -19,7 +25,7 @@ class Git extends GitBase {
19
25
  }
20
26
 
21
27
  static async isEnabled(options) {
22
- return options !== false && (await findUp('.git', { type: 'directory' }));
28
+ return options !== false && (await isGitRepo());
23
29
  }
24
30
 
25
31
  async init() {
@@ -46,7 +52,8 @@ class Git extends GitBase {
46
52
 
47
53
  rollback() {
48
54
  this.log.info('Rolling back changes...');
49
- const { isCommitted, isTagged, tagName } = this.getContext();
55
+ const { tagName } = this.config.getContext();
56
+ const { isCommitted, isTagged } = this.getContext();
50
57
  if (isTagged) {
51
58
  this.exec(`git tag --delete ${tagName}`);
52
59
  }
@@ -175,12 +182,12 @@ class Git extends GitBase {
175
182
 
176
183
  tag({ name, annotation = this.options.tagAnnotation, args = this.options.tagArgs } = {}) {
177
184
  const message = format(annotation, this.config.getContext());
178
- const tagName = name || this.getContext('tagName');
185
+ const tagName = name || this.config.getContext('tagName');
179
186
  return this.exec(['git', 'tag', '--annotate', '--message', message, ...fixArgs(args), tagName])
180
187
  .then(() => this.setContext({ isTagged: true }))
181
188
  .catch(err => {
182
- const { latestTagName, tagName } = this.getContext();
183
- if (/tag '.+' already exists/.test(err) && latestTagName === tagName) {
189
+ const { latestTag, tagName } = this.config.getContext();
190
+ if (/tag '.+' already exists/.test(err) && latestTag === tagName) {
184
191
  this.log.warn(`Tag "${tagName}" already exists`);
185
192
  } else {
186
193
  throw err;
@@ -8,7 +8,7 @@ export default {
8
8
  },
9
9
  tag: {
10
10
  type: 'confirm',
11
- message: context => `Tag (${format(context.git.tagName, context)})?`,
11
+ message: context => `Tag (${format(context.tagName, context)})?`,
12
12
  default: true
13
13
  },
14
14
  push: {
@@ -1,10 +1,12 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import open from 'open';
3
4
  import { Octokit } from '@octokit/rest';
4
- import globby from 'globby';
5
+ import { globby } from 'globby';
5
6
  import mime from 'mime-types';
6
7
  import _ from 'lodash';
7
8
  import retry from 'async-retry';
9
+ import newGithubReleaseUrl from 'new-github-release-url';
8
10
  import { format, parseVersion, readJSON, e } from '../../util.js';
9
11
  import Release from '../GitRelease.js';
10
12
  import prompts from './prompts.js';
@@ -21,7 +23,7 @@ const parseErrormsg = err => {
21
23
  let msg = err;
22
24
  if (err instanceof Error) {
23
25
  const { status, message } = err;
24
- const { headers } = err.response;
26
+ const headers = err.response ? err.response.headers : {};
25
27
  msg = `${_.get(headers, 'status', status)} (${message})`;
26
28
  }
27
29
  return msg;
@@ -36,14 +38,22 @@ class GitHub extends Release {
36
38
  async init() {
37
39
  await super.init();
38
40
 
39
- const { skipChecks, tokenRef } = this.options;
40
- const { isUpdate } = this.config;
41
+ const { skipChecks, tokenRef, web, update, assets } = this.options;
41
42
 
42
- if (!skipChecks) {
43
- if (!this.token) {
44
- throw e(`Environment variable "${tokenRef}" is required for GitHub releases.`, docs);
43
+ if (!this.token || web) {
44
+ if (!web) {
45
+ this.log.warn(`Environment variable "${tokenRef}" is required for automated GitHub Releases.`);
46
+ this.log.warn('Falling back to web-based GitHub Release.');
45
47
  }
48
+ this.setContext({ isWeb: true });
49
+ return;
50
+ }
46
51
 
52
+ if (web && assets) {
53
+ this.log.warn('Assets are not included in web-based releases.');
54
+ }
55
+
56
+ if (!skipChecks) {
47
57
  // If we're running on GitHub Actions, we can skip the authentication and
48
58
  // collaborator checks. Ref: https://bit.ly/2vsyRzu
49
59
  if (process.env.GITHUB_ACTIONS) {
@@ -61,11 +71,11 @@ class GitHub extends Release {
61
71
  }
62
72
  }
63
73
 
64
- if (isUpdate) {
65
- const { latestTagName } = this.getContext();
74
+ if (update) {
75
+ const { latestTag } = this.config.getContext();
66
76
  try {
67
77
  const { id, upload_url, tag_name } = await this.getLatestRelease();
68
- if (latestTagName === tag_name) {
78
+ if (latestTag === tag_name) {
69
79
  this.setContext({ isUpdate: true, isReleased: true, releaseId: id, upload_url });
70
80
  } else {
71
81
  this.setContext({ isUpdate: false });
@@ -74,7 +84,7 @@ class GitHub extends Release {
74
84
  this.setContext({ isUpdate: false });
75
85
  }
76
86
  if (!this.getContext('isUpdate')) {
77
- this.log.warn(`GitHub release for tag ${latestTagName} was not found. Creating new release.`);
87
+ this.log.warn(`GitHub release for tag ${latestTag} was not found. Creating new release.`);
78
88
  }
79
89
  }
80
90
  }
@@ -109,13 +119,16 @@ class GitHub extends Release {
109
119
 
110
120
  async release() {
111
121
  const { assets } = this.options;
112
- const { isUpdate } = this.getContext();
122
+ const { isWeb, isUpdate } = this.getContext();
113
123
  const { isCI } = this.config;
114
124
 
115
125
  const type = isUpdate ? 'update' : 'create';
116
126
  const publishMethod = `${type}Release`;
117
127
 
118
- if (isCI) {
128
+ if (isWeb) {
129
+ const task = () => this.createWebRelease();
130
+ return this.step({ task, label: 'Generating link to GitHub Release web interface', prompt: 'release' });
131
+ } else if (isCI) {
119
132
  await this.step({ task: () => this[publishMethod](), label: `GitHub ${type} release` });
120
133
  return this.step({ enabled: assets, task: () => this.uploadAssets(), label: 'GitHub upload assets' });
121
134
  } else {
@@ -179,11 +192,12 @@ class GitHub extends Release {
179
192
 
180
193
  getOctokitReleaseOptions(options = {}) {
181
194
  const { owner, project: repo } = this.getContext('repo');
182
- const { releaseName, draft = false, preRelease = false } = this.options;
183
- const { version, tagName, releaseNotes } = this.getContext();
195
+ const { releaseName, draft = false, preRelease = false, autoGenerate = false } = this.options;
196
+ const { tagName } = this.config.getContext();
197
+ const { version, releaseNotes } = this.getContext();
184
198
  const { isPreRelease } = parseVersion(version);
185
199
  const name = format(releaseName, this.config.getContext());
186
- const body = releaseNotes;
200
+ const body = autoGenerate ? '' : releaseNotes || '';
187
201
 
188
202
  return Object.assign(options, {
189
203
  owner,
@@ -192,7 +206,8 @@ class GitHub extends Release {
192
206
  name,
193
207
  body,
194
208
  draft,
195
- prerelease: isPreRelease || preRelease
209
+ prerelease: isPreRelease || preRelease,
210
+ generate_release_notes: autoGenerate
196
211
  });
197
212
  }
198
213
 
@@ -285,6 +300,34 @@ class GitHub extends Release {
285
300
  return `https://${host}/${repository}/releases/tag/${tagName}`;
286
301
  }
287
302
 
303
+ generateWebUrl() {
304
+ const host = this.options.host || this.getContext('repo.host');
305
+ const isGitHub = host === 'github.com';
306
+
307
+ const options = this.getOctokitReleaseOptions();
308
+ const url = newGithubReleaseUrl({
309
+ user: options.owner,
310
+ repo: options.repo,
311
+ tag: options.tag_name,
312
+ isPrerelease: options.prerelease,
313
+ title: options.name,
314
+ body: options.body
315
+ });
316
+ return isGitHub ? url : url.replace('github.com', host);
317
+ }
318
+
319
+ async createWebRelease() {
320
+ const { isCI } = this.config;
321
+ const { tagName } = this.config.getContext();
322
+ const url = this.generateWebUrl();
323
+ if (isCI) {
324
+ this.setContext({ isReleased: true, releaseUrl: url });
325
+ } else {
326
+ await open(url);
327
+ this.setContext({ isReleased: true, releaseUrl: this.getReleaseUrlFallback(tagName) });
328
+ }
329
+ }
330
+
288
331
  updateRelease() {
289
332
  const { isDryRun } = this.config;
290
333
  const release_id = this.getContext('releaseId');
@@ -1,10 +1,10 @@
1
1
  import { format } from '../../util.js';
2
2
 
3
3
  const message = context => {
4
- const { isPreRelease, isUpdate, github } = context;
5
- const { releaseName } = github;
4
+ const { isPreRelease, github } = context;
5
+ const { releaseName, update } = github;
6
6
  const name = format(releaseName, context);
7
- return `${isUpdate ? 'Update' : 'Create a'} ${isPreRelease ? 'pre-' : ''}release on GitHub (${name})?`;
7
+ return `${update ? 'Update' : 'Create a'} ${isPreRelease ? 'pre-' : ''}release on GitHub (${name})?`;
8
8
  };
9
9
 
10
10
  export default {
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import got from 'got';
4
- import globby from 'globby';
4
+ import { globby } from 'globby';
5
5
  import FormData from 'form-data';
6
6
  import _ from 'lodash';
7
7
  import Release from '../GitRelease.js';
@@ -132,7 +132,8 @@ class GitLab extends Release {
132
132
 
133
133
  async createRelease() {
134
134
  const { releaseName } = this.options;
135
- const { id, tagName, releaseNotes, repo, origin } = this.getContext();
135
+ const { tagName } = this.config.getContext();
136
+ const { id, releaseNotes, repo, origin } = this.getContext();
136
137
  const { isDryRun } = this.config;
137
138
  const name = format(releaseName, this.config.getContext());
138
139
  const description = releaseNotes || '-';
@@ -80,7 +80,7 @@ class npm extends Plugin {
80
80
  const tag = this.options.tag || (await this.resolveTag(version));
81
81
  this.setContext({ version, tag });
82
82
 
83
- if (this.config.isUpdate) return false;
83
+ if (!this.config.isIncrement) return false;
84
84
 
85
85
  const task = () => this.exec(`npm version ${version} --no-git-tag-version`);
86
86
  return this.spinner.show({ task, label: 'npm version' });