zx-bulk-release 2.19.0 → 2.20.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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [2.20.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.19.1...v2.20.0) (2026-04-05)
2
+
3
+ ### Features
4
+ * feat: support custom ghe urls (#89) ([e33d53c](https://github.com/semrel-extra/zx-bulk-release/commit/e33d53cfe4ac15d8fd66785622d9df428a15d8e6))
5
+
6
+ ## [2.19.1](https://github.com/semrel-extra/zx-bulk-release/compare/v2.19.0...v2.19.1) (2026-04-05)
7
+
8
+ ### Fixes & improvements
9
+ * fix: handle meta=none ([4bd052d](https://github.com/semrel-extra/zx-bulk-release/commit/4bd052d4deb3a2a6a6862860e7dabd205275fbcc))
10
+
1
11
  ## [2.19.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.18.3...v2.19.0) (2026-04-05)
2
12
 
3
13
  ### Features
package/README.md CHANGED
@@ -77,18 +77,40 @@ Any [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) compliant format
77
77
  "cmd": "yarn && yarn build && yarn test",
78
78
  "npmFetch": true,
79
79
  "changelog": "changelog",
80
- "ghPages": "gh-pages"
80
+ "ghPages": "gh-pages",
81
+ "diffTagUrl": "${repoPublicUrl}/compare/${prevTag}...${newTag}",
82
+ "diffCommitUrl": "${repoPublicUrl}/commit/${hash}"
81
83
  }
82
84
  ```
83
85
 
86
+ #### Changelog diff URLs
87
+ By default, changelog entries link to GitHub compare/commit pages. Override `diffTagUrl` and `diffCommitUrl` to customize for other platforms (e.g. Gerrit):
88
+ ```json
89
+ {
90
+ "diffTagUrl": "https://gerrit.foo.com/plugins/gitiles/${repoName}/+/refs/tags/${newTag}",
91
+ "diffCommitUrl": "https://gerrit.foo.com/plugins/gitiles/${repoName}/+/${hash}%5E%21"
92
+ }
93
+ ```
94
+ Available variables: `repoName`, `repoPublicUrl`, `prevTag`, `newTag`, `name`, `version`, `hash`, `short`.
95
+
96
+ #### GitHub Enterprise
97
+ Set `ghUrl` to point to your GHE instance. API URL (`ghApiUrl`) is derived automatically.
98
+ ```json
99
+ {
100
+ "ghUrl": "https://ghe.corp.com"
101
+ }
102
+ ```
103
+ Or via env: `GH_URL=https://ghe.corp.com` / `GITHUB_URL=https://ghe.corp.com`.
104
+
84
105
  ### env vars
85
106
  ```js
86
107
  export const parseEnv = (env = process.env) => {
87
- const {GH_USER, GH_USERNAME, GITHUB_USER, GITHUB_USERNAME, GH_TOKEN, GITHUB_TOKEN, NPM_TOKEN, NPM_REGISTRY, NPMRC, NPM_USERCONFIG, NPM_CONFIG_USERCONFIG, NPM_PROVENANCE, NPM_OIDC, ACTIONS_ID_TOKEN_REQUEST_URL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL} = env
108
+ const {GH_USER, GH_USERNAME, GITHUB_USER, GITHUB_USERNAME, GH_TOKEN, GITHUB_TOKEN, GH_URL, GITHUB_URL, NPM_TOKEN, NPM_REGISTRY, NPMRC, NPM_USERCONFIG, NPM_CONFIG_USERCONFIG, NPM_PROVENANCE, NPM_OIDC, ACTIONS_ID_TOKEN_REQUEST_URL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL} = env
88
109
 
89
110
  return {
90
- ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME,
111
+ ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME || 'x-access-token',
91
112
  ghToken: GH_TOKEN || GITHUB_TOKEN,
113
+ ghUrl: GH_URL || GITHUB_URL || 'https://github.com',
92
114
  npmToken: NPM_TOKEN,
93
115
  // npmConfig suppresses npmToken
94
116
  npmConfig: NPMRC || NPM_USERCONFIG || NPM_CONFIG_USERCONFIG,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
3
  "alias": "bulk-release",
4
- "version": "2.19.0",
4
+ "version": "2.20.0",
5
5
  "description": "zx-based alternative for multi-semantic-release",
6
6
  "type": "module",
7
7
  "exports": {
@@ -21,14 +21,27 @@ export const pushChangelog = queuefy(async (pkg) => {
21
21
  await pushCommit({cwd, branch, msg, gitCommitterEmail, gitCommitterName, basicAuth})
22
22
  })
23
23
 
24
+ export const DIFF_TAG_URL = '${repoPublicUrl}/compare/${prevTag}...${newTag}'
25
+ export const DIFF_COMMIT_URL = '${repoPublicUrl}/commit/${hash}'
26
+
27
+ export const interpolate = (template, vars) => {
28
+ const result = template.replace(/\$\{(\w+)}/g, (_, key) => vars[key] ?? '')
29
+ try { new URL(result) } catch { throw new Error(`invalid URL after interpolation: '${result}' (template: '${template}')`) }
30
+ return result
31
+ }
32
+
24
33
  export const formatReleaseNotes = async (pkg) => {
25
- const {name, version, tag = formatTag({name, version}), absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg
26
- const {repoPublicUrl} = await getRepo(cwd, {basicAuth})
27
- const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})`
34
+ const {name, version, tag = formatTag({name, version}), absPath: cwd, config: {ghBasicAuth: basicAuth, diffTagUrl = DIFF_TAG_URL, diffCommitUrl = DIFF_COMMIT_URL}} = pkg
35
+ const {repoPublicUrl, repoName} = await getRepo(cwd, {basicAuth})
36
+ const prevTag = pkg.latest.tag?.ref
37
+ const vars = {repoName, repoPublicUrl, prevTag, newTag: tag, name, version}
38
+ const diffUrl = interpolate(diffTagUrl, vars)
39
+ const releaseDiffRef = `## [${name}@${version}](${diffUrl}) (${new Date().toISOString().slice(0, 10)})`
28
40
  const releaseDetails = Object.values(pkg.changes
29
41
  .reduce((acc, {group, subj, short, hash}) => {
30
42
  const {commits} = acc[group] || (acc[group] = {commits: [], group})
31
- const commitRef = `* ${subj}${short ? ` [${short}](${repoPublicUrl}/commit/${hash})` : ''}`
43
+ const commitUrl = interpolate(diffCommitUrl, {...vars, hash, short})
44
+ const commitRef = `* ${subj}${short ? ` [${short}](${commitUrl})` : ''}`
32
45
 
33
46
  commits.push(commitRef)
34
47
 
@@ -8,7 +8,7 @@ import {asArray, getCommonPath, msgJoin} from '../util.js'
8
8
 
9
9
  // https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release
10
10
  export const ghRelease = async (pkg) => {
11
- const {ghBasicAuth: basicAuth, ghToken, ghAssets} = pkg.config
11
+ const {ghBasicAuth: basicAuth, ghToken, ghAssets, ghApiUrl} = pkg.config
12
12
  if (!ghToken) return null
13
13
 
14
14
  log({pkg})('create gh release')
@@ -23,7 +23,7 @@ export const ghRelease = async (pkg) => {
23
23
  body: releaseNotes
24
24
  })
25
25
 
26
- const res = await (await fetch(`https://api.github.com/repos/${repoName}/releases`, {
26
+ const res = await (await fetch(`${ghApiUrl}/repos/${repoName}/releases`, {
27
27
  method: 'POST',
28
28
  headers: {
29
29
  Accept: 'application/vnd.github.v3+json',
@@ -122,8 +122,8 @@ export const ghUploadAssets = async ({ghToken, ghAssets, uploadUrl, cwd}) => {
122
122
  }))
123
123
  }
124
124
 
125
- export const ghGetAsset = async ({repoName, tag, name}) => {
126
- return (await fetch(`https://github.com/${repoName}/releases/download/${tag.ref || tag}/${name}`, {
125
+ export const ghGetAsset = async ({repoName, tag, name, ghUrl}) => {
126
+ return (await fetch(`${ghUrl || 'https://github.com'}/${repoName}/releases/download/${tag.ref || tag}/${name}`, {
127
127
  headers: {
128
128
  // Accept: 'application/vnd.github.v3+json'
129
129
  }
@@ -50,16 +50,26 @@ export const normalizePkgConfig = (config, env) => {
50
50
  }
51
51
 
52
52
  export const normalizeMetaConfig = (meta) =>
53
- meta === true
54
- ? normalizeMetaConfig('commit')
55
- : typeof meta === 'string'
56
- ? { type: meta } // 'commit' | 'asset' | 'tag'
57
- : { type: 'none' }
53
+ meta === false || meta === 'none'
54
+ ? { type: null }
55
+ : meta === true
56
+ ? normalizeMetaConfig('commit')
57
+ : typeof meta === 'string'
58
+ ? { type: meta } // 'commit' | 'asset' | 'tag'
59
+ : { type: null }
58
60
 
59
- export const parseEnv = ({GH_USER, GH_USERNAME, GH_META, GITHUB_USER, GITHUB_USERNAME, GH_TOKEN, GITHUB_TOKEN, NPM_TOKEN, NPM_REGISTRY, NPMRC, NPM_USERCONFIG, NPM_CONFIG_USERCONFIG, NPM_PROVENANCE, NPM_OIDC, ACTIONS_ID_TOKEN_REQUEST_URL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL} = process.env) =>
60
- ({
61
+ export const GH_URL = 'https://github.com'
62
+
63
+ const resolveGhApiUrl = (ghUrl) =>
64
+ ghUrl === GH_URL ? 'https://api.github.com' : `${ghUrl.replace(/\/$/, '')}/api/v3`
65
+
66
+ export const parseEnv = ({GH_USER, GH_USERNAME, GH_META, GH_URL: _GH_URL, GITHUB_URL, GITHUB_USER, GITHUB_USERNAME, GH_TOKEN, GITHUB_TOKEN, NPM_TOKEN, NPM_REGISTRY, NPMRC, NPM_USERCONFIG, NPM_CONFIG_USERCONFIG, NPM_PROVENANCE, NPM_OIDC, ACTIONS_ID_TOKEN_REQUEST_URL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL} = process.env) => {
67
+ const ghUrl = _GH_URL || GITHUB_URL || GH_URL
68
+ return {
61
69
  ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME || ((GH_TOKEN || GITHUB_TOKEN) ? 'x-access-token' : undefined),
62
70
  ghToken: GH_TOKEN || GITHUB_TOKEN,
71
+ ghUrl,
72
+ ghApiUrl: resolveGhApiUrl(ghUrl),
63
73
  ghMeta: GH_META,
64
74
  npmConfig: NPMRC || NPM_USERCONFIG || NPM_CONFIG_USERCONFIG,
65
75
  npmToken: NPM_TOKEN,
@@ -68,6 +78,7 @@ export const parseEnv = ({GH_USER, GH_USERNAME, GH_META, GITHUB_USER, GITHUB_USE
68
78
  npmRegistry: NPM_REGISTRY || 'https://registry.npmjs.org',
69
79
  gitCommitterName: GIT_COMMITTER_NAME || 'Semrel Extra Bot',
70
80
  gitCommitterEmail: GIT_COMMITTER_EMAIL || 'semrel-extra-bot@hotmail.com',
71
- })
81
+ }
82
+ }
72
83
 
73
84
  export const normalizeFlags = (flags = {}) => Object.entries(flags).reduce((acc, [k, v]) => ({...acc, [camelize(k)]: v}), {})
@@ -217,11 +217,11 @@ export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g,
217
217
 
218
218
  export const getLatestMeta = async (pkg, tag) => {
219
219
  if (tag) {
220
- const {absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg
220
+ const {absPath: cwd, config: {ghBasicAuth: basicAuth, ghUrl}} = pkg
221
221
  const {repoName} = await getRepo(cwd, {basicAuth})
222
222
 
223
223
  try {
224
- return JSON.parse(await ghGetAsset({repoName, tag, name: 'meta.json'}))
224
+ return JSON.parse(await ghGetAsset({repoName, tag, name: 'meta.json', ghUrl}))
225
225
  } catch {}
226
226
 
227
227
  try {
@@ -28,7 +28,7 @@ export const rollbackRelease = async (pkg) => {
28
28
  if (!tag) return
29
29
 
30
30
  const cwd = pkg.context.git.root
31
- const {ghBasicAuth: basicAuth, ghToken, gitCommitterName, gitCommitterEmail} = pkg.config
31
+ const {ghBasicAuth: basicAuth, ghToken, ghApiUrl, gitCommitterName, gitCommitterEmail} = pkg.config
32
32
  if (!basicAuth) throw new Error('rollback requires git credentials (GH_TOKEN)')
33
33
  const {repoName} = await getRepo(cwd, {basicAuth})
34
34
 
@@ -37,12 +37,12 @@ export const rollbackRelease = async (pkg) => {
37
37
  // 1. Delete GitHub release
38
38
  if (ghToken) {
39
39
  try {
40
- const res = await fetch(`https://api.github.com/repos/${repoName}/releases/tags/${tag}`, {
40
+ const res = await fetch(`${ghApiUrl}/repos/${repoName}/releases/tags/${tag}`, {
41
41
  headers: {Authorization: `token ${ghToken}`, 'X-GitHub-Api-Version': '2022-11-28'}
42
42
  })
43
43
  if (res.ok) {
44
44
  const {id} = await res.json()
45
- await fetch(`https://api.github.com/repos/${repoName}/releases/${id}`, {
45
+ await fetch(`${ghApiUrl}/repos/${repoName}/releases/${id}`, {
46
46
  method: 'DELETE',
47
47
  headers: {Authorization: `token ${ghToken}`, 'X-GitHub-Api-Version': '2022-11-28'}
48
48
  })
@@ -98,7 +98,7 @@ export const recover = async (pkg) => {
98
98
  if (manifest) return false
99
99
 
100
100
  const cwd = await getRoot(pkg.absPath)
101
- const {ghBasicAuth: basicAuth, ghToken, gitCommitterName, gitCommitterEmail} = pkg.config
101
+ const {ghBasicAuth: basicAuth, ghToken, ghApiUrl, gitCommitterName, gitCommitterEmail} = pkg.config
102
102
  if (!basicAuth) throw new Error('recover requires git credentials (GH_TOKEN)')
103
103
  const {repoName} = await getRepo(cwd, {basicAuth})
104
104
 
@@ -107,12 +107,12 @@ export const recover = async (pkg) => {
107
107
  // 1. Delete GitHub release (also removes attached meta assets)
108
108
  if (ghToken) {
109
109
  try {
110
- const res = await fetch(`https://api.github.com/repos/${repoName}/releases/tags/${tag.ref}`, {
110
+ const res = await fetch(`${ghApiUrl}/repos/${repoName}/releases/tags/${tag.ref}`, {
111
111
  headers: {Authorization: `token ${ghToken}`, 'X-GitHub-Api-Version': '2022-11-28'}
112
112
  })
113
113
  if (res.ok) {
114
114
  const {id} = await res.json()
115
- await fetch(`https://api.github.com/repos/${repoName}/releases/${id}`, {
115
+ await fetch(`${ghApiUrl}/repos/${repoName}/releases/${id}`, {
116
116
  method: 'DELETE',
117
117
  headers: {Authorization: `token ${ghToken}`, 'X-GitHub-Api-Version': '2022-11-28'}
118
118
  })