@semantic-release/github 8.1.0 → 9.0.0-beta.2

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.
@@ -1,65 +1,76 @@
1
- const path = require('path');
2
- const {isPlainObject, castArray, uniqWith, uniq} = require('lodash');
3
- const dirGlob = require('dir-glob');
4
- const globby = require('globby');
5
- const debug = require('debug')('semantic-release:github');
1
+ import { basename, resolve } from "node:path";
6
2
 
7
- module.exports = async ({cwd}, assets) =>
8
- uniqWith(
9
- []
10
- .concat(
11
- ...(await Promise.all(
12
- assets.map(async (asset) => {
13
- // Wrap single glob definition in Array
14
- let glob = castArray(isPlainObject(asset) ? asset.path : asset);
15
- // TODO Temporary workaround for https://github.com/mrmlnc/fast-glob/issues/47
16
- glob = uniq([...(await dirGlob(glob, {cwd})), ...glob]);
3
+ import { isPlainObject, castArray, uniqWith, uniq } from "lodash-es";
4
+ import dirGlob from "dir-glob";
5
+ import { globby } from "globby";
6
+ import debugFactory from "debug";
17
7
 
18
- // Skip solo negated pattern (avoid to include every non js file with `!**/*.js`)
19
- if (glob.length <= 1 && glob[0].startsWith('!')) {
20
- debug(
21
- 'skipping the negated glob %o as its alone in its group and would retrieve a large amount of files',
22
- glob[0]
23
- );
24
- return [];
25
- }
8
+ const debug = debugFactory("semantic-release:github");
9
+
10
+ export default async function globAssets({ cwd }, assets) {
11
+ return uniqWith(
12
+ (
13
+ await Promise.all(
14
+ assets.map(async (asset) => {
15
+ // Wrap single glob definition in Array
16
+ let glob = castArray(isPlainObject(asset) ? asset.path : asset);
17
+
18
+ // TODO Temporary workaround for https://github.com/mrmlnc/fast-glob/issues/47
19
+ glob = uniq([...(await dirGlob(glob, { cwd })), ...glob]);
26
20
 
27
- const globbed = await globby(glob, {
28
- cwd,
29
- expandDirectories: false, // TODO Temporary workaround for https://github.com/mrmlnc/fast-glob/issues/47
30
- gitignore: false,
31
- dot: true,
32
- onlyFiles: false,
33
- });
21
+ // Skip solo negated pattern (avoid to include every non js file with `!**/*.js`)
22
+ if (glob.length <= 1 && glob[0].startsWith("!")) {
23
+ debug(
24
+ "skipping the negated glob %o as its alone in its group and would retrieve a large amount of files",
25
+ glob[0]
26
+ );
27
+ return [];
28
+ }
34
29
 
35
- if (isPlainObject(asset)) {
36
- if (globbed.length > 1) {
37
- // If asset is an Object with a glob the `path` property that resolve to multiple files,
38
- // Output an Object definition for each file matched and set each one with:
39
- // - `path` of the matched file
40
- // - `name` based on the actual file name (to avoid assets with duplicate `name`)
41
- // - other properties of the original asset definition
42
- return globbed.map((file) => ({...asset, path: file, name: path.basename(file)}));
43
- }
30
+ const globbed = await globby(glob, {
31
+ cwd,
32
+ expandDirectories: false, // TODO Temporary workaround for https://github.com/mrmlnc/fast-glob/issues/47
33
+ gitignore: false,
34
+ dot: true,
35
+ onlyFiles: false,
36
+ });
44
37
 
45
- // If asset is an Object, output an Object definition with:
46
- // - `path` of the matched file if there is one, or the original `path` definition (will be considered as a missing file)
38
+ if (isPlainObject(asset)) {
39
+ if (globbed.length > 1) {
40
+ // If asset is an Object with a glob the `path` property that resolve to multiple files,
41
+ // Output an Object definition for each file matched and set each one with:
42
+ // - `path` of the matched file
43
+ // - `name` based on the actual file name (to avoid assets with duplicate `name`)
47
44
  // - other properties of the original asset definition
48
- return {...asset, path: globbed[0] || asset.path};
45
+ return globbed.map((file) => ({
46
+ ...asset,
47
+ path: file,
48
+ name: basename(file),
49
+ }));
49
50
  }
50
51
 
51
- if (globbed.length > 0) {
52
- // If asset is a String definition, output each files matched
53
- return globbed;
54
- }
52
+ // If asset is an Object, output an Object definition with:
53
+ // - `path` of the matched file if there is one, or the original `path` definition (will be considered as a missing file)
54
+ // - other properties of the original asset definition
55
+ return { ...asset, path: globbed[0] || asset.path };
56
+ }
57
+
58
+ if (globbed.length > 0) {
59
+ // If asset is a String definition, output each files matched
60
+ return globbed;
61
+ }
55
62
 
56
- // If asset is a String definition but no match is found, output the elements of the original glob (each one will be considered as a missing file)
57
- return glob;
58
- })
59
- // Sort with Object first, to prioritize Object definition over Strings in dedup
60
- ))
63
+ // If asset is a String definition but no match is found, output the elements of the original glob (each one will be considered as a missing file)
64
+ return glob;
65
+ })
66
+ // Sort with Object first, to prioritize Object definition over Strings in dedup
61
67
  )
68
+ )
69
+ .flat()
62
70
  .sort((asset) => (isPlainObject(asset) ? -1 : 1)),
63
71
  // Compare `path` property if Object definition, value itself if String
64
- (a, b) => path.resolve(cwd, isPlainObject(a) ? a.path : a) === path.resolve(cwd, isPlainObject(b) ? b.path : b)
72
+ (a, b) =>
73
+ resolve(cwd, isPlainObject(a) ? a.path : a) ===
74
+ resolve(cwd, isPlainObject(b) ? b.path : b)
65
75
  );
76
+ }
@@ -1 +1,3 @@
1
- module.exports = ({type, main}) => type === 'prerelease' || (type === 'release' && !main);
1
+ export default function isPrerelease({ type, main }) {
2
+ return type === "prerelease" || (type === "release" && !main);
3
+ }
package/lib/octokit.js ADDED
@@ -0,0 +1,76 @@
1
+ /* c8 ignore start */
2
+ // @ts-check
3
+
4
+ import { createRequire } from "node:module";
5
+
6
+ // If maintaining @octokit/core and the separate plugins gets to cumbersome
7
+ // then the `octokit` package can be used which has all these plugins included.
8
+ // However the `octokit` package has a lot of other things we don't care about.
9
+ // We use only the bits we need to minimize the size of the package.
10
+ import { Octokit } from "@octokit/core";
11
+ import { paginateRest } from "@octokit/plugin-paginate-rest";
12
+ import { retry } from "@octokit/plugin-retry";
13
+ import { throttling } from "@octokit/plugin-throttling";
14
+ import urljoin from "url-join";
15
+ import { HttpProxyAgent } from "http-proxy-agent";
16
+ import { HttpsProxyAgent } from "https-proxy-agent";
17
+
18
+ import { RETRY_CONF } from "./definitions/retry.js";
19
+ import { THROTTLE_CONF } from "./definitions/throttle.js";
20
+
21
+ // NOTE: replace with import ... assert { type: 'json' } once supported
22
+ const require = createRequire(import.meta.url);
23
+ const pkg = require("../package.json");
24
+
25
+ const onRetry = (retryAfter, options, octokit, retryCount) => {
26
+ octokit.log.warn(
27
+ `Request quota exhausted for request ${options.method} ${options.url}`
28
+ );
29
+
30
+ if (retryCount <= RETRY_CONF.retries) {
31
+ octokit.log.debug(`Will retry after ${retryAfter}.`);
32
+ return true;
33
+ }
34
+ };
35
+
36
+ export const SemanticReleaseOctokit = Octokit.plugin(
37
+ paginateRest,
38
+ retry,
39
+ throttling
40
+ ).defaults({
41
+ userAgent: `@semantic-release/github v${pkg.version}`,
42
+ retry: RETRY_CONF,
43
+ throttle: {
44
+ ...THROTTLE_CONF,
45
+ onRateLimit: onRetry,
46
+ onSecondaryRateLimit: onRetry,
47
+ },
48
+ });
49
+ /* c8 ignore stop */
50
+
51
+ /**
52
+ * @param {{githubToken: string, proxy: any} | {githubUrl: string, githubApiPathPrefix: string, githubToken: string, proxy: any}} options
53
+ * @returns {{ auth: string, baseUrl?: string, request: { agent?: any } }}
54
+ */
55
+ export function toOctokitOptions(options) {
56
+ const baseUrl =
57
+ "githubUrl" in options && options.githubUrl
58
+ ? urljoin(options.githubUrl, options.githubApiPathPrefix)
59
+ : undefined;
60
+
61
+ const agent = options.proxy
62
+ ? baseUrl && new URL(baseUrl).protocol.replace(":", "") === "http"
63
+ ? // Some `proxy.headers` need to be passed as second arguments since version 6 or 7
64
+ // For simplicity, we just pass the same proxy object twice. It works 🤷🏻
65
+ new HttpProxyAgent(options.proxy, options.proxy)
66
+ : new HttpsProxyAgent(options.proxy, options.proxy)
67
+ : undefined;
68
+
69
+ return {
70
+ ...(baseUrl ? { baseUrl } : {}),
71
+ auth: options.githubToken,
72
+ request: {
73
+ agent,
74
+ },
75
+ };
76
+ }
@@ -1,11 +1,19 @@
1
- module.exports = (repositoryUrl) => {
2
- const [match, auth, host, path] = /^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<path>.*)$/.exec(repositoryUrl) || [];
1
+ export default function parseGitHubUrl(repositoryUrl) {
2
+ const [match, auth, host, path] =
3
+ /^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<path>.*)$/.exec(
4
+ repositoryUrl
5
+ ) || [];
3
6
  try {
4
- const [, owner, repo] = /^\/(?<owner>[^/]+)?\/?(?<repo>.+?)(?:\.git)?$/.exec(
5
- new URL(match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl).pathname
6
- );
7
- return {owner, repo};
7
+ const [, owner, repo] =
8
+ /^\/(?<owner>[^/]+)?\/?(?<repo>.+?)(?:\.git)?$/.exec(
9
+ new URL(
10
+ match
11
+ ? `ssh://${auth ? `${auth}@` : ""}${host}/${path}`
12
+ : repositoryUrl
13
+ ).pathname
14
+ );
15
+ return { owner, repo };
8
16
  } catch {
9
17
  return {};
10
18
  }
11
- };
19
+ }
package/lib/publish.js CHANGED
@@ -1,29 +1,44 @@
1
- const path = require('path');
2
- const {stat, readFile} = require('fs-extra');
3
- const {isPlainObject, template} = require('lodash');
4
- const mime = require('mime');
5
- const debug = require('debug')('semantic-release:github');
6
- const {RELEASE_NAME} = require('./definitions/constants');
7
- const parseGithubUrl = require('./parse-github-url');
8
- const globAssets = require('./glob-assets');
9
- const resolveConfig = require('./resolve-config');
10
- const getClient = require('./get-client');
11
- const isPrerelease = require('./is-prerelease');
12
-
13
- module.exports = async (pluginConfig, context) => {
1
+ import { resolve, basename, extname } from "node:path";
2
+ import { stat, readFile } from "node:fs/promises";
3
+
4
+ import { isPlainObject, template } from "lodash-es";
5
+ import mime from "mime";
6
+ import debugFactory from "debug";
7
+
8
+ import { RELEASE_NAME } from "./definitions/constants.js";
9
+ import parseGithubUrl from "./parse-github-url.js";
10
+ import globAssets from "./glob-assets.js";
11
+ import resolveConfig from "./resolve-config.js";
12
+ import { toOctokitOptions } from "./octokit.js";
13
+ import isPrerelease from "./is-prerelease.js";
14
+
15
+ const debug = debugFactory("semantic-release:github");
16
+
17
+ export default async function publish(pluginConfig, context, { Octokit }) {
14
18
  const {
15
19
  cwd,
16
- options: {repositoryUrl},
20
+ options: { repositoryUrl },
17
21
  branch,
18
- nextRelease: {name, gitTag, notes},
22
+ nextRelease: { name, gitTag, notes },
19
23
  logger,
20
24
  } = context;
21
- const {githubToken, githubUrl, githubApiPathPrefix, proxy, assets, draftRelease} = resolveConfig(
22
- pluginConfig,
23
- context
25
+ const {
26
+ githubToken,
27
+ githubUrl,
28
+ githubApiPathPrefix,
29
+ proxy,
30
+ assets,
31
+ draftRelease,
32
+ } = resolveConfig(pluginConfig, context);
33
+ const { owner, repo } = parseGithubUrl(repositoryUrl);
34
+ const octokit = new Octokit(
35
+ toOctokitOptions({
36
+ githubToken,
37
+ githubUrl,
38
+ githubApiPathPrefix,
39
+ proxy,
40
+ })
24
41
  );
25
- const {owner, repo} = parseGithubUrl(repositoryUrl);
26
- const octokit = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
27
42
  const release = {
28
43
  owner,
29
44
  repo,
@@ -34,39 +49,45 @@ module.exports = async (pluginConfig, context) => {
34
49
  prerelease: isPrerelease(branch),
35
50
  };
36
51
 
37
- debug('release object: %O', release);
52
+ debug("release object: %O", release);
38
53
 
39
- const draftReleaseOptions = {...release, draft: true};
54
+ const draftReleaseOptions = { ...release, draft: true };
40
55
 
41
56
  // When there are no assets, we publish a release directly.
42
57
  if (!assets || assets.length === 0) {
43
58
  // If draftRelease is true we create a draft release instead.
44
59
  if (draftRelease) {
45
60
  const {
46
- data: {html_url: url, id: releaseId},
47
- } = await octokit.request('POST /repos/{owner}/{repo}/releases', draftReleaseOptions);
48
-
49
- logger.log('Created GitHub draft release: %s', url);
50
- return {url, name: RELEASE_NAME, id: releaseId};
61
+ data: { html_url: url, id: releaseId },
62
+ } = await octokit.request(
63
+ "POST /repos/{owner}/{repo}/releases",
64
+ draftReleaseOptions
65
+ );
66
+
67
+ logger.log("Created GitHub draft release: %s", url);
68
+ return { url, name: RELEASE_NAME, id: releaseId };
51
69
  }
52
70
 
53
71
  const {
54
- data: {html_url: url, id: releaseId},
55
- } = await octokit.request('POST /repos/{owner}/{repo}/releases', release);
72
+ data: { html_url: url, id: releaseId },
73
+ } = await octokit.request("POST /repos/{owner}/{repo}/releases", release);
56
74
 
57
- logger.log('Published GitHub release: %s', url);
58
- return {url, name: RELEASE_NAME, id: releaseId};
75
+ logger.log("Published GitHub release: %s", url);
76
+ return { url, name: RELEASE_NAME, id: releaseId };
59
77
  }
60
78
 
61
79
  // We'll create a draft release, append the assets to it, and then publish it.
62
80
  // This is so that the assets are available when we get a Github release event.
63
81
  const {
64
- data: {upload_url: uploadUrl, html_url: draftUrl, id: releaseId},
65
- } = await octokit.request('POST /repos/{owner}/{repo}/releases', draftReleaseOptions);
82
+ data: { upload_url: uploadUrl, html_url: draftUrl, id: releaseId },
83
+ } = await octokit.request(
84
+ "POST /repos/{owner}/{repo}/releases",
85
+ draftReleaseOptions
86
+ );
66
87
 
67
88
  // Append assets to the release
68
89
  const globbedAssets = await globAssets(context, assets);
69
- debug('globed assets: %o', globbedAssets);
90
+ debug("globed assets: %o", globbedAssets);
70
91
 
71
92
  await Promise.all(
72
93
  globbedAssets.map(async (asset) => {
@@ -74,58 +95,67 @@ module.exports = async (pluginConfig, context) => {
74
95
  let file;
75
96
 
76
97
  try {
77
- file = await stat(path.resolve(cwd, filePath));
98
+ file = await stat(resolve(cwd, filePath));
78
99
  } catch {
79
- logger.error('The asset %s cannot be read, and will be ignored.', filePath);
100
+ logger.error(
101
+ "The asset %s cannot be read, and will be ignored.",
102
+ filePath
103
+ );
80
104
  return;
81
105
  }
82
106
 
83
107
  if (!file || !file.isFile()) {
84
- logger.error('The asset %s is not a file, and will be ignored.', filePath);
108
+ logger.error(
109
+ "The asset %s is not a file, and will be ignored.",
110
+ filePath
111
+ );
85
112
  return;
86
113
  }
87
114
 
88
- const fileName = template(asset.name || path.basename(filePath))(context);
115
+ const fileName = template(asset.name || basename(filePath))(context);
89
116
  const upload = {
90
- method: 'POST',
117
+ method: "POST",
91
118
  url: uploadUrl,
92
- data: await readFile(path.resolve(cwd, filePath)),
119
+ data: await readFile(resolve(cwd, filePath)),
93
120
  name: fileName,
94
121
  headers: {
95
- 'content-type': mime.getType(path.extname(fileName)) || 'text/plain',
96
- 'content-length': file.size,
122
+ "content-type": mime.getType(extname(fileName)) || "text/plain",
123
+ "content-length": file.size,
97
124
  },
98
125
  };
99
126
 
100
- debug('file path: %o', filePath);
101
- debug('file name: %o', fileName);
127
+ debug("file path: %o", filePath);
128
+ debug("file name: %o", fileName);
102
129
 
103
130
  if (isPlainObject(asset) && asset.label) {
104
131
  upload.label = template(asset.label)(context);
105
132
  }
106
133
 
107
134
  const {
108
- data: {browser_download_url: downloadUrl},
135
+ data: { browser_download_url: downloadUrl },
109
136
  } = await octokit.request(upload);
110
- logger.log('Published file %s', downloadUrl);
137
+ logger.log("Published file %s", downloadUrl);
111
138
  })
112
139
  );
113
140
 
114
141
  // If we want to create a draft we don't need to update the release again
115
142
  if (draftRelease) {
116
- logger.log('Created GitHub draft release: %s', draftUrl);
117
- return {url: draftUrl, name: RELEASE_NAME, id: releaseId};
143
+ logger.log("Created GitHub draft release: %s", draftUrl);
144
+ return { url: draftUrl, name: RELEASE_NAME, id: releaseId };
118
145
  }
119
146
 
120
147
  const {
121
- data: {html_url: url},
122
- } = await octokit.request('PATCH /repos/{owner}/{repo}/releases/{release_id}', {
123
- owner,
124
- repo,
125
- release_id: releaseId,
126
- draft: false,
127
- });
148
+ data: { html_url: url },
149
+ } = await octokit.request(
150
+ "PATCH /repos/{owner}/{repo}/releases/{release_id}",
151
+ {
152
+ owner,
153
+ repo,
154
+ release_id: releaseId,
155
+ draft: false,
156
+ }
157
+ );
128
158
 
129
- logger.log('Published GitHub release: %s', url);
130
- return {url, name: RELEASE_NAME, id: releaseId};
131
- };
159
+ logger.log("Published GitHub release: %s", url);
160
+ return { url, name: RELEASE_NAME, id: releaseId };
161
+ }
@@ -1,6 +1,6 @@
1
- const {isNil, castArray} = require('lodash');
1
+ import { isNil, castArray } from "lodash-es";
2
2
 
3
- module.exports = (
3
+ export default function resolveConfig(
4
4
  {
5
5
  githubUrl,
6
6
  githubApiPathPrefix,
@@ -15,23 +15,34 @@ module.exports = (
15
15
  addReleases,
16
16
  draftRelease,
17
17
  },
18
- {env}
19
- ) => ({
20
- githubToken: env.GH_TOKEN || env.GITHUB_TOKEN,
21
- githubUrl: githubUrl || env.GITHUB_API_URL || env.GH_URL || env.GITHUB_URL,
22
- githubApiPathPrefix: githubApiPathPrefix || env.GH_PREFIX || env.GITHUB_PREFIX || '',
23
- proxy: isNil(proxy) ? env.http_proxy || env.HTTP_PROXY || false : proxy,
24
- assets: assets ? castArray(assets) : assets,
25
- successComment,
26
- failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle,
27
- failComment,
28
- labels: isNil(labels) ? ['semantic-release'] : labels === false ? false : castArray(labels),
29
- assignees: assignees ? castArray(assignees) : assignees,
30
- releasedLabels: isNil(releasedLabels)
31
- ? [`released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>`]
32
- : releasedLabels === false
33
- ? false
34
- : castArray(releasedLabels),
35
- addReleases: isNil(addReleases) ? false : addReleases,
36
- draftRelease: isNil(draftRelease) ? false : draftRelease,
37
- });
18
+ { env }
19
+ ) {
20
+ return {
21
+ githubToken: env.GH_TOKEN || env.GITHUB_TOKEN,
22
+ githubUrl: githubUrl || env.GITHUB_API_URL || env.GH_URL || env.GITHUB_URL,
23
+ githubApiPathPrefix:
24
+ githubApiPathPrefix || env.GH_PREFIX || env.GITHUB_PREFIX || "",
25
+ proxy: isNil(proxy) ? env.http_proxy || env.HTTP_PROXY || false : proxy,
26
+ assets: assets ? castArray(assets) : assets,
27
+ successComment,
28
+ failTitle: isNil(failTitle)
29
+ ? "The automated release is failing 🚨"
30
+ : failTitle,
31
+ failComment,
32
+ labels: isNil(labels)
33
+ ? ["semantic-release"]
34
+ : labels === false
35
+ ? false
36
+ : castArray(labels),
37
+ assignees: assignees ? castArray(assignees) : assignees,
38
+ releasedLabels: isNil(releasedLabels)
39
+ ? [
40
+ `released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>`,
41
+ ]
42
+ : releasedLabels === false
43
+ ? false
44
+ : castArray(releasedLabels),
45
+ addReleases: isNil(addReleases) ? false : addReleases,
46
+ draftRelease: isNil(draftRelease) ? false : draftRelease,
47
+ };
48
+ }