@semantic-release/github 7.0.5 β†’ 7.1.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
@@ -59,13 +59,16 @@ When creating the token, the **minimum required scopes** are:
59
59
  - [`repo`](https://github.com/settings/tokens/new?scopes=repo) for a private repository
60
60
  - [`public_repo`](https://github.com/settings/tokens/new?scopes=public_repo) for a public repository
61
61
 
62
+ _Note on GitHub Actions:_ You can use the default token which is provided in the secret _GITHUB_TOKEN_. However releases done with this token will NOT trigger release events to start other workflows.
63
+ If you have actions that trigger on newly created releases, please use a generated token for that and store it in your repository's secrets (any other name than GITHUB_TOKEN is fine).
64
+
62
65
  ### Environment variables
63
66
 
64
- | Variable | Description |
65
- | ------------------------------ | --------------------------------------------------------- |
66
- | `GH_TOKEN` or `GITHUB_TOKEN` | **Required.** The token used to authenticate with GitHub. |
67
- | `GH_URL` or `GITHUB_URL` | The GitHub Enterprise endpoint. |
68
- | `GH_PREFIX` or `GITHUB_PREFIX` | The GitHub Enterprise API prefix. |
67
+ | Variable | Description |
68
+ | -------------------------------------------------- | --------------------------------------------------------- |
69
+ | `GH_TOKEN` or `GITHUB_TOKEN` | **Required.** The token used to authenticate with GitHub. |
70
+ | `GITHUB_API_URL` or `GH_URL` or `GITHUB_URL` | The GitHub Enterprise endpoint. |
71
+ | `GH_PREFIX` or `GITHUB_PREFIX` | The GitHub Enterprise API prefix. |
69
72
 
70
73
  ### Options
71
74
 
@@ -80,7 +83,8 @@ When creating the token, the **minimum required scopes** are:
80
83
  | `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` |
81
84
  | `labels` | The [labels](https://help.github.com/articles/about-labels) to add to the issue created when a release fails. Set to `false` to not add any label. | `['semantic-release']` |
82
85
  | `assignees` | The [assignees](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users) to add to the issue created when a release fails. | - |
83
- | `releasedLabels` | The [labels](https://help.github.com/articles/about-labels) to add to each issue and pull request resolved by the release. Set to `false` to not add any label. See [releasedLabels](#releasedlabels). | `['released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>']` |
86
+ | `releasedLabels` | The [labels](https://help.github.com/articles/about-labels) to add to each issue and pull request resolved by the release. Set to `false` to not add any label. See [releasedLabels](#releasedlabels). | `['released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>']- |
87
+ | `addReleases` | Will add release links to the GitHub Release. Can be `false`, `"bottom"` or `"top"`. See [addReleases](#addReleases). | `false` |
84
88
 
85
89
  #### proxy
86
90
 
@@ -201,3 +205,13 @@ Each label name is generated with [Lodash template](https://lodash.com/docs#temp
201
205
  The `releasedLabels` ```['released<%= nextRelease.channel ? ` on @\${nextRelease.channel}` : "" %> from <%= branch.name %>']``` will generate the label:
202
206
 
203
207
  > released on @next from branch next
208
+
209
+ #### addReleases
210
+
211
+ Add links to other releases to the GitHub release body.
212
+
213
+ Valid values for this option are `false`, `"top"` or `"bottom"`.
214
+
215
+ ##### addReleases example
216
+
217
+ See [The introducing PR](https://github.com/semantic-release/github/pull/282) for an example on how it will look.
package/index.js CHANGED
@@ -14,7 +14,7 @@ async function verifyConditions(pluginConfig, context) {
14
14
  // If the GitHub publish plugin is used and has `assets`, `successComment`, `failComment`, `failTitle`, `labels` or `assignees` configured, validate it now in order to prevent any release if the configuration is wrong
15
15
  if (options.publish) {
16
16
  const publishPlugin =
17
- castArray(options.publish).find(config => config.path && config.path === '@semantic-release/github') || {};
17
+ castArray(options.publish).find((config) => config.path && config.path === '@semantic-release/github') || {};
18
18
 
19
19
  pluginConfig.assets = defaultTo(pluginConfig.assets, publishPlugin.assets);
20
20
  pluginConfig.successComment = defaultTo(pluginConfig.successComment, publishPlugin.successComment);
@@ -3,9 +3,9 @@ const {isString} = require('lodash');
3
3
  const pkg = require('../../package.json');
4
4
 
5
5
  const [homepage] = pkg.homepage.split('#');
6
- const stringify = object =>
6
+ const stringify = (object) =>
7
7
  isString(object) ? object : inspect(object, {breakLength: Infinity, depth: 2, maxArrayLength: 5});
8
- const linkify = file => `${homepage}/blob/master/${file}`;
8
+ const linkify = (file) => `${homepage}/blob/master/${file}`;
9
9
 
10
10
  module.exports = {
11
11
  EINVALIDASSETS: ({assets}) => ({
@@ -57,6 +57,12 @@ Your configuration for the \`assignees\` option is \`${stringify(assignees)}\`.`
57
57
  )}) if defined, must be an \`Array\` of non empty \`String\`.
58
58
 
59
59
  Your configuration for the \`releasedLabels\` option is \`${stringify(releasedLabels)}\`.`,
60
+ }),
61
+ EINVALIDADDRELEASES: ({addReleases}) => ({
62
+ message: 'Invalid `addReleases` option.',
63
+ details: `The [addReleases option](${linkify('README.md#options')}) if defined, must be one of \`false|top|bottom\`.
64
+
65
+ Your configuration for the \`addReleases\` option is \`${stringify(addReleases)}\`.`,
60
66
  }),
61
67
  EINVALIDGITHUBURL: () => ({
62
68
  message: 'The git repository URL is not a valid GitHub URL.',
@@ -73,7 +79,7 @@ By default the \`repositoryUrl\` option is retrieved from the \`repository\` pro
73
79
  Your configuration for the \`proxy\` option is \`${stringify(proxy)}\`.`,
74
80
  }),
75
81
  EMISSINGREPO: ({owner, repo}) => ({
76
- message: `The repository ${owner}/${repo} doesn’t exist.`,
82
+ message: `The repository ${owner}/${repo} doesn't exist.`,
77
83
  details: `The **semantic-release** \`repositoryUrl\` option must refer to your GitHub repository. The repository must be accessible with the [GitHub API](https://developer.github.com/v3).
78
84
 
79
85
  By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment.
@@ -83,7 +89,7 @@ If you are using [GitHub Enterprise](https://enterprise.github.com) please make
83
89
  )}).`,
84
90
  }),
85
91
  EGHNOPERMISSION: ({owner, repo}) => ({
86
- message: `The GitHub token doesn’t allow to push on the repository ${owner}/${repo}.`,
92
+ message: `The GitHub token doesn't allow to push on the repository ${owner}/${repo}.`,
87
93
  details: `The user associated with the [GitHub token](${linkify(
88
94
  'README.md#github-authentication'
89
95
  )}) configured in the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable must allows to push to the repository ${owner}/${repo}.
@@ -7,5 +7,5 @@ module.exports = async (github, title, owner, repo) => {
7
7
  q: `in:title+repo:${owner}/${repo}+type:issue+state:open+${title}`,
8
8
  });
9
9
 
10
- return issues.filter(issue => issue.body && issue.body.includes(ISSUE_ID));
10
+ return issues.filter((issue) => issue.body && issue.body.includes(ISSUE_ID));
11
11
  };
package/lib/get-client.js CHANGED
@@ -11,7 +11,7 @@ const {RETRY_CONF, RATE_LIMITS, GLOBAL_RATE_LIMIT} = require('./definitions/rate
11
11
  /**
12
12
  * Http error status for which to not retry.
13
13
  */
14
- const SKIP_RETRY_CODES = [400, 401, 403];
14
+ const SKIP_RETRY_CODES = new Set([400, 401, 403]);
15
15
 
16
16
  /**
17
17
  * Create or retrieve the throttler function for a given rate limit group.
@@ -50,7 +50,7 @@ module.exports = ({githubToken, githubUrl, githubApiPathPrefix, proxy}) => {
50
50
  try {
51
51
  return await getThrottler(limitKey, globalThrottler).wrap(request)(options);
52
52
  } catch (error) {
53
- if (SKIP_RETRY_CODES.includes(error.status)) {
53
+ if (SKIP_RETRY_CODES.has(error.status)) {
54
54
  throw new pRetry.AbortError(error);
55
55
  }
56
56
 
@@ -4,14 +4,16 @@ const GET_HELP_URL = `${HOME_URL}#get-help`;
4
4
  const USAGE_DOC_URL = `${HOME_URL}/blob/caribou/docs/usage/README.md`;
5
5
  const NEW_ISSUE_URL = `${HOME_URL}/issues/new`;
6
6
 
7
- const formatError = error => `### ${error.message}
7
+ const formatError = (error) => `### ${error.message}
8
8
 
9
- ${error.details ||
10
- `Unfortunately this error doesn’t have any additional information.${
9
+ ${
10
+ error.details ||
11
+ `Unfortunately this error doesn't have any additional information.${
11
12
  error.pluginName
12
13
  ? ` Feel free to kindly ask the author of the \`${error.pluginName}\` plugin to add more helpful information.`
13
14
  : ''
14
- }`}`;
15
+ }`
16
+ }`;
15
17
 
16
18
  module.exports = (branch, errors) => `## :rotating_light: The automated release from the \`${
17
19
  branch.name
@@ -36,7 +38,7 @@ If those don’t help, or if this issue is reporting something you think isn’t
36
38
 
37
39
  ---
38
40
 
39
- ${errors.map(formatError).join('\n\n---\n\n')}
41
+ ${errors.map((error) => formatError(error)).join('\n\n---\n\n')}
40
42
 
41
43
  ---
42
44
 
@@ -0,0 +1,22 @@
1
+ const {RELEASE_NAME} = require('./definitions/constants');
2
+
3
+ const linkify = (releaseInfo) =>
4
+ `${
5
+ releaseInfo.url
6
+ ? releaseInfo.url.startsWith('http')
7
+ ? `[${releaseInfo.name}](${releaseInfo.url})`
8
+ : `${releaseInfo.name}: \`${releaseInfo.url}\``
9
+ : `\`${releaseInfo.name}\``
10
+ }`;
11
+
12
+ const filterReleases = (releaseInfos) =>
13
+ releaseInfos.filter((releaseInfo) => releaseInfo.name && releaseInfo.name !== RELEASE_NAME);
14
+
15
+ module.exports = (releaseInfos) =>
16
+ `${
17
+ filterReleases(releaseInfos).length > 0
18
+ ? `This release is also available on:\n${filterReleases(releaseInfos)
19
+ .map((releaseInfo) => `- ${linkify(releaseInfo)}`)
20
+ .join('\n')}`
21
+ : ''
22
+ }`;
@@ -1,5 +1,5 @@
1
1
  const HOME_URL = 'https://github.com/semantic-release/semantic-release';
2
- const linkify = releaseInfo =>
2
+ const linkify = (releaseInfo) =>
3
3
  `${releaseInfo.url ? `[${releaseInfo.name}](${releaseInfo.url})` : `\`${releaseInfo.name}\``}`;
4
4
 
5
5
  module.exports = (issue, releaseInfos, nextRelease) =>
@@ -10,7 +10,7 @@ module.exports = (issue, releaseInfos, nextRelease) =>
10
10
  ? `\n\nThe release is available on${
11
11
  releaseInfos.length === 1
12
12
  ? ` ${linkify(releaseInfos[0])}`
13
- : `:\n${releaseInfos.map(releaseInfo => `- ${linkify(releaseInfo)}`).join('\n')}`
13
+ : `:\n${releaseInfos.map((releaseInfo) => `- ${linkify(releaseInfo)}`).join('\n')}`
14
14
  }`
15
15
  : ''
16
16
  }
@@ -10,7 +10,7 @@ module.exports = async ({cwd}, assets) =>
10
10
  []
11
11
  .concat(
12
12
  ...(await Promise.all(
13
- assets.map(async asset => {
13
+ assets.map(async (asset) => {
14
14
  // Wrap single glob definition in Array
15
15
  let glob = castArray(isPlainObject(asset) ? asset.path : asset);
16
16
  // TODO Temporary workaround for https://github.com/mrmlnc/fast-glob/issues/47
@@ -40,7 +40,7 @@ module.exports = async ({cwd}, assets) =>
40
40
  // - `path` of the matched file
41
41
  // - `name` based on the actual file name (to avoid assets with duplicate `name`)
42
42
  // - other properties of the original asset definition
43
- return globbed.map(file => ({...asset, path: file, name: basename(file)}));
43
+ return globbed.map((file) => ({...asset, path: file, name: basename(file)}));
44
44
  }
45
45
 
46
46
  // If asset is an Object, output an Object definition with:
@@ -60,7 +60,7 @@ module.exports = async ({cwd}, assets) =>
60
60
  // Sort with Object first, to prioritize Object definition over Strings in dedup
61
61
  ))
62
62
  )
63
- .sort(asset => (isPlainObject(asset) ? -1 : 1)),
63
+ .sort((asset) => (isPlainObject(asset) ? -1 : 1)),
64
64
  // Compare `path` property if Object definition, value itself if String
65
65
  (a, b) => path.resolve(cwd, isPlainObject(a) ? a.path : a) === path.resolve(cwd, isPlainObject(b) ? b.path : b)
66
66
  );
@@ -1,4 +1,4 @@
1
- module.exports = repositoryUrl => {
1
+ module.exports = (repositoryUrl) => {
2
2
  const [match, auth, host, path] = /^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<path>.*)$/.exec(repositoryUrl) || [];
3
3
  try {
4
4
  const [, owner, repo] = /^\/(?<owner>[^/]+)?\/?(?<repo>.+?)(?:\.git)?$/.exec(
package/lib/publish.js CHANGED
@@ -28,11 +28,11 @@ module.exports = async (pluginConfig, context) => {
28
28
  // When there are no assets, we publish a release directly
29
29
  if (!assets || assets.length === 0) {
30
30
  const {
31
- data: {html_url: url},
31
+ data: {html_url: url, id: releaseId},
32
32
  } = await github.repos.createRelease(release);
33
33
 
34
34
  logger.log('Published GitHub release: %s', url);
35
- return {url, name: RELEASE_NAME};
35
+ return {url, name: RELEASE_NAME, id: releaseId};
36
36
  }
37
37
 
38
38
  // We'll create a draft release, append the assets to it, and then publish it.
@@ -48,7 +48,7 @@ module.exports = async (pluginConfig, context) => {
48
48
  debug('globed assets: %o', globbedAssets);
49
49
 
50
50
  await Promise.all(
51
- globbedAssets.map(async asset => {
51
+ globbedAssets.map(async (asset) => {
52
52
  const filePath = isPlainObject(asset) ? asset.path : asset;
53
53
  let file;
54
54
 
@@ -94,5 +94,5 @@ module.exports = async (pluginConfig, context) => {
94
94
  } = await github.repos.updateRelease({owner, repo, release_id: releaseId, draft: false});
95
95
 
96
96
  logger.log('Published GitHub release: %s', url);
97
- return {url, name: RELEASE_NAME};
97
+ return {url, name: RELEASE_NAME, id: releaseId};
98
98
  };
@@ -12,11 +12,12 @@ module.exports = (
12
12
  labels,
13
13
  assignees,
14
14
  releasedLabels,
15
+ addReleases,
15
16
  },
16
17
  {env}
17
18
  ) => ({
18
19
  githubToken: env.GH_TOKEN || env.GITHUB_TOKEN,
19
- githubUrl: githubUrl || env.GH_URL || env.GITHUB_URL,
20
+ githubUrl: githubUrl || env.GITHUB_API_URL || env.GH_URL || env.GITHUB_URL,
20
21
  githubApiPathPrefix: githubApiPathPrefix || env.GH_PREFIX || env.GITHUB_PREFIX || '',
21
22
  proxy: proxy || env.HTTP_PROXY,
22
23
  assets: assets ? castArray(assets) : assets,
@@ -30,4 +31,5 @@ module.exports = (
30
31
  : releasedLabels === false
31
32
  ? false
32
33
  : castArray(releasedLabels),
34
+ addReleases: isNil(addReleases) ? false : addReleases,
33
35
  });
package/lib/success.js CHANGED
@@ -1,4 +1,4 @@
1
- const {isNil, uniqBy, template, flatten} = require('lodash');
1
+ const {isNil, uniqBy, template, flatten, isEmpty} = require('lodash');
2
2
  const pFilter = require('p-filter');
3
3
  const AggregateError = require('aggregate-error');
4
4
  const issueParser = require('issue-parser');
@@ -9,6 +9,8 @@ const getClient = require('./get-client');
9
9
  const getSearchQueries = require('./get-search-queries');
10
10
  const getSuccessComment = require('./get-success-comment');
11
11
  const findSRIssues = require('./find-sr-issues');
12
+ const {RELEASE_NAME} = require('./definitions/constants');
13
+ const getReleaseLinks = require('./get-release-links');
12
14
 
13
15
  module.exports = async (pluginConfig, context) => {
14
16
  const {
@@ -27,6 +29,7 @@ module.exports = async (pluginConfig, context) => {
27
29
  failComment,
28
30
  failTitle,
29
31
  releasedLabels,
32
+ addReleases,
30
33
  } = resolveConfig(pluginConfig, context);
31
34
 
32
35
  const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
@@ -39,11 +42,11 @@ module.exports = async (pluginConfig, context) => {
39
42
  logger.log('Skip commenting on issues and pull requests.');
40
43
  } else {
41
44
  const parser = issueParser('github', githubUrl ? {hosts: [githubUrl]} : {});
42
- const releaseInfos = releases.filter(release => Boolean(release.name));
45
+ const releaseInfos = releases.filter((release) => Boolean(release.name));
43
46
  const shas = commits.map(({hash}) => hash);
44
47
 
45
48
  const searchQueries = getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
46
- async q => (await github.search.issuesAndPullRequests({q})).data.items
49
+ async (q) => (await github.search.issuesAndPullRequests({q})).data.items
47
50
  );
48
51
 
49
52
  const prs = await pFilter(
@@ -55,24 +58,27 @@ module.exports = async (pluginConfig, context) => {
55
58
 
56
59
  debug(
57
60
  'found pull requests: %O',
58
- prs.map(pr => pr.number)
61
+ prs.map((pr) => pr.number)
59
62
  );
60
63
 
61
64
  // Parse the release commits message and PRs body to find resolved issues/PRs via comment keyworkds
62
- const issues = [...prs.map(pr => pr.body), ...commits.map(commit => commit.message)].reduce((issues, message) => {
63
- return message
64
- ? issues.concat(
65
- parser(message)
66
- .actions.close.filter(action => isNil(action.slug) || action.slug === `${owner}/${repo}`)
67
- .map(action => ({number: parseInt(action.issue, 10)}))
68
- )
69
- : issues;
70
- }, []);
65
+ const issues = [...prs.map((pr) => pr.body), ...commits.map((commit) => commit.message)].reduce(
66
+ (issues, message) => {
67
+ return message
68
+ ? issues.concat(
69
+ parser(message)
70
+ .actions.close.filter((action) => isNil(action.slug) || action.slug === `${owner}/${repo}`)
71
+ .map((action) => ({number: Number.parseInt(action.issue, 10)}))
72
+ )
73
+ : issues;
74
+ },
75
+ []
76
+ );
71
77
 
72
78
  debug('found issues via comments: %O', issues);
73
79
 
74
80
  await Promise.all(
75
- uniqBy([...prs, ...issues], 'number').map(async issue => {
81
+ uniqBy([...prs, ...issues], 'number').map(async (issue) => {
76
82
  const body = successComment
77
83
  ? template(successComment)({...context, issue})
78
84
  : getSuccessComment(issue, releaseInfos, nextRelease);
@@ -85,7 +91,7 @@ module.exports = async (pluginConfig, context) => {
85
91
  logger.log('Added comment to issue #%d: %s', issue.number, url);
86
92
 
87
93
  if (releasedLabels) {
88
- const labels = releasedLabels.map(label => template(label)(context));
94
+ const labels = releasedLabels.map((label) => template(label)(context));
89
95
  // Don’t use .issues.addLabels for GHE < 2.16 support
90
96
  // https://github.com/semantic-release/github/issues/138
91
97
  await github.request('POST /repos/:owner/:repo/issues/:number/labels', {
@@ -100,7 +106,7 @@ module.exports = async (pluginConfig, context) => {
100
106
  if (error.status === 403) {
101
107
  logger.error('Not allowed to add a comment to the issue #%d.', issue.number);
102
108
  } else if (error.status === 404) {
103
- logger.error('Failed to add a comment to the issue #%d as it doesn’t exist.', issue.number);
109
+ logger.error("Failed to add a comment to the issue #%d as it doesn't exist.", issue.number);
104
110
  } else {
105
111
  errors.push(error);
106
112
  logger.error('Failed to add a comment to the issue #%d.', issue.number);
@@ -119,7 +125,7 @@ module.exports = async (pluginConfig, context) => {
119
125
  debug('found semantic-release issues: %O', srIssues);
120
126
 
121
127
  await Promise.all(
122
- srIssues.map(async issue => {
128
+ srIssues.map(async (issue) => {
123
129
  debug('close issue: %O', issue);
124
130
  try {
125
131
  const updateIssue = {owner, repo, issue_number: issue.number, state: 'closed'};
@@ -137,6 +143,21 @@ module.exports = async (pluginConfig, context) => {
137
143
  );
138
144
  }
139
145
 
146
+ if (addReleases !== false && errors.length === 0) {
147
+ const ghRelease = releases.find((release) => release.name && release.name === RELEASE_NAME);
148
+ if (!isNil(ghRelease)) {
149
+ const ghRelaseId = ghRelease.id;
150
+ const additionalReleases = getReleaseLinks(releases);
151
+ if (!isEmpty(additionalReleases) && !isNil(ghRelaseId)) {
152
+ const newBody =
153
+ addReleases === 'top'
154
+ ? additionalReleases.concat('\n---\n', nextRelease.notes)
155
+ : nextRelease.notes.concat('\n---\n', additionalReleases);
156
+ await github.repos.updateRelease({owner, repo, release_id: ghRelaseId, body: newBody});
157
+ }
158
+ }
159
+ }
160
+
140
161
  if (errors.length > 0) {
141
162
  throw new AggregateError(errors);
142
163
  }
package/lib/verify.js CHANGED
@@ -6,16 +6,18 @@ const resolveConfig = require('./resolve-config');
6
6
  const getClient = require('./get-client');
7
7
  const getError = require('./get-error');
8
8
 
9
- const isNonEmptyString = value => isString(value) && value.trim();
10
- const isStringOrStringArray = value => isNonEmptyString(value) || (isArray(value) && value.every(isNonEmptyString));
11
- const isArrayOf = validator => array => isArray(array) && array.every(value => validator(value));
12
- const canBeDisabled = validator => value => value === false || validator(value);
9
+ const isNonEmptyString = (value) => isString(value) && value.trim();
10
+ const oneOf = (enumArray) => (value) => enumArray.some((element) => element === value);
11
+ const isStringOrStringArray = (value) =>
12
+ isNonEmptyString(value) || (isArray(value) && value.every((string) => isNonEmptyString(string)));
13
+ const isArrayOf = (validator) => (array) => isArray(array) && array.every((value) => validator(value));
14
+ const canBeDisabled = (validator) => (value) => value === false || validator(value);
13
15
 
14
16
  const VALIDATORS = {
15
- proxy: proxy =>
17
+ proxy: (proxy) =>
16
18
  isNonEmptyString(proxy) || (isPlainObject(proxy) && isNonEmptyString(proxy.host) && isNumber(proxy.port)),
17
19
  assets: isArrayOf(
18
- asset => isStringOrStringArray(asset) || (isPlainObject(asset) && isStringOrStringArray(asset.path))
20
+ (asset) => isStringOrStringArray(asset) || (isPlainObject(asset) && isStringOrStringArray(asset.path))
19
21
  ),
20
22
  successComment: canBeDisabled(isNonEmptyString),
21
23
  failTitle: canBeDisabled(isNonEmptyString),
@@ -23,6 +25,7 @@ const VALIDATORS = {
23
25
  labels: canBeDisabled(isArrayOf(isNonEmptyString)),
24
26
  assignees: isArrayOf(isNonEmptyString),
25
27
  releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)),
28
+ addReleases: canBeDisabled(oneOf(['bottom', 'top'])),
26
29
  };
27
30
 
28
31
  module.exports = async (pluginConfig, context) => {
@@ -68,6 +71,14 @@ module.exports = async (pluginConfig, context) => {
68
71
  },
69
72
  } = await github.repos.get({repo, owner});
70
73
  if (!push) {
74
+ // If authenticated as GitHub App installation, `push` will always be false.
75
+ // We send another request to check if current authentication is an installation.
76
+ // Note: we cannot check if the installation has all required permissions, it's
77
+ // up to the user to make sure it has
78
+ if (await github.request('HEAD /installation/repositories', {per_page: 1}).catch(() => false)) {
79
+ return;
80
+ }
81
+
71
82
  errors.push(getError('EGHNOPERMISSION', {owner, repo}));
72
83
  }
73
84
  } catch (error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@semantic-release/github",
3
3
  "description": "semantic-release plugin to publish a GitHub release and comment on released Pull Requests/Issues",
4
- "version": "7.0.5",
4
+ "version": "7.1.1",
5
5
  "author": "Pierre Vanduynslager (https://twitter.com/@pvdlg_)",
6
6
  "ava": {
7
7
  "files": [
@@ -45,7 +45,7 @@
45
45
  "server-destroy": "^1.0.1",
46
46
  "sinon": "^9.0.0",
47
47
  "tempy": "^0.5.0",
48
- "xo": "^0.28.0"
48
+ "xo": "^0.30.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=10.18"
@@ -110,7 +110,8 @@
110
110
  {
111
111
  "properties": "never"
112
112
  }
113
- ]
113
+ ],
114
+ "unicorn/string-content": "off"
114
115
  }
115
116
  }
116
117
  }