bulk-release 2.19.1 → 2.21.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 +25 -0
- package/README.md +72 -77
- package/package.json +3 -3
- package/src/main/js/config.js +12 -3
- package/src/main/js/index.js +0 -6
- package/src/main/js/processor/api/gh.js +111 -0
- package/src/main/js/{api → processor/api}/git.js +17 -26
- package/src/main/js/{api → processor/api}/npm.js +70 -28
- package/src/main/js/processor/deps.js +1 -1
- package/src/main/js/processor/exec.js +4 -4
- package/src/main/js/processor/generators/meta.js +80 -0
- package/src/main/js/processor/generators/notes.js +37 -0
- package/src/main/js/processor/{meta.js → generators/tag.js} +3 -109
- package/src/main/js/processor/log.js +86 -0
- package/src/main/js/processor/publishers/changelog.js +26 -0
- package/src/main/js/processor/publishers/cmd.js +6 -0
- package/src/main/js/processor/publishers/gh-pages.js +32 -0
- package/src/main/js/processor/publishers/gh-release.js +41 -0
- package/src/main/js/processor/publishers/meta.js +58 -0
- package/src/main/js/processor/publishers/npm.js +15 -0
- package/src/main/js/processor/release.js +71 -66
- package/src/main/js/{steps → processor/steps}/analyze.js +18 -24
- package/src/main/js/processor/steps/build.js +20 -0
- package/src/main/js/processor/steps/clean.js +7 -0
- package/src/main/js/processor/steps/contextify.js +49 -0
- package/src/main/js/processor/steps/publish.js +39 -0
- package/src/main/js/processor/steps/teardown.js +58 -0
- package/src/main/js/processor/steps/test.js +10 -0
- package/src/main/js/util.js +32 -77
- package/src/main/js/api/changelog.js +0 -42
- package/src/main/js/api/gh.js +0 -131
- package/src/main/js/log.js +0 -63
- package/src/main/js/steps/build.js +0 -23
- package/src/main/js/steps/clean.js +0 -7
- package/src/main/js/steps/contextify.js +0 -154
- package/src/main/js/steps/publish.js +0 -47
- package/src/main/js/steps/test.js +0 -16
|
@@ -5,7 +5,7 @@ export {traverseQueue, traverseDeps} from '@semrel-extra/topo'
|
|
|
5
5
|
|
|
6
6
|
export const updateDeps = async (pkg) => {
|
|
7
7
|
const changes = []
|
|
8
|
-
const {
|
|
8
|
+
const {packages} = pkg.ctx
|
|
9
9
|
|
|
10
10
|
await traverseDeps({pkg, packages, cb: async ({name, version, deps, scope, pkg: dep}) => {
|
|
11
11
|
const prev = pkg.latest.meta?.[scope]?.[name]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {tpl} from '../util.js'
|
|
2
|
-
import {log} from '
|
|
2
|
+
import {log} from './log.js'
|
|
3
3
|
import {$} from 'zx-extra'
|
|
4
4
|
|
|
5
5
|
export const exec = async (pkg, name) => {
|
|
6
|
-
const cmd = tpl(pkg.
|
|
6
|
+
const cmd = tpl(pkg.ctx.flags[name] ?? pkg.config[name], {...pkg, ...pkg.ctx})
|
|
7
7
|
const now = Date.now()
|
|
8
8
|
|
|
9
9
|
if (cmd) {
|
|
10
|
-
log(
|
|
10
|
+
log.info(`run ${name} '${cmd}'`)
|
|
11
11
|
const result = await $({cwd: pkg.absPath, quote: v => v, preferLocal: true})`${cmd}`
|
|
12
12
|
|
|
13
|
-
log(
|
|
13
|
+
log.info(`duration ${name}: ${Date.now() - now}`)
|
|
14
14
|
return result
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Meta generator: builds pkg.meta payload and resolves latest-release meta from git tags / gh assets / meta branch.
|
|
2
|
+
|
|
3
|
+
import {semver, $, fs, path} from 'zx-extra'
|
|
4
|
+
import {fetchRepo, getTags as getGitTags, getRepo} from '../api/git.js'
|
|
5
|
+
import {fetchManifest} from '../api/npm.js'
|
|
6
|
+
import {ghGetAsset} from '../api/gh.js'
|
|
7
|
+
import {parseTag} from './tag.js'
|
|
8
|
+
|
|
9
|
+
export const isAssetMode = (type) => type === 'asset' || type === 'assets'
|
|
10
|
+
|
|
11
|
+
// Build pkg.meta and, in asset mode, inject a meta.json entry into pkg.config.ghAssets.
|
|
12
|
+
// Runs in the prepare phase — serial, before any publisher.run() — so the asset mutation
|
|
13
|
+
// is guaranteed visible to gh-release.
|
|
14
|
+
export const prepareMeta = async (pkg) => {
|
|
15
|
+
const {type} = pkg.config.meta
|
|
16
|
+
if (type === null) return
|
|
17
|
+
|
|
18
|
+
const {absPath: cwd} = pkg
|
|
19
|
+
const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
20
|
+
pkg.meta = {
|
|
21
|
+
META_VERSION: '1',
|
|
22
|
+
hash,
|
|
23
|
+
name: pkg.name,
|
|
24
|
+
version: pkg.version,
|
|
25
|
+
dependencies: pkg.dependencies,
|
|
26
|
+
devDependencies: pkg.devDependencies,
|
|
27
|
+
peerDependencies: pkg.peerDependencies,
|
|
28
|
+
optionalDependencies: pkg.optionalDependencies,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isAssetMode(type)) {
|
|
32
|
+
pkg.config.ghAssets = [...pkg.config.ghAssets || [], {
|
|
33
|
+
name: 'meta.json',
|
|
34
|
+
contents: JSON.stringify(pkg.meta, null, 2),
|
|
35
|
+
}]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const getLatest = async (pkg) => {
|
|
40
|
+
const {absPath: cwd, name} = pkg
|
|
41
|
+
const tag = await getLatestTag(cwd, name)
|
|
42
|
+
const meta = await getLatestMeta(pkg, tag)
|
|
43
|
+
|
|
44
|
+
return {tag, meta}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const getTags = async (cwd, ref) =>
|
|
48
|
+
(await getGitTags(cwd, ref))
|
|
49
|
+
.map(tag => parseTag(tag.trim()))
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.sort((a, b) => semver.rcompare(a.version, b.version))
|
|
52
|
+
|
|
53
|
+
export const getLatestTag = async (cwd, name) =>
|
|
54
|
+
(await getTags(cwd)).find(tag => tag.name === name)
|
|
55
|
+
|
|
56
|
+
export const getLatestTaggedVersion = async (cwd, name) =>
|
|
57
|
+
(await getLatestTag(cwd, name))?.version || undefined
|
|
58
|
+
|
|
59
|
+
export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
60
|
+
|
|
61
|
+
export const getLatestMeta = async (pkg, tag) => {
|
|
62
|
+
if (tag) {
|
|
63
|
+
const {absPath: cwd, config: {ghBasicAuth: basicAuth, ghUrl}} = pkg
|
|
64
|
+
const {repoName} = await getRepo(cwd, {basicAuth})
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(await ghGetAsset({repoName, tag, name: 'meta.json', ghUrl}))
|
|
68
|
+
} catch {}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const _cwd = await fetchRepo({cwd, branch: 'meta', basicAuth})
|
|
72
|
+
return await Promise.any([
|
|
73
|
+
fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
|
|
74
|
+
fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
|
|
75
|
+
])
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return fetchManifest(pkg, {nothrow: true})
|
|
80
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Release notes formatting. Pure except for a single getRepo() call to resolve repoPublicUrl.
|
|
2
|
+
|
|
3
|
+
import {getRepo} from '../api/git.js'
|
|
4
|
+
import {formatTag} from './tag.js'
|
|
5
|
+
|
|
6
|
+
export const DIFF_TAG_URL = '${repoPublicUrl}/compare/${prevTag}...${newTag}'
|
|
7
|
+
export const DIFF_COMMIT_URL = '${repoPublicUrl}/commit/${hash}'
|
|
8
|
+
|
|
9
|
+
export const interpolate = (template, vars) => {
|
|
10
|
+
const result = template.replace(/\$\{(\w+)}/g, (_, key) => vars[key] ?? '')
|
|
11
|
+
try { new URL(result) } catch { throw new Error(`invalid URL after interpolation: '${result}' (template: '${template}')`) }
|
|
12
|
+
return result
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const formatReleaseNotes = async (pkg) => {
|
|
16
|
+
const {name, version, tag = formatTag({name, version}), absPath: cwd, config: {ghBasicAuth: basicAuth, diffTagUrl = DIFF_TAG_URL, diffCommitUrl = DIFF_COMMIT_URL}} = pkg
|
|
17
|
+
const {repoPublicUrl, repoName} = await getRepo(cwd, {basicAuth})
|
|
18
|
+
const prevTag = pkg.latest.tag?.ref
|
|
19
|
+
const vars = {repoName, repoPublicUrl, prevTag, newTag: tag, name, version}
|
|
20
|
+
const diffUrl = interpolate(diffTagUrl, vars)
|
|
21
|
+
const releaseDiffRef = `## [${name}@${version}](${diffUrl}) (${new Date().toISOString().slice(0, 10)})`
|
|
22
|
+
const releaseDetails = Object.values(pkg.changes
|
|
23
|
+
.reduce((acc, {group, subj, short, hash}) => {
|
|
24
|
+
const {commits} = acc[group] || (acc[group] = {commits: [], group})
|
|
25
|
+
const commitUrl = interpolate(diffCommitUrl, {...vars, hash, short})
|
|
26
|
+
const commitRef = `* ${subj}${short ? ` [${short}](${commitUrl})` : ''}`
|
|
27
|
+
|
|
28
|
+
commits.push(commitRef)
|
|
29
|
+
|
|
30
|
+
return acc
|
|
31
|
+
}, {}))
|
|
32
|
+
.map(({group, commits}) => `
|
|
33
|
+
### ${group}
|
|
34
|
+
${commits.join('\n')}`).join('\n')
|
|
35
|
+
|
|
36
|
+
return releaseDiffRef + '\n' + releaseDetails + '\n'
|
|
37
|
+
}
|
|
@@ -1,78 +1,7 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Pure tag parsing/formatting. No IO, no side effects.
|
|
2
2
|
|
|
3
3
|
import {Buffer} from 'node:buffer'
|
|
4
|
-
import {
|
|
5
|
-
import {semver, $, fs, path} from 'zx-extra'
|
|
6
|
-
import {log} from '../log.js'
|
|
7
|
-
import {fetchRepo, pushCommit, getTags as getGitTags, pushTag, getRepo} from '../api/git.js'
|
|
8
|
-
import {fetchManifest} from '../api/npm.js'
|
|
9
|
-
import {ghGetAsset} from '../api/gh.js'
|
|
10
|
-
|
|
11
|
-
export const pushReleaseTag = async (pkg) => {
|
|
12
|
-
const {name, version, tag = formatTag({name, version}), config: {gitCommitterEmail, gitCommitterName}} = pkg
|
|
13
|
-
const cwd = pkg.context.git.root
|
|
14
|
-
|
|
15
|
-
pkg.context.git.tag = tag
|
|
16
|
-
log({pkg})(`push release tag ${tag}`)
|
|
17
|
-
|
|
18
|
-
await pushTag({cwd, tag, gitCommitterEmail, gitCommitterName})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const prepareMeta = async (pkg) => {
|
|
22
|
-
const {absPath: cwd} = pkg
|
|
23
|
-
const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
24
|
-
pkg.meta = {
|
|
25
|
-
META_VERSION: '1',
|
|
26
|
-
hash,
|
|
27
|
-
name: pkg.name,
|
|
28
|
-
version: pkg.version,
|
|
29
|
-
dependencies: pkg.dependencies,
|
|
30
|
-
devDependencies: pkg.devDependencies,
|
|
31
|
-
peerDependencies: pkg.peerDependencies,
|
|
32
|
-
optionalDependencies: pkg.optionalDependencies,
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const pushMeta = queuefy(async (pkg) => {
|
|
37
|
-
const {type} = pkg.config.meta
|
|
38
|
-
|
|
39
|
-
if (type === null) {
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!pkg.meta) {
|
|
44
|
-
await prepareMeta(pkg)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (type === 'asset' || type === 'assets') {
|
|
48
|
-
pkg.config.ghAssets = [...pkg.config.ghAssets || [], {
|
|
49
|
-
name: 'meta.json',
|
|
50
|
-
contents: JSON.stringify(pkg.meta, null, 2)
|
|
51
|
-
}]
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
log({pkg})('push artifact to branch \'meta\'')
|
|
56
|
-
|
|
57
|
-
const {name, version, meta, tag = formatTag({name, version}), absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
58
|
-
const to = '.'
|
|
59
|
-
const branch = 'meta'
|
|
60
|
-
const msg = `chore: release meta ${name} ${version}`
|
|
61
|
-
const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
|
|
62
|
-
|
|
63
|
-
await pushCommit({cwd, to, branch, msg, files, gitCommitterEmail, gitCommitterName, basicAuth})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
export const getLatest = async (pkg) => {
|
|
67
|
-
const {absPath: cwd, name } = pkg
|
|
68
|
-
const tag = await getLatestTag(cwd, name)
|
|
69
|
-
const meta = await getLatestMeta(pkg, tag)
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
tag,
|
|
73
|
-
meta
|
|
74
|
-
}
|
|
75
|
-
}
|
|
4
|
+
import {semver} from 'zx-extra'
|
|
76
5
|
|
|
77
6
|
const isSafeName = n => /^(@?[a-z0-9-]+\/)?[a-z0-9-]+$/.test(n)
|
|
78
7
|
|
|
@@ -197,41 +126,6 @@ export const parseTag = (tag) => f0.parse(tag) || f1.parse(tag) || lerna.parse(t
|
|
|
197
126
|
|
|
198
127
|
export const formatTag = (tag, tagFormat = tag.format) => getFormatter(tagFormat).format(tag) || f0.format(tag) || f1.format(tag) || null
|
|
199
128
|
|
|
200
|
-
export const getTags = async (cwd, ref = '') =>
|
|
201
|
-
(await getGitTags(cwd, ref))
|
|
202
|
-
.map(tag => parseTag(tag.trim()))
|
|
203
|
-
.filter(Boolean)
|
|
204
|
-
.sort((a, b) => semver.rcompare(a.version, b.version))
|
|
205
|
-
|
|
206
|
-
export const getLatestTag = async (cwd, name) =>
|
|
207
|
-
(await getTags(cwd)).find(tag => tag.name === name)
|
|
208
|
-
|
|
209
|
-
export const getLatestTaggedVersion = async (cwd, name) =>
|
|
210
|
-
(await getLatestTag(cwd, name))?.version || undefined
|
|
211
|
-
|
|
212
129
|
export const formatDateTag = (date = new Date()) => `${date.getUTCFullYear()}.${date.getUTCMonth() + 1}.${date.getUTCDate()}`
|
|
213
130
|
|
|
214
|
-
export const parseDateTag = (date) => new Date(date.replaceAll('.', '-')+'Z')
|
|
215
|
-
|
|
216
|
-
export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
217
|
-
|
|
218
|
-
export const getLatestMeta = async (pkg, tag) => {
|
|
219
|
-
if (tag) {
|
|
220
|
-
const {absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg
|
|
221
|
-
const {repoName} = await getRepo(cwd, {basicAuth})
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
return JSON.parse(await ghGetAsset({repoName, tag, name: 'meta.json'}))
|
|
225
|
-
} catch {}
|
|
226
|
-
|
|
227
|
-
try {
|
|
228
|
-
const _cwd = await fetchRepo({cwd, branch: 'meta', basicAuth})
|
|
229
|
-
return await Promise.any([
|
|
230
|
-
fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
|
|
231
|
-
fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
|
|
232
|
-
])
|
|
233
|
-
} catch {}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return fetchManifest(pkg, {nothrow: true})
|
|
237
|
-
}
|
|
131
|
+
export const parseDateTag = (date) => new Date(date.replaceAll('.', '-') + 'Z')
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {$, fs} from 'zx-extra'
|
|
2
|
+
import {get, set} from '../util.js'
|
|
3
|
+
|
|
4
|
+
// Credential redactor: masks tokens/passwords that may leak into log output.
|
|
5
|
+
// Secrets are registered via `log.secret(value)` — typically once, when env/config is parsed.
|
|
6
|
+
const secrets = new Set()
|
|
7
|
+
export const redact = (v) => {
|
|
8
|
+
if (!secrets.size || typeof v !== 'string') return v
|
|
9
|
+
for (const s of secrets) v = v.replaceAll(s, '***')
|
|
10
|
+
return v
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Module-level logger. Delegates to $.report when inside a release run, falls back to console.
|
|
14
|
+
const sanitize = (args) => args.map(a => typeof a === 'string' ? redact(a) : a)
|
|
15
|
+
const r = () => $.report || console
|
|
16
|
+
|
|
17
|
+
export const log = Object.assign(
|
|
18
|
+
(...args) => r().log(...sanitize(args)),
|
|
19
|
+
{
|
|
20
|
+
info: (...args) => r().log(...sanitize(args)),
|
|
21
|
+
warn: (...args) => r().warn(...sanitize(args)),
|
|
22
|
+
error: (...args) => r().error(...sanitize(args)),
|
|
23
|
+
secret: (...values) => values.forEach(v => { if (v) secrets.add(String(v)) }),
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export const createReport = ({logger = console, packages = {}, queue = [], flags} = {}) => ({
|
|
28
|
+
logger,
|
|
29
|
+
flags,
|
|
30
|
+
file: flags.report || flags.file,
|
|
31
|
+
status: 'initial',
|
|
32
|
+
events: [],
|
|
33
|
+
queue,
|
|
34
|
+
packages: Object.entries(packages).reduce((acc, [name, {manifest: {version}, absPath, relPath}]) => {
|
|
35
|
+
acc[name] = {
|
|
36
|
+
status: 'initial',
|
|
37
|
+
name,
|
|
38
|
+
version,
|
|
39
|
+
path: absPath,
|
|
40
|
+
relPath
|
|
41
|
+
}
|
|
42
|
+
return acc
|
|
43
|
+
}, {}),
|
|
44
|
+
get(key, pkgName) {
|
|
45
|
+
return get(
|
|
46
|
+
pkgName ? this.packages[pkgName] : this,
|
|
47
|
+
key
|
|
48
|
+
)
|
|
49
|
+
},
|
|
50
|
+
set(key, value, pkgName) {
|
|
51
|
+
// set({k1: v1, k2: v2}, pkgName) — batch
|
|
52
|
+
if (key && typeof key === 'object') {
|
|
53
|
+
const target = value ? this.packages[value] : this
|
|
54
|
+
for (const [k, v] of Object.entries(key)) set(target, k, v)
|
|
55
|
+
return this
|
|
56
|
+
}
|
|
57
|
+
set(
|
|
58
|
+
pkgName ? this.packages[pkgName] : this,
|
|
59
|
+
key,
|
|
60
|
+
value
|
|
61
|
+
)
|
|
62
|
+
return this
|
|
63
|
+
},
|
|
64
|
+
setStatus(status, name) {
|
|
65
|
+
this.set('status', status, name)
|
|
66
|
+
this.save()
|
|
67
|
+
return this
|
|
68
|
+
},
|
|
69
|
+
getStatus(status, name) {
|
|
70
|
+
return this.get('status', name)
|
|
71
|
+
},
|
|
72
|
+
_log(level, ...chunks) {
|
|
73
|
+
const scope = $.scope || '~'
|
|
74
|
+
const msg = sanitize(chunks)
|
|
75
|
+
this.events.push({msg, scope, date: Date.now(), level})
|
|
76
|
+
logger[level === 'info' ? 'log' : level](`[${scope}]`, ...msg)
|
|
77
|
+
return this
|
|
78
|
+
},
|
|
79
|
+
log(...chunks) { return this._log('info', ...chunks) },
|
|
80
|
+
warn(...chunks) { return this._log('warn', ...chunks) },
|
|
81
|
+
error(...chunks) { return this._log('error', ...chunks) },
|
|
82
|
+
save() {
|
|
83
|
+
this.file && fs.outputJsonSync(this.file, this)
|
|
84
|
+
return this
|
|
85
|
+
}
|
|
86
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|