bulk-release 2.16.2 → 2.18.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 +10 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/main/js/api/git.js +7 -0
- package/src/main/js/steps/contextify.js +30 -1
- package/src/main/js/steps/publish.js +21 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [2.18.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.17.0...v2.18.0) (2026-04-05)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
* feat: introduce recovery mode ([2f15fd8](https://github.com/semrel-extra/zx-bulk-release/commit/2f15fd83cfb41a4a55a4ab12d880c00e24c5c717))
|
|
5
|
+
|
|
6
|
+
## [2.17.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.16.2...v2.17.0) (2026-04-05)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
* feat: rollback git tags on npm push issues ([3d55439](https://github.com/semrel-extra/zx-bulk-release/commit/3d55439a150f08bf2c0cf3d1edae9f327abce0e4))
|
|
10
|
+
|
|
1
11
|
## [2.16.2](https://github.com/semrel-extra/zx-bulk-release/compare/v2.16.1...v2.16.2) (2026-04-05)
|
|
2
12
|
|
|
3
13
|
### Fixes & improvements
|
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ GH_TOKEN=ghtoken GH_USER=username NPM_TOKEN=npmtoken npx zx-bulk-release [opts]
|
|
|
50
50
|
| `--dry-run` / `--no-publish` | Disable any publish logic | |
|
|
51
51
|
| `--report` | Persist release state to file | |
|
|
52
52
|
| `--snapshot` | Disable any publishing steps except of `npm` and `publishCmd` (if defined), then push packages to the `snapshot` channel | |
|
|
53
|
+
| `--recover` | Remove orphan git tags (tag pushed but npm publish failed) and retry release | |
|
|
53
54
|
| `--debug` | Enable [zx](https://github.com/google/zx#verbose) verbose mode | |
|
|
54
55
|
| `--version` / `-v` | Print own version | |
|
|
55
56
|
|
package/package.json
CHANGED
package/src/main/js/api/git.js
CHANGED
|
@@ -109,11 +109,18 @@ export const getTags = async (cwd, ref) =>
|
|
|
109
109
|
|
|
110
110
|
export const pushTag = async ({cwd, tag, gitCommitterName, gitCommitterEmail}) => {
|
|
111
111
|
await setUserConfig(cwd, gitCommitterName, gitCommitterEmail)
|
|
112
|
+
|
|
112
113
|
await $({cwd})`
|
|
113
114
|
git tag -m ${tag} ${tag} &&
|
|
114
115
|
git push origin ${tag}`
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
export const deleteRemoteTag = async ({cwd, tag}) => {
|
|
119
|
+
log()(`rolling back remote tag '${tag}'`)
|
|
120
|
+
await $({cwd, nothrow: true})`git push origin :refs/tags/${tag}`
|
|
121
|
+
await $({cwd, nothrow: true})`git tag -d ${tag}`
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
// Memoize prevents .git/config lock
|
|
118
125
|
// https://github.com/qiwi/packasso/actions/runs/4539987310/jobs/8000403413#step:7:282
|
|
119
126
|
export const setUserConfig = memoizeBy(async(cwd, gitCommitterName, gitCommitterEmail) => $({cwd})`
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import {getPkgConfig} from '../config.js'
|
|
2
2
|
import {getLatest} from '../processor/meta.js'
|
|
3
|
-
import {getRoot, getSha} from '../api/git.js'
|
|
3
|
+
import {getRoot, getSha, deleteRemoteTag} from '../api/git.js'
|
|
4
|
+
import {fetchManifest} from '../api/npm.js'
|
|
5
|
+
import {log} from '../log.js'
|
|
4
6
|
import {$} from 'zx-extra'
|
|
5
7
|
|
|
6
8
|
// Inspired by https://docs.github.com/en/actions/learn-github-actions/contexts
|
|
7
9
|
export const contextify = async (pkg, {packages, root, flags, env}) => {
|
|
8
10
|
pkg.config = await getPkgConfig([pkg.absPath, root.absPath], env)
|
|
9
11
|
pkg.latest = await getLatest(pkg)
|
|
12
|
+
|
|
13
|
+
if (flags.recover && pkg.latest.tag) {
|
|
14
|
+
await recover(pkg)
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
pkg.context = {
|
|
11
18
|
git: {
|
|
12
19
|
sha: await getSha(pkg.absPath),
|
|
@@ -17,3 +24,25 @@ export const contextify = async (pkg, {packages, root, flags, env}) => {
|
|
|
17
24
|
packages
|
|
18
25
|
}
|
|
19
26
|
}
|
|
27
|
+
|
|
28
|
+
// Verify that the latest tagged version actually exists on npm.
|
|
29
|
+
// If not (tag was pushed but npm publish failed), delete the orphan tag
|
|
30
|
+
// so the next analyze phase can re-detect changes and retry the release.
|
|
31
|
+
const recover = async (pkg) => {
|
|
32
|
+
const needsNpm = !pkg.manifest.private && pkg.config.npmPublish !== false
|
|
33
|
+
if (!needsNpm) return
|
|
34
|
+
|
|
35
|
+
const {tag} = pkg.latest
|
|
36
|
+
const manifest = await fetchManifest({
|
|
37
|
+
name: pkg.name,
|
|
38
|
+
version: tag.version,
|
|
39
|
+
config: pkg.config,
|
|
40
|
+
}, {nothrow: true})
|
|
41
|
+
|
|
42
|
+
if (!manifest) {
|
|
43
|
+
const cwd = await getRoot(pkg.absPath)
|
|
44
|
+
log({pkg})(`recover: tag '${tag.ref}' exists but ${pkg.name}@${tag.version} not found on npm, removing orphan tag`)
|
|
45
|
+
await deleteRemoteTag({cwd, tag: tag.ref})
|
|
46
|
+
pkg.latest = await getLatest(pkg)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -5,14 +5,11 @@ import {npmPersist, npmPublish} from '../api/npm.js'
|
|
|
5
5
|
import {prepareMeta, pushMeta, pushReleaseTag} from '../processor/meta.js'
|
|
6
6
|
import {pushChangelog} from '../api/changelog.js'
|
|
7
7
|
import {ghPages, ghRelease} from '../api/gh.js'
|
|
8
|
+
import {deleteRemoteTag} from '../api/git.js'
|
|
8
9
|
|
|
9
10
|
export const publish = memoizeBy(async (pkg, run = exec) => within(async () => {
|
|
10
11
|
$.scope = pkg.name
|
|
11
12
|
|
|
12
|
-
// Debug
|
|
13
|
-
// https://www.npmjs.com/package/@packasso/preset-ts-tsc-uvu/v/0.0.0?activeTab=code
|
|
14
|
-
// https://github.com/qiwi/packasso/actions/runs/4514909191/jobs/7951564982#step:7:817
|
|
15
|
-
// https://github.com/qiwi/packasso/blob/meta/2023-3-24-packasso-preset-ts-tsc-uvu-0-21-0-f0.json
|
|
16
13
|
if (pkg.version !== pkg.manifest.version) {
|
|
17
14
|
throw new Error('package.json version not synced')
|
|
18
15
|
}
|
|
@@ -27,14 +24,26 @@ export const publish = memoizeBy(async (pkg, run = exec) => within(async () => {
|
|
|
27
24
|
])
|
|
28
25
|
} else {
|
|
29
26
|
await pushReleaseTag(pkg)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
try {
|
|
28
|
+
await Promise.all([
|
|
29
|
+
pushMeta(pkg),
|
|
30
|
+
pushChangelog(pkg),
|
|
31
|
+
npmPublish(pkg),
|
|
32
|
+
ghRelease(pkg),
|
|
33
|
+
ghPages(pkg),
|
|
34
|
+
run(pkg, 'publishCmd')
|
|
35
|
+
])
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Rollback the tag only for npm-published packages so the next run can retry.
|
|
38
|
+
// Git-tag-only packages (private or npmPublish: false) keep their tag — it IS the release.
|
|
39
|
+
const needsNpm = !pkg.manifest.private && pkg.config.npmPublish !== false
|
|
40
|
+
const cwd = pkg.context.git.root
|
|
41
|
+
const tag = pkg.context.git.tag
|
|
42
|
+
if (tag && needsNpm) {
|
|
43
|
+
await deleteRemoteTag({cwd, tag})
|
|
44
|
+
}
|
|
45
|
+
throw e
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
48
|
pkg.published = true
|
|
40
49
|
}))
|