bulk-release 2.21.0 → 3.0.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 +22 -0
- package/README.md +140 -28
- package/package.json +12 -5
- package/src/main/js/index.js +1 -1
- package/src/main/js/{processor → post}/api/gh.js +0 -27
- package/src/main/js/{processor → post}/api/git.js +2 -10
- package/src/main/js/{processor → post}/api/npm.js +4 -2
- package/src/main/js/post/courier/channels/changelog.js +29 -0
- package/src/main/js/{processor/publishers → post/courier/channels}/cmd.js +1 -0
- package/src/main/js/post/courier/channels/gh-pages.js +30 -0
- package/src/main/js/post/courier/channels/gh-release.js +35 -0
- package/src/main/js/post/courier/channels/meta.js +34 -0
- package/src/main/js/post/courier/channels/npm.js +26 -0
- package/src/main/js/post/courier/index.js +113 -0
- package/src/main/js/post/courier/parcel.js +77 -0
- package/src/main/js/{processor → post/depot}/exec.js +2 -2
- package/src/main/js/{processor → post/depot}/generators/meta.js +3 -3
- package/src/main/js/{processor → post/depot}/generators/notes.js +1 -1
- package/src/main/js/{processor → post/depot}/steps/analyze.js +2 -2
- package/src/main/js/{processor → post/depot}/steps/build.js +2 -2
- package/src/main/js/{processor → post/depot}/steps/clean.js +2 -2
- package/src/main/js/{processor → post/depot}/steps/contextify.js +3 -3
- package/src/main/js/post/depot/steps/pack.js +59 -0
- package/src/main/js/post/depot/steps/publish.js +29 -0
- package/src/main/js/{processor → post/depot}/steps/test.js +1 -1
- package/src/main/js/{processor → post}/release.js +44 -37
- package/src/main/js/post/tar.js +73 -0
- package/src/test/js/utils/gh-server.js +33 -0
- package/src/test/js/utils/mock.js +132 -0
- package/src/test/js/{test-utils.js → utils/repo.js} +3 -3
- package/src/main/js/processor/publishers/changelog.js +0 -26
- package/src/main/js/processor/publishers/gh-pages.js +0 -32
- package/src/main/js/processor/publishers/gh-release.js +0 -41
- package/src/main/js/processor/publishers/meta.js +0 -58
- package/src/main/js/processor/publishers/npm.js +0 -15
- package/src/main/js/processor/steps/publish.js +0 -39
- package/src/main/js/processor/steps/teardown.js +0 -58
- /package/src/main/js/{processor → post/depot}/deps.js +0 -0
- /package/src/main/js/{processor → post/depot}/generators/tag.js +0 -0
- /package/src/main/js/{processor → post}/log.js +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {PassThrough} from 'node:stream'
|
|
2
|
+
import {EventEmitter} from 'node:events'
|
|
3
|
+
import {tempy} from 'zx-extra'
|
|
4
|
+
|
|
5
|
+
export const tmpDir = tempy.temporaryDirectory()
|
|
6
|
+
|
|
7
|
+
function proc(stdout = '', code = 0) {
|
|
8
|
+
const p = new EventEmitter()
|
|
9
|
+
p.stdout = new PassThrough()
|
|
10
|
+
p.stderr = new PassThrough()
|
|
11
|
+
p.stdin = new PassThrough()
|
|
12
|
+
p.pid = 1
|
|
13
|
+
process.nextTick(() => {
|
|
14
|
+
p.stdout.end(stdout)
|
|
15
|
+
p.stderr.end('')
|
|
16
|
+
p.emit('close', code, null)
|
|
17
|
+
p.emit('exit', code, null)
|
|
18
|
+
})
|
|
19
|
+
return p
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createMock(responses = []) {
|
|
23
|
+
const calls = []
|
|
24
|
+
const spawn = (cmd, args) => {
|
|
25
|
+
const command = args?.[args.length - 1] || cmd
|
|
26
|
+
calls.push(command)
|
|
27
|
+
for (const [pattern, output, code] of responses) {
|
|
28
|
+
if (typeof pattern === 'string' ? command.includes(pattern) : pattern.test(command))
|
|
29
|
+
return proc(output, code ?? 0)
|
|
30
|
+
}
|
|
31
|
+
return proc('')
|
|
32
|
+
}
|
|
33
|
+
return {spawn, calls}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const has = (calls, pattern) =>
|
|
37
|
+
calls.some(c => typeof pattern === 'string' ? c.includes(pattern) : pattern.test(c))
|
|
38
|
+
|
|
39
|
+
export const gitResponses = (overrides = {}) => [
|
|
40
|
+
['git rev-parse --show-toplevel', overrides.root ?? tmpDir],
|
|
41
|
+
['git rev-parse HEAD', overrides.sha ?? 'abc1234567890'],
|
|
42
|
+
['git config --get remote.origin.url', overrides.origin ?? 'https://github.com/test-org/test-repo.git'],
|
|
43
|
+
['git tag -l', overrides.tags ?? ''],
|
|
44
|
+
[/git log .+--format/, overrides.log ?? ''],
|
|
45
|
+
['git config user.name', ''],
|
|
46
|
+
['git config user.email', ''],
|
|
47
|
+
[/git tag -m/, ''],
|
|
48
|
+
['git push', ''],
|
|
49
|
+
['git fetch', ''],
|
|
50
|
+
['git clone', ''],
|
|
51
|
+
['git init', ''],
|
|
52
|
+
['git add', ''],
|
|
53
|
+
['git commit', ''],
|
|
54
|
+
[/git remote/, ''],
|
|
55
|
+
[/git config --unset/, ''],
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
export const npmResponses = (overrides = {}) => [
|
|
59
|
+
['npm --version', overrides.npmVersion ?? '10.0.0'],
|
|
60
|
+
['npm publish', ''],
|
|
61
|
+
['npm view', overrides.view ?? ''],
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export const defaultResponses = (overrides = {}) => [
|
|
65
|
+
...gitResponses(overrides),
|
|
66
|
+
...npmResponses(overrides),
|
|
67
|
+
[/curl/, overrides.curl ?? '{}'],
|
|
68
|
+
[/echo/, ''],
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
export const makePkg = (overrides = {}) => ({
|
|
72
|
+
name: overrides.name ?? 'test-pkg',
|
|
73
|
+
version: overrides.version ?? '1.0.1',
|
|
74
|
+
absPath: overrides.absPath ?? `${tmpDir}/packages/test-pkg`,
|
|
75
|
+
relPath: overrides.relPath ?? 'packages/test-pkg',
|
|
76
|
+
manifest: {
|
|
77
|
+
name: overrides.name ?? 'test-pkg',
|
|
78
|
+
version: overrides.version ?? '1.0.1',
|
|
79
|
+
private: overrides.private ?? false,
|
|
80
|
+
...overrides.manifest,
|
|
81
|
+
},
|
|
82
|
+
config: {
|
|
83
|
+
buildCmd: 'echo build',
|
|
84
|
+
testCmd: 'echo test',
|
|
85
|
+
gitCommitterName: 'Bot',
|
|
86
|
+
gitCommitterEmail: 'bot@test.com',
|
|
87
|
+
ghBasicAuth: 'x-access-token:ghp_test',
|
|
88
|
+
ghToken: 'ghp_test',
|
|
89
|
+
npmPublish: true,
|
|
90
|
+
npmFetch: false,
|
|
91
|
+
changelog: 'changelog',
|
|
92
|
+
...overrides.config,
|
|
93
|
+
},
|
|
94
|
+
changes: overrides.changes ?? [{group: 'Fixes', releaseType: 'patch', change: 'fix: test'}],
|
|
95
|
+
releaseType: overrides.releaseType ?? 'patch',
|
|
96
|
+
tag: overrides.tag ?? '2026.1.1-test-pkg.1.0.1-f0',
|
|
97
|
+
latest: overrides.latest ?? {tag: null, meta: null},
|
|
98
|
+
ctx: overrides.ctx ?? null,
|
|
99
|
+
...overrides.extra,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
export const makeCtx = (overrides = {}) => ({
|
|
103
|
+
cwd: overrides.cwd ?? tmpDir,
|
|
104
|
+
env: {PATH: process.env.PATH, HOME: process.env.HOME, GH_TOKEN: 'ghp_test', NPM_TOKEN: 'npm_test', ...overrides.env},
|
|
105
|
+
flags: {build: true, test: true, publish: true, ...overrides.flags},
|
|
106
|
+
root: {absPath: tmpDir, ...overrides.root},
|
|
107
|
+
packages: overrides.packages ?? {},
|
|
108
|
+
queue: overrides.queue ?? [],
|
|
109
|
+
prev: overrides.prev ?? {},
|
|
110
|
+
graphs: overrides.graphs ?? {},
|
|
111
|
+
report: overrides.report ?? makeReport(),
|
|
112
|
+
channels: overrides.channels ?? [],
|
|
113
|
+
run: overrides.run ?? (async () => {}),
|
|
114
|
+
git: {sha: 'abc1234567890', root: tmpDir, ...overrides.git},
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export const makeReport = () => {
|
|
118
|
+
const events = []
|
|
119
|
+
return {
|
|
120
|
+
status: 'initial',
|
|
121
|
+
events,
|
|
122
|
+
packages: {},
|
|
123
|
+
log(...chunks) { events.push({level: 'info', msg: chunks}); return this },
|
|
124
|
+
warn(...chunks) { events.push({level: 'warn', msg: chunks}); return this },
|
|
125
|
+
error(...chunks) { events.push({level: 'error', msg: chunks}); return this },
|
|
126
|
+
set() { return this },
|
|
127
|
+
get() { return this },
|
|
128
|
+
setStatus() { return this },
|
|
129
|
+
getStatus() { return this },
|
|
130
|
+
save() { return this },
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -2,7 +2,7 @@ import {fs, path, tempy, $, sleep} from 'zx-extra'
|
|
|
2
2
|
import {fileURLToPath} from 'node:url'
|
|
3
3
|
|
|
4
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
5
|
-
export const fixtures = path.resolve(__dirname, '
|
|
5
|
+
export const fixtures = path.resolve(__dirname, '../../fixtures')
|
|
6
6
|
|
|
7
7
|
export const createNpmRegistry = () => {
|
|
8
8
|
let p
|
|
@@ -10,8 +10,8 @@ export const createNpmRegistry = () => {
|
|
|
10
10
|
return {
|
|
11
11
|
address: $.env.NPM_REGISTRY || 'http://localhost:4873',
|
|
12
12
|
async start() {
|
|
13
|
-
fs.removeSync(path.resolve(__dirname, '
|
|
14
|
-
const config = path.resolve(__dirname, '
|
|
13
|
+
fs.removeSync(path.resolve(__dirname, '../../../../storage'))
|
|
14
|
+
const config = path.resolve(__dirname, '../../../../verdaccio.config.yaml')
|
|
15
15
|
p = $({preferLocal: true})`verdaccio --config ${config}`
|
|
16
16
|
|
|
17
17
|
return sleep(1000)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {$} from 'zx-extra'
|
|
2
|
-
import {queuefy} from 'queuefy'
|
|
3
|
-
import {fetchRepo, pushCommit} from '../api/git.js'
|
|
4
|
-
import {formatReleaseNotes} from '../generators/notes.js'
|
|
5
|
-
import {log} from '../log.js'
|
|
6
|
-
import {asTuple, msgJoin} from '../../util.js'
|
|
7
|
-
|
|
8
|
-
const run = queuefy(async (pkg) => {
|
|
9
|
-
const {absPath: cwd, config: {changelog: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
10
|
-
if (!opts) return
|
|
11
|
-
|
|
12
|
-
log.info('push changelog')
|
|
13
|
-
const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = asTuple(opts, ['branch', 'file', 'msg'])
|
|
14
|
-
const _cwd = await fetchRepo({cwd, branch, basicAuth})
|
|
15
|
-
const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
|
|
16
|
-
const releaseNotes = await formatReleaseNotes(pkg)
|
|
17
|
-
|
|
18
|
-
await $({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
|
|
19
|
-
await pushCommit({cwd, branch, msg, gitCommitterEmail, gitCommitterName, basicAuth})
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
export default {
|
|
23
|
-
name: 'changelog',
|
|
24
|
-
when: (pkg) => !!pkg.config.changelog,
|
|
25
|
-
run,
|
|
26
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {queuefy} from 'queuefy'
|
|
2
|
-
import {path} from 'zx-extra'
|
|
3
|
-
import {log} from '../log.js'
|
|
4
|
-
import {pushCommit} from '../api/git.js'
|
|
5
|
-
import {asTuple, msgJoin} from '../../util.js'
|
|
6
|
-
|
|
7
|
-
const run = queuefy(async (pkg) => {
|
|
8
|
-
const {config: {ghPages: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
9
|
-
if (!opts) return
|
|
10
|
-
|
|
11
|
-
const [branch = 'gh-pages', from = 'docs', to = '.', ..._msg] = asTuple(opts, ['branch', 'from', 'to', 'msg'])
|
|
12
|
-
const msg = msgJoin(_msg, pkg, 'docs: update docs ${{name}} ${{version}}')
|
|
13
|
-
|
|
14
|
-
log.info(`publish docs to ${branch}`)
|
|
15
|
-
|
|
16
|
-
await pushCommit({
|
|
17
|
-
cwd: path.join(pkg.absPath, from),
|
|
18
|
-
from: '.',
|
|
19
|
-
to,
|
|
20
|
-
branch,
|
|
21
|
-
msg,
|
|
22
|
-
gitCommitterEmail,
|
|
23
|
-
gitCommitterName,
|
|
24
|
-
basicAuth,
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
export default {
|
|
29
|
-
name: 'gh-pages',
|
|
30
|
-
when: (pkg) => !!pkg.config.ghPages,
|
|
31
|
-
run,
|
|
32
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import {log} from '../log.js'
|
|
2
|
-
import {getRepo, getRoot} from '../api/git.js'
|
|
3
|
-
import {ghCreateRelease, ghDeleteReleaseByTag, ghUploadAssets} from '../api/gh.js'
|
|
4
|
-
import {formatTag} from '../generators/tag.js'
|
|
5
|
-
import {formatReleaseNotes} from '../generators/notes.js'
|
|
6
|
-
|
|
7
|
-
const run = async (pkg) => {
|
|
8
|
-
const {ghBasicAuth: basicAuth, ghToken, ghAssets, ghApiUrl} = pkg.config
|
|
9
|
-
if (!ghToken) return null
|
|
10
|
-
|
|
11
|
-
log.info('create gh release')
|
|
12
|
-
|
|
13
|
-
const now = Date.now()
|
|
14
|
-
const {name, version, absPath: cwd, tag = formatTag({name, version})} = pkg
|
|
15
|
-
const {repoName} = await getRepo(cwd, {basicAuth})
|
|
16
|
-
const body = await formatReleaseNotes(pkg)
|
|
17
|
-
const res = await ghCreateRelease({ghApiUrl, ghToken, repoName, tag, body})
|
|
18
|
-
|
|
19
|
-
if (ghAssets?.length) {
|
|
20
|
-
// GH API returns a pseudo-url `...releases/110103594/assets{?name,label}` — strip the template.
|
|
21
|
-
const uploadUrl = res.upload_url.slice(0, res.upload_url.indexOf('{'))
|
|
22
|
-
await ghUploadAssets({ghToken, ghAssets, uploadUrl, cwd})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
log.info(`duration gh release: ${Date.now() - now}`)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const undo = async (pkg, {tag}) => {
|
|
29
|
-
const {ghBasicAuth: basicAuth, ghToken, ghApiUrl} = pkg.config
|
|
30
|
-
if (!ghToken) return
|
|
31
|
-
const cwd = await getRoot(pkg.absPath)
|
|
32
|
-
const {repoName} = await getRepo(cwd, {basicAuth})
|
|
33
|
-
await ghDeleteReleaseByTag({ghApiUrl, ghToken, repoName, tag})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default {
|
|
37
|
-
name: 'gh-release',
|
|
38
|
-
when: (pkg) => !!pkg.config.ghToken,
|
|
39
|
-
run,
|
|
40
|
-
undo,
|
|
41
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Meta publisher: pushes meta artifacts to the `meta` branch and undoes them on rollback.
|
|
2
|
-
// The meta payload itself is built by the generator (../generators/meta.js).
|
|
3
|
-
|
|
4
|
-
import {queuefy} from 'queuefy'
|
|
5
|
-
import {fs, path} from 'zx-extra'
|
|
6
|
-
import {log} from '../log.js'
|
|
7
|
-
import {fetchRepo, pushCommit} from '../api/git.js'
|
|
8
|
-
import {formatTag} from '../generators/tag.js'
|
|
9
|
-
import {prepareMeta, getArtifactPath, isAssetMode} from '../generators/meta.js'
|
|
10
|
-
|
|
11
|
-
// Push the meta artifact to the `meta` branch. No-op in asset mode (handled by gh-release).
|
|
12
|
-
const pushMetaBranch = queuefy(async (pkg) => {
|
|
13
|
-
const {type} = pkg.config.meta
|
|
14
|
-
if (type === null || isAssetMode(type)) return
|
|
15
|
-
|
|
16
|
-
log.info('push artifact to branch \'meta\'')
|
|
17
|
-
|
|
18
|
-
const {name, version, meta, tag = formatTag({name, version}), absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
19
|
-
const msg = `chore: release meta ${name} ${version}`
|
|
20
|
-
const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
|
|
21
|
-
|
|
22
|
-
await pushCommit({cwd, to: '.', branch: 'meta', msg, files, gitCommitterEmail, gitCommitterName, basicAuth})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
// Remove a meta artifact for a given tag from the meta branch.
|
|
26
|
-
// Returns true if something was removed and pushed.
|
|
27
|
-
const removeMetaArtifact = async (pkg, {tag, version, reason}) => {
|
|
28
|
-
const {config: {meta: {type}, ghBasicAuth: basicAuth, gitCommitterName, gitCommitterEmail}} = pkg
|
|
29
|
-
// Asset-mode meta lives inside the GH release; deleting the release already removes it.
|
|
30
|
-
if (type === null || isAssetMode(type)) return false
|
|
31
|
-
|
|
32
|
-
const cwd = pkg.ctx?.git?.root || pkg.absPath
|
|
33
|
-
const metaCwd = await fetchRepo({cwd, branch: 'meta', basicAuth})
|
|
34
|
-
const artifactPath = getArtifactPath(tag)
|
|
35
|
-
const candidates = [
|
|
36
|
-
path.resolve(metaCwd, `${artifactPath}.json`),
|
|
37
|
-
path.resolve(metaCwd, artifactPath),
|
|
38
|
-
]
|
|
39
|
-
let removed = false
|
|
40
|
-
for (const p of candidates) {
|
|
41
|
-
if (fs.existsSync(p)) {
|
|
42
|
-
await fs.remove(p)
|
|
43
|
-
removed = true
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (removed) {
|
|
47
|
-
await pushCommit({cwd, branch: 'meta', msg: `chore: ${reason} ${pkg.name} ${version}`, gitCommitterName, gitCommitterEmail, basicAuth})
|
|
48
|
-
}
|
|
49
|
-
return removed
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default {
|
|
53
|
-
name: 'meta',
|
|
54
|
-
when: (pkg) => pkg.config.meta.type !== null,
|
|
55
|
-
prepare: prepareMeta,
|
|
56
|
-
run: pushMetaBranch,
|
|
57
|
-
undo: removeMetaArtifact,
|
|
58
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {npmPublish} from '../api/npm.js'
|
|
2
|
-
|
|
3
|
-
// Domain predicate: does this package get published to npm?
|
|
4
|
-
// Private packages and those with `npmPublish: false` are git-tag-only —
|
|
5
|
-
// the tag itself IS the release.
|
|
6
|
-
export const isNpmPublished = (pkg) =>
|
|
7
|
-
!pkg.manifest.private && pkg.config.npmPublish !== false
|
|
8
|
-
|
|
9
|
-
export default {
|
|
10
|
-
name: 'npm',
|
|
11
|
-
when: isNpmPublished,
|
|
12
|
-
run: (pkg) => npmPublish(pkg),
|
|
13
|
-
snapshot: true,
|
|
14
|
-
// No undo: npm unpublish is unreliable (time-limited, cached by mirrors).
|
|
15
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import {memoizeBy} from '../../util.js'
|
|
2
|
-
import {exec} from '../exec.js'
|
|
3
|
-
import {log} from '../log.js'
|
|
4
|
-
import {npmPersist} from '../api/npm.js'
|
|
5
|
-
import {pushTag} from '../api/git.js'
|
|
6
|
-
import {formatTag} from '../generators/tag.js'
|
|
7
|
-
import {isNpmPublished} from '../publishers/npm.js'
|
|
8
|
-
import {rollbackRelease} from './teardown.js'
|
|
9
|
-
|
|
10
|
-
const pushReleaseTag = async (pkg, ctx) => {
|
|
11
|
-
const {name, version, tag = formatTag({name, version}), config: {gitCommitterEmail, gitCommitterName}} = pkg
|
|
12
|
-
ctx.git.tag = tag
|
|
13
|
-
log.info(`push release tag ${tag}`)
|
|
14
|
-
await pushTag({cwd: ctx.git.root, tag, gitCommitterEmail, gitCommitterName})
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const publish = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
18
|
-
if (pkg.version !== pkg.manifest.version)
|
|
19
|
-
throw new Error('package.json version not synced')
|
|
20
|
-
|
|
21
|
-
const {run = exec, publishers = [], flags} = ctx
|
|
22
|
-
const snapshot = !!flags.snapshot
|
|
23
|
-
const active = publishers.filter(p => (!snapshot || p.snapshot) && p.when(pkg))
|
|
24
|
-
|
|
25
|
-
await npmPersist(pkg)
|
|
26
|
-
|
|
27
|
-
// Prepare phase: serial pkg mutations (e.g. meta injects into ghAssets) — must finish before any run().
|
|
28
|
-
for (const p of active) await p.prepare?.(pkg)
|
|
29
|
-
|
|
30
|
-
if (!snapshot) await pushReleaseTag(pkg, ctx)
|
|
31
|
-
try {
|
|
32
|
-
await Promise.all(active.map(p => p.run(pkg, run)))
|
|
33
|
-
} catch (e) {
|
|
34
|
-
// Roll back full release for npm-published packages; git-tag-only packages keep their tag — it IS the release.
|
|
35
|
-
if (!snapshot && isNpmPublished(pkg)) await rollbackRelease(pkg, ctx)
|
|
36
|
-
throw e
|
|
37
|
-
}
|
|
38
|
-
pkg.published = true
|
|
39
|
-
})
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Release teardown: undo a published (or half-published) release.
|
|
2
|
-
//
|
|
3
|
-
// Two entry points share the same core:
|
|
4
|
-
// - rollbackRelease: called inline from publish.js on mid-publish failure (tag known from pkg.ctx).
|
|
5
|
-
// - recover: standalone --recover mode — detect orphan tags (tagged but missing on npm) and tear them down.
|
|
6
|
-
//
|
|
7
|
-
// Teardown walks the publishers registry in reverse and calls undo() on each that applies.
|
|
8
|
-
|
|
9
|
-
import {log} from '../log.js'
|
|
10
|
-
import {deleteRemoteTag} from '../api/git.js'
|
|
11
|
-
import {fetchManifest} from '../api/npm.js'
|
|
12
|
-
import {isNpmPublished} from '../publishers/npm.js'
|
|
13
|
-
|
|
14
|
-
// Tear down a release: undo every applicable publisher, then delete the git tag.
|
|
15
|
-
// Failures in individual undo steps are warned, not thrown — teardown is best-effort.
|
|
16
|
-
const teardownRelease = async (pkg, ctx, {tag, version, reason}) => {
|
|
17
|
-
if (!pkg.config.ghBasicAuth) throw new Error(`${reason} requires git credentials (GH_TOKEN)`)
|
|
18
|
-
|
|
19
|
-
for (const p of [...ctx.publishers].reverse()) {
|
|
20
|
-
if (!p.undo || !p.when(pkg)) continue
|
|
21
|
-
try {
|
|
22
|
-
const result = await p.undo(pkg, {tag, version, reason})
|
|
23
|
-
if (result !== false) log.info(`${reason}: ${p.name} undone for '${tag}'`)
|
|
24
|
-
} catch (e) {
|
|
25
|
-
log.warn(`${reason}: ${p.name} undo failed`, e)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
await deleteRemoteTag({cwd: ctx.git.root, tag})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Rollback a release that failed mid-publish (called inline from publish.js).
|
|
33
|
-
// Uses the current release tag; skips the npm existence check — we already know it failed.
|
|
34
|
-
export const rollbackRelease = async (pkg, ctx = pkg.ctx) => {
|
|
35
|
-
const tag = ctx.git.tag
|
|
36
|
-
if (!tag) return
|
|
37
|
-
log.info(`rollback: cleaning up failed release for tag '${tag}'`)
|
|
38
|
-
await teardownRelease(pkg, ctx, {tag, version: pkg.version, reason: 'rollback'})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Standalone recovery: if a tag exists but the package is missing from npm, treat it as an orphan and tear down.
|
|
42
|
-
export const recover = async (pkg, ctx = pkg.ctx) => {
|
|
43
|
-
if (!isNpmPublished(pkg)) return false
|
|
44
|
-
|
|
45
|
-
const {tag} = pkg.latest
|
|
46
|
-
if (!tag) return false
|
|
47
|
-
|
|
48
|
-
const manifest = await fetchManifest({
|
|
49
|
-
name: pkg.name,
|
|
50
|
-
version: tag.version,
|
|
51
|
-
config: pkg.config,
|
|
52
|
-
}, {nothrow: true})
|
|
53
|
-
if (manifest) return false
|
|
54
|
-
|
|
55
|
-
log.info(`recover: tag '${tag.ref}' exists but ${pkg.name}@${tag.version} not found on npm, rolling back failed release`)
|
|
56
|
-
await teardownRelease(pkg, ctx, {tag: tag.ref, version: tag.version, reason: 'recover'})
|
|
57
|
-
return true
|
|
58
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|