zx-bulk-release 2.1.6 → 2.2.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 +8 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/main/js/analyze.js +5 -18
- package/src/main/js/changelog.js +42 -0
- package/src/main/js/config.js +10 -6
- package/src/main/js/{tag.js → meta.js} +57 -6
- package/src/main/js/npm.js +7 -8
- package/src/main/js/processor.js +87 -20
- package/src/main/js/publish.js +13 -97
- package/src/main/js/repo.js +24 -10
- package/src/main/js/util.js +2 -0
- package/src/main/js/state.js +0 -56
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [2.2.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.1.6...v2.2.0) (2023-03-23)
|
|
2
|
+
|
|
3
|
+
### Fixes & improvements
|
|
4
|
+
* fix: fix basicAuth tpl ([b6af2f5](https://github.com/semrel-extra/zx-bulk-release/commit/b6af2f51a2e13ddb5a3b6b1ef288595aae61e746))
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
* feat: inherit gh and npm creds from pkg release configs ([f07dd4f](https://github.com/semrel-extra/zx-bulk-release/commit/f07dd4fa0cf9d26942780ccf0c34406b345dae39))
|
|
8
|
+
|
|
1
9
|
## [2.1.6](https://github.com/semrel-extra/zx-bulk-release/compare/v2.1.5...v2.1.6) (2023-03-22)
|
|
2
10
|
|
|
3
11
|
### Fixes & improvements
|
package/README.md
CHANGED
|
@@ -146,9 +146,10 @@ By default, it omits the packages marked as `private`. You can override this by
|
|
|
146
146
|
|
|
147
147
|
### `analyze`
|
|
148
148
|
Determines pkg changes, release type, next version etc.
|
|
149
|
+
|
|
149
150
|
```js
|
|
150
151
|
export const analyze = async (pkg, packages, root) => {
|
|
151
|
-
pkg.config = await
|
|
152
|
+
pkg.config = await getPkgConfig(pkg.absPath, root.absPath)
|
|
152
153
|
pkg.latest = await getLatest(pkg)
|
|
153
154
|
|
|
154
155
|
const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
|
package/package.json
CHANGED
package/src/main/js/analyze.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {semver} from 'zx-extra'
|
|
2
2
|
import {updateDeps} from './deps.js'
|
|
3
|
-
import {formatTag} from './
|
|
3
|
+
import {formatTag} from './meta.js';
|
|
4
4
|
import {log} from './log.js'
|
|
5
|
+
import {getCommits} from './repo.js'
|
|
5
6
|
|
|
6
7
|
export const analyze = async (pkg, packages) => {
|
|
7
8
|
const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
|
|
@@ -25,22 +26,8 @@ export const semanticRules = [
|
|
|
25
26
|
{group: 'BREAKING CHANGES', releaseType: 'major', keywords: ['BREAKING CHANGE', 'BREAKING CHANGES']},
|
|
26
27
|
]
|
|
27
28
|
|
|
28
|
-
export const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
$.cwd = cwd
|
|
32
|
-
return (await $.raw`git log ${range} --format=+++%s__%b__%h__%H -- .`)
|
|
33
|
-
.toString()
|
|
34
|
-
.split('+++')
|
|
35
|
-
.filter(Boolean)
|
|
36
|
-
.map(msg => {
|
|
37
|
-
const [subj, body, short, hash] = msg.split('__').map(raw => raw.trim())
|
|
38
|
-
return {subj, body, short, hash}
|
|
39
|
-
})
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
export const getSemanticChanges = async (cwd, since) => {
|
|
43
|
-
const commits = await getPkgCommits(cwd, since)
|
|
29
|
+
export const getSemanticChanges = async (cwd, from, to) => {
|
|
30
|
+
const commits = await getCommits(cwd, from, to)
|
|
44
31
|
|
|
45
32
|
return analyzeCommits(commits)
|
|
46
33
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {$} from 'zx-extra'
|
|
2
|
+
import {log} from './log.js'
|
|
3
|
+
import {fetchRepo, getRepo, pushCommit} from './repo.js'
|
|
4
|
+
import {msgJoin} from './util.js'
|
|
5
|
+
import {formatTag} from './meta.js'
|
|
6
|
+
|
|
7
|
+
export const pushChangelog = async (pkg) => {
|
|
8
|
+
const {absPath: cwd, config: {changelog: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
9
|
+
if (!opts) return
|
|
10
|
+
|
|
11
|
+
log({pkg})('push changelog')
|
|
12
|
+
const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = typeof opts === 'string'
|
|
13
|
+
? opts.split(' ')
|
|
14
|
+
: [opts.branch, opts.file, opts.msg]
|
|
15
|
+
const _cwd = await fetchRepo({cwd, branch, basicAuth})
|
|
16
|
+
const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
|
|
17
|
+
const releaseNotes = await formatReleaseNotes(pkg)
|
|
18
|
+
|
|
19
|
+
await $.o({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
|
|
20
|
+
await pushCommit({cwd, branch, msg, gitCommitterEmail, gitCommitterName, basicAuth})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const formatReleaseNotes = async (pkg) => {
|
|
24
|
+
const {name, version, absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg
|
|
25
|
+
const {repoPublicUrl} = await getRepo(cwd, {basicAuth})
|
|
26
|
+
const tag = formatTag({name, version})
|
|
27
|
+
const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})`
|
|
28
|
+
const releaseDetails = Object.values(pkg.changes
|
|
29
|
+
.reduce((acc, {group, subj, short, hash}) => {
|
|
30
|
+
const {commits} = acc[group] || (acc[group] = {commits: [], group})
|
|
31
|
+
const commitRef = `* ${subj}${short ? ` [${short}](${repoPublicUrl}/commit/${hash})` : ''}`
|
|
32
|
+
|
|
33
|
+
commits.push(commitRef)
|
|
34
|
+
|
|
35
|
+
return acc
|
|
36
|
+
}, {}))
|
|
37
|
+
.map(({group, commits}) => `
|
|
38
|
+
### ${group}
|
|
39
|
+
${commits.join('\n')}`).join('\n')
|
|
40
|
+
|
|
41
|
+
return releaseDiffRef + '\n' + releaseDetails + '\n'
|
|
42
|
+
}
|
package/src/main/js/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cosmiconfig } from 'cosmiconfig'
|
|
2
|
+
import { camelize } from './util.js'
|
|
2
3
|
|
|
3
4
|
const CONFIG_NAME = 'release'
|
|
4
5
|
const CONFIG_FILES = [
|
|
@@ -15,22 +16,26 @@ const CONFIG_FILES = [
|
|
|
15
16
|
|
|
16
17
|
export const defaultConfig = {
|
|
17
18
|
cmd: 'yarn && yarn build && yarn test',
|
|
18
|
-
npmFetch: true,
|
|
19
19
|
changelog: 'changelog',
|
|
20
|
+
npmFetch: true,
|
|
20
21
|
ghRelease: true,
|
|
21
22
|
// npmPublish: true,
|
|
22
23
|
// ghPages: 'gh-pages'
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export const
|
|
26
|
-
|
|
26
|
+
export const getPkgConfig = async (...cwds) =>
|
|
27
|
+
normalizePkgConfig((await Promise.all(cwds.map(
|
|
27
28
|
cwd => cosmiconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILES }).search(cwd).then(r => r?.config)
|
|
28
29
|
))).find(Boolean) || defaultConfig)
|
|
29
30
|
|
|
30
|
-
export const
|
|
31
|
+
export const normalizePkgConfig = (config, env) => ({
|
|
32
|
+
...parseEnv(env),
|
|
31
33
|
...config,
|
|
32
34
|
npmFetch: config.npmFetch || config.fetch || config.fetchPkg,
|
|
33
|
-
buildCmd: config.buildCmd || config.cmd
|
|
35
|
+
buildCmd: config.buildCmd || config.cmd,
|
|
36
|
+
get ghBasicAuth() {
|
|
37
|
+
return this.ghUser && this.ghToken ? `${this.ghUser}:${this.ghToken}` : false
|
|
38
|
+
}
|
|
34
39
|
})
|
|
35
40
|
|
|
36
41
|
export const parseEnv = (env = process.env) => {
|
|
@@ -47,5 +52,4 @@ export const parseEnv = (env = process.env) => {
|
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
const camelize = s => s.replace(/-./g, x => x[1].toUpperCase())
|
|
51
55
|
export const normalizeFlags = (flags = {}) => Object.entries(flags).reduce((acc, [k, v]) => ({...acc, [camelize(k)]: v}), {})
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Semantic tags processing
|
|
2
2
|
|
|
3
|
-
import {ctx, semver,
|
|
3
|
+
import {ctx, semver, $, fs, path} from 'zx-extra'
|
|
4
4
|
import {Buffer} from 'buffer'
|
|
5
|
-
import {parseEnv} from './config.js'
|
|
6
5
|
import {log} from './log.js'
|
|
6
|
+
import {fetchRepo, pushCommit} from './repo.js'
|
|
7
|
+
import {fetchManifest} from './npm.js'
|
|
7
8
|
|
|
8
9
|
export const pushTag = (pkg) => ctx(async ($) => {
|
|
9
|
-
const {absPath: cwd, name, version} = pkg
|
|
10
|
+
const {absPath: cwd, name, version, config: {gitCommitterEmail, gitCommitterName}} = pkg
|
|
10
11
|
const tag = formatTag({name, version})
|
|
11
|
-
const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
|
|
12
12
|
|
|
13
13
|
pkg.context.git.tag = tag
|
|
14
14
|
log({pkg})(`push release tag ${tag}`)
|
|
@@ -20,6 +20,41 @@ export const pushTag = (pkg) => ctx(async ($) => {
|
|
|
20
20
|
await $`git push origin ${tag}`
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
+
export const pushMeta = async (pkg) => {
|
|
24
|
+
log({pkg})('push artifact to branch \'meta\'')
|
|
25
|
+
|
|
26
|
+
const {name, version, absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
27
|
+
const tag = formatTag({name, version})
|
|
28
|
+
const to = '.'
|
|
29
|
+
const branch = 'meta'
|
|
30
|
+
const msg = `chore: release meta ${name} ${version}`
|
|
31
|
+
const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
32
|
+
const meta = {
|
|
33
|
+
META_VERSION: '1',
|
|
34
|
+
name: pkg.name,
|
|
35
|
+
hash,
|
|
36
|
+
version: pkg.version,
|
|
37
|
+
dependencies: pkg.dependencies,
|
|
38
|
+
devDependencies: pkg.devDependencies,
|
|
39
|
+
peerDependencies: pkg.peerDependencies,
|
|
40
|
+
optionalDependencies: pkg.optionalDependencies,
|
|
41
|
+
}
|
|
42
|
+
const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
|
|
43
|
+
|
|
44
|
+
await pushCommit({cwd, to, branch, msg, files, gitCommitterEmail, gitCommitterName, basicAuth})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const getLatest = async (pkg) => {
|
|
48
|
+
const {absPath: cwd, name} = pkg
|
|
49
|
+
const tag = await getLatestTag(cwd, name)
|
|
50
|
+
const meta = await getLatestMeta(cwd, tag?.ref) || await fetchManifest(pkg, {nothrow: true})
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
tag,
|
|
54
|
+
meta
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
const f0 = {
|
|
24
59
|
parse(tag) {
|
|
25
60
|
if (!tag.endsWith('-f0')) return null
|
|
@@ -102,8 +137,8 @@ export const parseTag = (tag) => f0.parse(tag) || f1.parse(tag) || lerna.parse(t
|
|
|
102
137
|
|
|
103
138
|
export const formatTag = (tag) => f0.format(tag) || f1.format(tag) || null
|
|
104
139
|
|
|
105
|
-
export const getTags = async (cwd) =>
|
|
106
|
-
(await $.o({cwd})`git tag -l`).toString()
|
|
140
|
+
export const getTags = async (cwd, ref = '') =>
|
|
141
|
+
(await $.o({cwd})`git tag -l ${ref}`).toString()
|
|
107
142
|
.split('\n')
|
|
108
143
|
.map(tag => parseTag(tag.trim()))
|
|
109
144
|
.filter(Boolean)
|
|
@@ -118,3 +153,19 @@ export const getLatestTaggedVersion = async (cwd, name) =>
|
|
|
118
153
|
export const formatDateTag = (date = new Date()) => `${date.getUTCFullYear()}.${date.getUTCMonth() + 1}.${date.getUTCDate()}`
|
|
119
154
|
|
|
120
155
|
export const parseDateTag = (date) => new Date(date.replaceAll('.', '-')+'Z')
|
|
156
|
+
|
|
157
|
+
export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
158
|
+
|
|
159
|
+
export const getLatestMeta = async (cwd, tag) => {
|
|
160
|
+
if (!tag) return null
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const _cwd = await fetchRepo({cwd, branch: 'meta'})
|
|
164
|
+
return await Promise.any([
|
|
165
|
+
fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
|
|
166
|
+
fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
|
|
167
|
+
])
|
|
168
|
+
} catch {}
|
|
169
|
+
|
|
170
|
+
return null
|
|
171
|
+
}
|
package/src/main/js/npm.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {parseEnv} from './config.js'
|
|
2
1
|
import {log} from './log.js'
|
|
3
2
|
import {$, ctx, fs, path, INI, fetch} from 'zx-extra'
|
|
4
3
|
|
|
5
|
-
export const fetchPkg = async (pkg
|
|
4
|
+
export const fetchPkg = async (pkg) => {
|
|
6
5
|
const id = `${pkg.name}@${pkg.version}`
|
|
7
6
|
|
|
8
7
|
try {
|
|
9
8
|
log({pkg})(`fetching '${id}'`)
|
|
10
9
|
const cwd = pkg.absPath
|
|
11
|
-
const {npmRegistry, npmToken, npmConfig} =
|
|
10
|
+
const {npmRegistry, npmToken, npmConfig} = pkg.config
|
|
12
11
|
const bearerToken = getBearerToken(npmRegistry, npmToken, npmConfig)
|
|
13
12
|
const tarball = getTarballUrl(npmRegistry, pkg.name, pkg.version)
|
|
14
13
|
await $.raw`wget --timeout=10 --header='Authorization: ${bearerToken}' -qO- ${tarball} | tar -xvz --strip-components=1 --exclude='package.json' -C ${cwd}`
|
|
@@ -19,8 +18,8 @@ export const fetchPkg = async (pkg, {env = $.env} = {}) => {
|
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
export const fetchManifest = async (pkg, {nothrow
|
|
23
|
-
const {npmRegistry, npmToken, npmConfig} =
|
|
21
|
+
export const fetchManifest = async (pkg, {nothrow} = {}) => {
|
|
22
|
+
const {npmRegistry, npmToken, npmConfig} = pkg.config
|
|
24
23
|
const bearerToken = getBearerToken(npmRegistry, npmToken, npmConfig)
|
|
25
24
|
const url = getManifestUrl(npmRegistry, pkg.name, pkg.version)
|
|
26
25
|
|
|
@@ -36,9 +35,9 @@ export const fetchManifest = async (pkg, {nothrow, env = $.env} = {}) => {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
export const npmPublish = (pkg) => ctx(async ($) => {
|
|
39
|
-
const {absPath: cwd, name, version, manifest} = pkg
|
|
40
|
-
if (manifest.private ||
|
|
41
|
-
const {npmRegistry, npmToken, npmConfig} =
|
|
38
|
+
const {absPath: cwd, name, version, manifest, config} = pkg
|
|
39
|
+
if (manifest.private || config?.npmPublish === false) return
|
|
40
|
+
const {npmRegistry, npmToken, npmConfig} = config
|
|
42
41
|
const npmrc = npmConfig ? npmConfig : path.resolve(cwd, '.npmrc')
|
|
43
42
|
|
|
44
43
|
log({pkg})(`publish npm package ${name} ${version} to ${npmRegistry}`)
|
package/src/main/js/processor.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import os from 'node:os'
|
|
2
|
-
import {$, within} from 'zx-extra'
|
|
3
|
-
import {
|
|
4
|
-
import {topo, traverseQueue} from './deps.js'
|
|
2
|
+
import {$, fs, within} from 'zx-extra'
|
|
3
|
+
import {queuefy} from 'queuefy'
|
|
5
4
|
import {analyze} from './analyze.js'
|
|
6
5
|
import {build} from './build.js'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import {getPkgConfig} from './config.js'
|
|
7
|
+
import {topo, traverseQueue} from './deps.js'
|
|
8
|
+
import {log} from './log.js'
|
|
9
|
+
import {getLatest} from './meta.js'
|
|
10
|
+
import {publish} from './publish.js'
|
|
11
|
+
import {getRoot, getSha} from './repo.js'
|
|
12
|
+
import {get, memoizeBy, set, tpl} from './util.js'
|
|
12
13
|
|
|
13
14
|
export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within(async () => {
|
|
14
15
|
const {state, build, publish} = createContext(flags, env)
|
|
@@ -16,21 +17,25 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
|
|
|
16
17
|
log()('zx-bulk-release')
|
|
17
18
|
|
|
18
19
|
try {
|
|
19
|
-
const {packages, queue, root, sources, next, prev, nodes} = await topo({cwd, flags})
|
|
20
|
+
const {packages, queue, root, sources, next, prev, nodes, graphs, edges} = await topo({cwd, flags})
|
|
20
21
|
log()('queue:', queue)
|
|
22
|
+
log()('graphs', graphs)
|
|
21
23
|
|
|
22
|
-
state
|
|
24
|
+
state
|
|
25
|
+
.setQueue(queue)
|
|
26
|
+
.setPackages(packages)
|
|
23
27
|
|
|
24
28
|
await traverseQueue({queue, prev, async cb(name) {
|
|
25
29
|
state.setStatus('analyzing', name)
|
|
26
30
|
const pkg = packages[name]
|
|
27
31
|
await contextify(pkg, packages, root)
|
|
28
32
|
await analyze(pkg, packages)
|
|
29
|
-
state
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
state
|
|
34
|
+
.set('config', pkg.config, name)
|
|
35
|
+
.set('version', pkg.version, name)
|
|
36
|
+
.set('prevVersion', pkg.latest.tag?.version || pkg.manifest.version, name)
|
|
37
|
+
.set('releaseType', pkg.releaseType, name)
|
|
38
|
+
.set('tag', pkg.tag, name)
|
|
34
39
|
}})
|
|
35
40
|
|
|
36
41
|
state.setStatus('pending')
|
|
@@ -58,8 +63,9 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
|
|
|
58
63
|
}})
|
|
59
64
|
} catch (e) {
|
|
60
65
|
log({level: 'error'})(e)
|
|
61
|
-
state
|
|
62
|
-
|
|
66
|
+
state
|
|
67
|
+
.set('error', e)
|
|
68
|
+
.setStatus('failure')
|
|
63
69
|
throw e
|
|
64
70
|
}
|
|
65
71
|
state.setStatus('success')
|
|
@@ -95,14 +101,75 @@ const createContext = (flags, env) => {
|
|
|
95
101
|
|
|
96
102
|
// Inspired by https://docs.github.com/en/actions/learn-github-actions/contexts
|
|
97
103
|
export const contextify = async (pkg, packages, root) => {
|
|
98
|
-
pkg.config = await
|
|
104
|
+
pkg.config = await getPkgConfig(pkg.absPath, root.absPath)
|
|
99
105
|
pkg.latest = await getLatest(pkg)
|
|
100
106
|
pkg.context = {
|
|
101
107
|
git: {
|
|
102
|
-
sha:
|
|
103
|
-
root:
|
|
108
|
+
sha: await getSha(pkg.absPath),
|
|
109
|
+
root: await getRoot(pkg.absPath)
|
|
104
110
|
},
|
|
105
111
|
env: $.env,
|
|
106
112
|
packages
|
|
107
113
|
}
|
|
108
114
|
}
|
|
115
|
+
|
|
116
|
+
export const createState = ({logger = console, file = null} = {}) => ({
|
|
117
|
+
logger,
|
|
118
|
+
file,
|
|
119
|
+
status: 'initial',
|
|
120
|
+
queue: [],
|
|
121
|
+
packages: {},
|
|
122
|
+
events: [],
|
|
123
|
+
setQueue(queue) {
|
|
124
|
+
this.queue = queue
|
|
125
|
+
return this
|
|
126
|
+
},
|
|
127
|
+
setPackages(packages) {
|
|
128
|
+
this.packages = Object.entries(packages).reduce((acc, [name, {manifest: {version}, absPath, relPath}]) => {
|
|
129
|
+
acc[name] = {
|
|
130
|
+
status: 'initial',
|
|
131
|
+
name,
|
|
132
|
+
version,
|
|
133
|
+
path: absPath,
|
|
134
|
+
relPath
|
|
135
|
+
}
|
|
136
|
+
return acc
|
|
137
|
+
}, {})
|
|
138
|
+
return this
|
|
139
|
+
},
|
|
140
|
+
get(key, pkgName) {
|
|
141
|
+
return get(
|
|
142
|
+
pkgName ? this.packages[pkgName] : this,
|
|
143
|
+
key
|
|
144
|
+
)
|
|
145
|
+
},
|
|
146
|
+
set(key, value, pkgName) {
|
|
147
|
+
set(
|
|
148
|
+
pkgName ? this.packages[pkgName] : this,
|
|
149
|
+
key,
|
|
150
|
+
value
|
|
151
|
+
)
|
|
152
|
+
return this
|
|
153
|
+
},
|
|
154
|
+
setStatus(status, name) {
|
|
155
|
+
this.set('status', status, name)
|
|
156
|
+
this.save()
|
|
157
|
+
return this
|
|
158
|
+
},
|
|
159
|
+
getStatus(status, name) {
|
|
160
|
+
return this.get('status', name)
|
|
161
|
+
},
|
|
162
|
+
log(ctx = {}) {
|
|
163
|
+
return function (...chunks) {
|
|
164
|
+
const {pkg, scope = pkg?.name || '~', level = 'info'} = ctx
|
|
165
|
+
const msg = chunks.map(c => typeof c === 'string' ? tpl(c, ctx) : c)
|
|
166
|
+
const event = {msg, scope, date: Date.now(), level}
|
|
167
|
+
this.events.push(event)
|
|
168
|
+
logger[level](`[${scope}]`, ...msg)
|
|
169
|
+
}.bind(this)
|
|
170
|
+
},
|
|
171
|
+
save() {
|
|
172
|
+
this.file && fs.outputJsonSync(this.file, this)
|
|
173
|
+
return this
|
|
174
|
+
}
|
|
175
|
+
})
|
package/src/main/js/publish.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {fs, path, $} from 'zx-extra'
|
|
2
|
-
import {formatTag,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import {formatTag, pushTag} from './meta.js'
|
|
3
|
+
import {pushCommit, getRepo} from './repo.js'
|
|
4
|
+
import {npmPublish} from './npm.js'
|
|
5
|
+
import {pushChangelog, formatReleaseNotes} from './changelog.js'
|
|
6
6
|
import {msgJoin} from './util.js'
|
|
7
7
|
import {runCmd} from './processor.js'
|
|
8
8
|
import {log} from './log.js'
|
|
9
|
+
import {pushMeta} from './meta.js'
|
|
9
10
|
|
|
10
11
|
export const publish = async (pkg, run = runCmd) => {
|
|
11
12
|
await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
|
|
@@ -18,38 +19,14 @@ export const publish = async (pkg, run = runCmd) => {
|
|
|
18
19
|
await run(pkg, 'publishCmd')
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export const pushMeta = async (pkg) => {
|
|
22
|
-
log({pkg})('push artifact to branch \'meta\'')
|
|
23
|
-
|
|
24
|
-
const {name, version, absPath: cwd} = pkg
|
|
25
|
-
const tag = formatTag({name, version})
|
|
26
|
-
const to = '.'
|
|
27
|
-
const branch = 'meta'
|
|
28
|
-
const msg = `chore: release meta ${name} ${version}`
|
|
29
|
-
const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
30
|
-
const meta = {
|
|
31
|
-
META_VERSION: '1',
|
|
32
|
-
name: pkg.name,
|
|
33
|
-
hash,
|
|
34
|
-
version: pkg.version,
|
|
35
|
-
dependencies: pkg.dependencies,
|
|
36
|
-
devDependencies: pkg.devDependencies,
|
|
37
|
-
peerDependencies: pkg.peerDependencies,
|
|
38
|
-
optionalDependencies: pkg.optionalDependencies,
|
|
39
|
-
}
|
|
40
|
-
const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
|
|
41
|
-
|
|
42
|
-
await push({cwd, to, branch, msg, files})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
22
|
export const ghRelease = async (pkg) => {
|
|
46
23
|
log({pkg})(`create gh release`)
|
|
47
24
|
|
|
48
|
-
const {ghToken} =
|
|
25
|
+
const {ghBasicAuth: basicAuth, ghToken} = pkg.config
|
|
49
26
|
if (!ghToken) return null
|
|
50
27
|
|
|
51
28
|
const {name, version, absPath: cwd} = pkg
|
|
52
|
-
const {repoName} = await
|
|
29
|
+
const {repoName} = await getRepo(cwd, {basicAuth})
|
|
53
30
|
const tag = formatTag({name, version})
|
|
54
31
|
const releaseNotes = await formatReleaseNotes(pkg)
|
|
55
32
|
const releaseData = JSON.stringify({
|
|
@@ -61,45 +38,8 @@ export const ghRelease = async (pkg) => {
|
|
|
61
38
|
await $.o({cwd})`curl -H "Authorization: token ${ghToken}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${repoName}/releases -d ${releaseData}`
|
|
62
39
|
}
|
|
63
40
|
|
|
64
|
-
const pushChangelog = async (pkg) => {
|
|
65
|
-
const {config: {changelog: opts}} = pkg
|
|
66
|
-
if (!opts) return
|
|
67
|
-
|
|
68
|
-
log({pkg})('push changelog')
|
|
69
|
-
const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = typeof opts === 'string'
|
|
70
|
-
? opts.split(' ')
|
|
71
|
-
: [opts.branch, opts.file, opts.msg]
|
|
72
|
-
const _cwd = await fetch({cwd: pkg.absPath, branch})
|
|
73
|
-
const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
|
|
74
|
-
const releaseNotes = await formatReleaseNotes(pkg)
|
|
75
|
-
|
|
76
|
-
await $.o({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
|
|
77
|
-
await push({cwd: pkg.absPath, branch, msg})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const formatReleaseNotes = async (pkg) => {
|
|
81
|
-
const {name, version, absPath: cwd} = pkg
|
|
82
|
-
const {repoPublicUrl} = await parseRepo(cwd)
|
|
83
|
-
const tag = formatTag({name, version})
|
|
84
|
-
const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})`
|
|
85
|
-
const releaseDetails = Object.values(pkg.changes
|
|
86
|
-
.reduce((acc, {group, subj, short, hash}) => {
|
|
87
|
-
const {commits} = acc[group] || (acc[group] = {commits: [], group})
|
|
88
|
-
const commitRef = `* ${subj}${short ? ` [${short}](${repoPublicUrl}/commit/${hash})` : ''}`
|
|
89
|
-
|
|
90
|
-
commits.push(commitRef)
|
|
91
|
-
|
|
92
|
-
return acc
|
|
93
|
-
}, {}))
|
|
94
|
-
.map(({group, commits}) => `
|
|
95
|
-
### ${group}
|
|
96
|
-
${commits.join('\n')}`).join('\n')
|
|
97
|
-
|
|
98
|
-
return releaseDiffRef + '\n' + releaseDetails + '\n'
|
|
99
|
-
}
|
|
100
|
-
|
|
101
41
|
const ghPages = async (pkg) => {
|
|
102
|
-
const {config: {ghPages: opts}} = pkg
|
|
42
|
+
const {config: {ghPages: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
|
|
103
43
|
if (!opts) return
|
|
104
44
|
|
|
105
45
|
const [branch = 'gh-pages', from = 'docs', to = '.', ..._msg] = typeof opts === 'string'
|
|
@@ -109,38 +49,14 @@ const ghPages = async (pkg) => {
|
|
|
109
49
|
|
|
110
50
|
log({pkg})(`publish docs to ${branch}`)
|
|
111
51
|
|
|
112
|
-
await
|
|
52
|
+
await pushCommit({
|
|
113
53
|
cwd: path.join(pkg.absPath, from),
|
|
114
54
|
from: '.',
|
|
115
55
|
to,
|
|
116
56
|
branch,
|
|
117
|
-
msg
|
|
57
|
+
msg,
|
|
58
|
+
gitCommitterEmail,
|
|
59
|
+
gitCommitterName,
|
|
60
|
+
basicAuth
|
|
118
61
|
})
|
|
119
62
|
}
|
|
120
|
-
|
|
121
|
-
export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
122
|
-
|
|
123
|
-
export const getLatestMeta = async (cwd, tag) => {
|
|
124
|
-
if (!tag) return null
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const _cwd = await fetch({cwd, branch: 'meta'})
|
|
128
|
-
return await Promise.any([
|
|
129
|
-
fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
|
|
130
|
-
fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
|
|
131
|
-
])
|
|
132
|
-
} catch {}
|
|
133
|
-
|
|
134
|
-
return null
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export const getLatest = async (pkg) => {
|
|
138
|
-
const {absPath: cwd, name} = pkg
|
|
139
|
-
const tag = await getLatestTag(cwd, name)
|
|
140
|
-
const meta = await getLatestMeta(cwd, tag?.ref) || await fetchManifest(pkg, {nothrow: true})
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
tag,
|
|
144
|
-
meta
|
|
145
|
-
}
|
|
146
|
-
}
|
package/src/main/js/repo.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import {$, ctx, fs, path, tempy, copy} from 'zx-extra'
|
|
2
|
-
import {parseEnv} from './config.js'
|
|
3
2
|
import {log} from './log.js'
|
|
4
3
|
import {keyByValue} from './util.js'
|
|
5
4
|
|
|
6
5
|
const branches = {}
|
|
7
|
-
export const
|
|
6
|
+
export const fetchRepo = async ({cwd: _cwd, branch, origin: _origin, basicAuth}) => ctx(async ($) => {
|
|
8
7
|
const root = await getRoot(_cwd)
|
|
9
8
|
const id = `${root}:${branch}`
|
|
10
9
|
|
|
11
10
|
if (branches[id]) return branches[id]
|
|
12
11
|
|
|
13
|
-
const origin = _origin || (await
|
|
12
|
+
const origin = _origin || (await getRepo(_cwd, {basicAuth})).repoAuthedUrl
|
|
14
13
|
const cwd = tempy.temporaryDirectory()
|
|
15
14
|
$.cwd = cwd
|
|
16
15
|
try {
|
|
@@ -25,14 +24,14 @@ export const fetch = async ({cwd: _cwd, branch, origin: _origin}) => ctx(async (
|
|
|
25
24
|
return branches[id]
|
|
26
25
|
})
|
|
27
26
|
|
|
28
|
-
export const
|
|
27
|
+
export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = [], basicAuth, gitCommitterEmail, gitCommitterName}) => ctx(async ($) => {
|
|
29
28
|
let retries = 3
|
|
30
29
|
let _cwd
|
|
31
30
|
|
|
32
31
|
while (retries > 0) {
|
|
33
32
|
try {
|
|
34
|
-
|
|
35
|
-
_cwd = await
|
|
33
|
+
|
|
34
|
+
_cwd = await fetchRepo({cwd, branch, origin, basicAuth})
|
|
36
35
|
|
|
37
36
|
for (let {relpath, contents} of files) {
|
|
38
37
|
const _contents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2)
|
|
@@ -62,16 +61,15 @@ export const push = async ({cwd, from, to, branch, origin, msg, ignoreFiles, fil
|
|
|
62
61
|
})
|
|
63
62
|
|
|
64
63
|
const repos = {}
|
|
65
|
-
export const
|
|
64
|
+
export const getRepo = async (_cwd, {basicAuth} = {}) => {
|
|
66
65
|
const cwd = await getRoot(_cwd)
|
|
67
66
|
if (repos[cwd]) return repos[cwd]
|
|
68
67
|
|
|
69
|
-
const {ghToken, ghUser} = parseEnv($.env)
|
|
70
68
|
const originUrl = await getOrigin(cwd)
|
|
71
69
|
const [, , repoHost, repoName] = originUrl.replace(':', '/').replace(/\.git/, '').match(/.+(@|\/\/)([^/]+)\/(.+)$/) || []
|
|
72
70
|
const repoPublicUrl = `https://${repoHost}/${repoName}`
|
|
73
|
-
const repoAuthedUrl =
|
|
74
|
-
? `https://${
|
|
71
|
+
const repoAuthedUrl = basicAuth && repoHost && repoName
|
|
72
|
+
? `https://${basicAuth}@${repoHost}/${repoName}.git`
|
|
75
73
|
: originUrl
|
|
76
74
|
|
|
77
75
|
repos[cwd] = {
|
|
@@ -95,3 +93,19 @@ export const getOrigin = async (cwd) => {
|
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
export const getRoot = async (cwd) => (await $.o({cwd})`git rev-parse --show-toplevel`).toString().trim()
|
|
96
|
+
|
|
97
|
+
export const getSha = async (cwd) => (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
98
|
+
|
|
99
|
+
export const getCommits = async (cwd, from, to = 'HEAD') => ctx(async ($) => {
|
|
100
|
+
const ref = from ? `${from}..${to}` : to
|
|
101
|
+
|
|
102
|
+
$.cwd = cwd
|
|
103
|
+
return (await $.raw`git log ${ref} --format=+++%s__%b__%h__%H -- .`)
|
|
104
|
+
.toString()
|
|
105
|
+
.split('+++')
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
.map(msg => {
|
|
108
|
+
const [subj, body, short, hash] = msg.split('__').map(raw => raw.trim())
|
|
109
|
+
return {subj, body, short, hash}
|
|
110
|
+
})
|
|
111
|
+
})
|
package/src/main/js/util.js
CHANGED
package/src/main/js/state.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import {get, set, tpl} from './util.js'
|
|
2
|
-
import {fs} from 'zx-extra'
|
|
3
|
-
|
|
4
|
-
export const createState = ({logger = console, file = null} = {}) => ({
|
|
5
|
-
logger,
|
|
6
|
-
file,
|
|
7
|
-
status: 'initial',
|
|
8
|
-
queue: [],
|
|
9
|
-
packages: [],
|
|
10
|
-
events: [],
|
|
11
|
-
setQueue(queue, packages) {
|
|
12
|
-
this.queue = queue
|
|
13
|
-
this.packages = queue.map(name => {
|
|
14
|
-
const {manifest: {version}, absPath, relPath} = packages[name]
|
|
15
|
-
return {
|
|
16
|
-
status: 'initial',
|
|
17
|
-
name,
|
|
18
|
-
version,
|
|
19
|
-
path: absPath,
|
|
20
|
-
relPath
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
},
|
|
24
|
-
get(key, pkgName) {
|
|
25
|
-
return get(
|
|
26
|
-
pkgName ? this.packages.find(({name}) => name === pkgName) : this,
|
|
27
|
-
key
|
|
28
|
-
)
|
|
29
|
-
},
|
|
30
|
-
set(key, value, pkgName) {
|
|
31
|
-
set(
|
|
32
|
-
pkgName ? this.packages.find(({name}) => name === pkgName) : this,
|
|
33
|
-
key,
|
|
34
|
-
value
|
|
35
|
-
)
|
|
36
|
-
},
|
|
37
|
-
setStatus(status, name) {
|
|
38
|
-
this.set('status', status, name)
|
|
39
|
-
this.save()
|
|
40
|
-
},
|
|
41
|
-
getStatus(status, name) {
|
|
42
|
-
return this.get('status', name)
|
|
43
|
-
},
|
|
44
|
-
log(ctx = {}) {
|
|
45
|
-
return function (...chunks) {
|
|
46
|
-
const {pkg, scope = pkg?.name || '~', level = 'info'} = ctx
|
|
47
|
-
const msg = chunks.map(c => typeof c === 'string' ? tpl(c, ctx) : c)
|
|
48
|
-
const event = {msg, scope, date: Date.now(), level}
|
|
49
|
-
this.events.push(event)
|
|
50
|
-
logger[level](`[${scope}]`, ...msg)
|
|
51
|
-
}.bind(this)
|
|
52
|
-
},
|
|
53
|
-
save() {
|
|
54
|
-
this.file && fs.outputJsonSync(this.file, this)
|
|
55
|
-
}
|
|
56
|
-
})
|