zx-bulk-release 2.2.5 → 2.2.7
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 +11 -0
- package/README.md +12 -7
- package/package.json +1 -1
- package/src/main/js/analyze.js +1 -1
- package/src/main/js/config.js +7 -10
- package/src/main/js/git.js +13 -32
- package/src/main/js/log.js +18 -24
- package/src/main/js/processor.js +37 -37
- package/src/main/js/util.js +2 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## [2.2.7](https://github.com/semrel-extra/zx-bulk-release/compare/v2.2.6...v2.2.7) (2023-03-24)
|
|
2
|
+
|
|
3
|
+
### Fixes & improvements
|
|
4
|
+
* docs: describe more CLI flags ([6f7a053](https://github.com/semrel-extra/zx-bulk-release/commit/6f7a053ae2fe2da8fd71858b132ef4ac4d1456ad))
|
|
5
|
+
* refactor: move `topo` to ctx builder ([6fec83c](https://github.com/semrel-extra/zx-bulk-release/commit/6fec83c10dd76feb93a3148eb2c032c6eaa902f1))
|
|
6
|
+
|
|
7
|
+
## [2.2.6](https://github.com/semrel-extra/zx-bulk-release/compare/v2.2.5...v2.2.6) (2023-03-24)
|
|
8
|
+
|
|
9
|
+
### Fixes & improvements
|
|
10
|
+
* refactor: apply memoize to git utils ([ff8ff78](https://github.com/semrel-extra/zx-bulk-release/commit/ff8ff7840eb65171b7d69d45cafed944627bfd20))
|
|
11
|
+
|
|
1
12
|
## [2.2.5](https://github.com/semrel-extra/zx-bulk-release/compare/v2.2.4...v2.2.5) (2023-03-24)
|
|
2
13
|
|
|
3
14
|
### Fixes & improvements
|
package/README.md
CHANGED
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* No extra builds. The required deps are fetched from the pkg registry (`npmFetch` config opt).
|
|
19
19
|
|
|
20
20
|
## Roadmap
|
|
21
|
+
* [x] Store release metrics to `meta`.
|
|
22
|
+
* [ ] ~~Self-repair. Restore broken/missing metadata from external registries (npm, pypi, m2)~~. Tags should be the only source of truth
|
|
21
23
|
* [ ] Multistack. Add support for java/kt/py.
|
|
22
|
-
* [ ] Self-repair. Restore broken/missing metadata from external registries (npm, pypi, m2).
|
|
23
24
|
* [ ] Semaphore. Let several release agents to serve the monorepo at the same time.
|
|
24
|
-
* [ ] Stats. Store release metrics to `meta`.
|
|
25
25
|
|
|
26
26
|
## Requirements
|
|
27
27
|
* macOS / linux
|
|
@@ -37,11 +37,16 @@ yarn add zx-bulk-release
|
|
|
37
37
|
```shell
|
|
38
38
|
GH_TOKEN=ghtoken GH_USER=username NPM_TOKEN=npmtoken npx zx-bulk-release [opts]
|
|
39
39
|
```
|
|
40
|
-
| Flag
|
|
41
|
-
|
|
42
|
-
| `--
|
|
43
|
-
| `--
|
|
44
|
-
| `--
|
|
40
|
+
| Flag | Description | Default |
|
|
41
|
+
|------------------------------|----------------------------------------------------------------|------------------|
|
|
42
|
+
| `--ignore` | Packages to ignore: `a, b` | |
|
|
43
|
+
| `--include-private` | Include `private` packages | `false` |
|
|
44
|
+
| `--concurrency` | `build/publish` threads limit | `os.cpus.length` |
|
|
45
|
+
| `--no-build` | Skip `buildCmd` invoke | |
|
|
46
|
+
| `--no-npm-fetch` | Disable npm artifacts fetching | |
|
|
47
|
+
| `--dry-run` / `--no-publish` | Disable any publish logic | |
|
|
48
|
+
| `--report` | Persist release state to file | |
|
|
49
|
+
| `--debug` | Enable [zx](https://github.com/google/zx#verbose) verbose mode | |
|
|
45
50
|
|
|
46
51
|
### JS API
|
|
47
52
|
```js
|
package/package.json
CHANGED
package/src/main/js/analyze.js
CHANGED
|
@@ -6,7 +6,7 @@ import {getCommits} from './git.js'
|
|
|
6
6
|
|
|
7
7
|
export const analyze = async (pkg, packages) => {
|
|
8
8
|
const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
|
|
9
|
-
const depsChanges = await updateDeps(pkg, packages)
|
|
9
|
+
const depsChanges = await updateDeps(pkg, pkg.context.packages)
|
|
10
10
|
const changes = [...semanticChanges, ...depsChanges]
|
|
11
11
|
const releaseType = getNextReleaseType(changes)
|
|
12
12
|
|
package/src/main/js/config.js
CHANGED
|
@@ -15,10 +15,10 @@ const CONFIG_FILES = [
|
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
export const defaultConfig = {
|
|
18
|
-
cmd:
|
|
19
|
-
changelog:
|
|
20
|
-
npmFetch:
|
|
21
|
-
ghRelease:
|
|
18
|
+
cmd: 'yarn && yarn build && yarn test',
|
|
19
|
+
changelog: 'changelog',
|
|
20
|
+
npmFetch: true,
|
|
21
|
+
ghRelease: true,
|
|
22
22
|
// npmPublish: true,
|
|
23
23
|
// ghPages: 'gh-pages'
|
|
24
24
|
}
|
|
@@ -38,10 +38,8 @@ export const normalizePkgConfig = (config, env) => ({
|
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
export const parseEnv = (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return {
|
|
41
|
+
export const parseEnv = ({GH_USER, GH_USERNAME, GITHUB_USER, GITHUB_USERNAME, GH_TOKEN, GITHUB_TOKEN, NPM_TOKEN, NPM_REGISTRY, NPMRC, NPM_USERCONFIG, NPM_CONFIG_USERCONFIG, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL} = process.env) =>
|
|
42
|
+
({
|
|
45
43
|
ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME,
|
|
46
44
|
ghToken: GH_TOKEN || GITHUB_TOKEN,
|
|
47
45
|
npmConfig: NPMRC || NPM_USERCONFIG || NPM_CONFIG_USERCONFIG,
|
|
@@ -49,7 +47,6 @@ export const parseEnv = (env = process.env) => {
|
|
|
49
47
|
npmRegistry: NPM_REGISTRY || 'https://registry.npmjs.org',
|
|
50
48
|
gitCommitterName: GIT_COMMITTER_NAME || 'Semrel Extra Bot',
|
|
51
49
|
gitCommitterEmail: GIT_COMMITTER_EMAIL || 'semrel-extra-bot@hotmail.com',
|
|
52
|
-
}
|
|
53
|
-
}
|
|
50
|
+
})
|
|
54
51
|
|
|
55
52
|
export const normalizeFlags = (flags = {}) => Object.entries(flags).reduce((acc, [k, v]) => ({...acc, [camelize(k)]: v}), {})
|
package/src/main/js/git.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import {$, ctx, fs, path, tempy, copy} from 'zx-extra'
|
|
2
2
|
import {log} from './log.js'
|
|
3
|
-
import {keyByValue} from './util.js'
|
|
4
|
-
|
|
5
|
-
const branches = {}
|
|
6
|
-
export const fetchRepo = async ({cwd: _cwd, branch, origin: _origin, basicAuth}) => ctx(async ($) => {
|
|
7
|
-
const root = await getRoot(_cwd)
|
|
8
|
-
const id = `${root}:${branch}`
|
|
9
|
-
|
|
10
|
-
if (branches[id]) return branches[id]
|
|
3
|
+
import {keyByValue, memoizeBy} from './util.js'
|
|
11
4
|
|
|
5
|
+
export const fetchRepo = memoizeBy(async ({cwd: _cwd, branch, origin: _origin, basicAuth}) => ctx(async ($) => {
|
|
12
6
|
const origin = _origin || (await getRepo(_cwd, {basicAuth})).repoAuthedUrl
|
|
13
7
|
const cwd = tempy.temporaryDirectory()
|
|
14
8
|
$.cwd = cwd
|
|
@@ -19,10 +13,9 @@ export const fetchRepo = async ({cwd: _cwd, branch, origin: _origin, basicAuth})
|
|
|
19
13
|
await $`git init .`
|
|
20
14
|
await $`git remote add origin ${origin}`
|
|
21
15
|
}
|
|
22
|
-
branches[id] = cwd
|
|
23
16
|
|
|
24
|
-
return
|
|
25
|
-
})
|
|
17
|
+
return cwd
|
|
18
|
+
}), async ({cwd, branch}) => `${await getRoot(cwd)}:${branch}`)
|
|
26
19
|
|
|
27
20
|
export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = [], basicAuth, gitCommitterEmail, gitCommitterName}) => ctx(async ($) => {
|
|
28
21
|
let retries = 3
|
|
@@ -54,7 +47,6 @@ export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFile
|
|
|
54
47
|
} catch (e) {
|
|
55
48
|
retries -= 1
|
|
56
49
|
log({level: 'error'})('git push failed', 'branch', branch, 'retries left', retries, e)
|
|
57
|
-
branches[keyByValue(branches, _cwd)] = null
|
|
58
50
|
|
|
59
51
|
if (retries === 0) {
|
|
60
52
|
throw e
|
|
@@ -63,11 +55,11 @@ export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFile
|
|
|
63
55
|
}
|
|
64
56
|
})
|
|
65
57
|
|
|
66
|
-
const
|
|
67
|
-
export const getRepo = async (_cwd, {basicAuth} = {}) => {
|
|
68
|
-
const cwd = await getRoot(_cwd)
|
|
69
|
-
if (repos[cwd]) return repos[cwd]
|
|
58
|
+
export const getRoot = async (cwd) => (await $.o({cwd})`git rev-parse --show-toplevel`).toString().trim()
|
|
70
59
|
|
|
60
|
+
export const getSha = async (cwd) => (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
61
|
+
|
|
62
|
+
export const getRepo = memoizeBy(async (cwd, {basicAuth} = {}) => {
|
|
71
63
|
const originUrl = await getOrigin(cwd)
|
|
72
64
|
const [, , repoHost, repoName] = originUrl.replace(':', '/').replace(/\.git/, '').match(/.+(@|\/\/)([^/]+)\/(.+)$/) || []
|
|
73
65
|
const repoPublicUrl = `https://${repoHost}/${repoName}`
|
|
@@ -75,29 +67,18 @@ export const getRepo = async (_cwd, {basicAuth} = {}) => {
|
|
|
75
67
|
? `https://${basicAuth}@${repoHost}/${repoName}.git`
|
|
76
68
|
: originUrl
|
|
77
69
|
|
|
78
|
-
|
|
70
|
+
return {
|
|
79
71
|
repoName,
|
|
80
72
|
repoHost,
|
|
81
73
|
repoPublicUrl,
|
|
82
74
|
repoAuthedUrl,
|
|
83
75
|
originUrl,
|
|
84
76
|
}
|
|
77
|
+
}, getRoot)
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const origins = {}
|
|
90
|
-
export const getOrigin = async (cwd) => {
|
|
91
|
-
if (!origins[cwd]) {
|
|
92
|
-
origins[cwd] = $.o({cwd})`git config --get remote.origin.url`.then(r => r.toString().trim())
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return origins[cwd]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export const getRoot = async (cwd) => (await $.o({cwd})`git rev-parse --show-toplevel`).toString().trim()
|
|
99
|
-
|
|
100
|
-
export const getSha = async (cwd) => (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
79
|
+
export const getOrigin = memoizeBy(async (cwd) =>
|
|
80
|
+
$.o({cwd})`git config --get remote.origin.url`.then(r => r.toString().trim())
|
|
81
|
+
)
|
|
101
82
|
|
|
102
83
|
export const getCommits = async (cwd, from, to = 'HEAD') => ctx(async ($) => {
|
|
103
84
|
const ref = from ? `${from}..${to}` : to
|
package/src/main/js/log.js
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
import {$, fs} from 'zx-extra'
|
|
2
2
|
import {get, set, tpl} from './util.js'
|
|
3
3
|
|
|
4
|
-
export const log = (ctx) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return console.log
|
|
10
|
-
}
|
|
4
|
+
export const log = (ctx) =>
|
|
5
|
+
$.report
|
|
6
|
+
? $.report.log(ctx)
|
|
7
|
+
: console.log
|
|
11
8
|
|
|
12
|
-
export const createReport = ({logger = console,
|
|
9
|
+
export const createReport = ({logger = console, packages = {}, queue = [], flags} = {}) => ({
|
|
13
10
|
logger,
|
|
14
|
-
|
|
11
|
+
flags,
|
|
12
|
+
file: flags.report || flags.file,
|
|
15
13
|
status: 'initial',
|
|
16
|
-
queue: [],
|
|
17
|
-
packages: {},
|
|
18
14
|
events: [],
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return this
|
|
31
|
-
},
|
|
15
|
+
queue,
|
|
16
|
+
packages: Object.entries(packages).reduce((acc, [name, {manifest: {version}, absPath, relPath}]) => {
|
|
17
|
+
acc[name] = {
|
|
18
|
+
status: 'initial',
|
|
19
|
+
name,
|
|
20
|
+
version,
|
|
21
|
+
path: absPath,
|
|
22
|
+
relPath
|
|
23
|
+
}
|
|
24
|
+
return acc
|
|
25
|
+
}, {}),
|
|
32
26
|
get(key, pkgName) {
|
|
33
27
|
return get(
|
|
34
28
|
pkgName ? this.packages[pkgName] : this,
|
package/src/main/js/processor.js
CHANGED
|
@@ -13,23 +13,20 @@ import {fetchPkg, npmPublish} from './npm.js'
|
|
|
13
13
|
import {memoizeBy, tpl} from './util.js'
|
|
14
14
|
|
|
15
15
|
export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within(async () => {
|
|
16
|
-
const
|
|
16
|
+
const context = await createContext({flags, env, cwd})
|
|
17
|
+
const {report, build, publish, packages, queue, prev, graphs} = context
|
|
17
18
|
|
|
18
|
-
report
|
|
19
|
+
report
|
|
20
|
+
.log()('zx-bulk-release')
|
|
21
|
+
.log()('queue:', queue)
|
|
22
|
+
.log()('graphs', graphs)
|
|
19
23
|
|
|
20
24
|
try {
|
|
21
|
-
const {packages, queue, root, prev, graphs} = await topo({cwd, flags})
|
|
22
|
-
report
|
|
23
|
-
.log()('queue:', queue)
|
|
24
|
-
.log()('graphs', graphs)
|
|
25
|
-
.set('queue', queue)
|
|
26
|
-
.setPackages(packages)
|
|
27
|
-
|
|
28
25
|
await traverseQueue({queue, prev, async cb(name) {
|
|
29
26
|
report.setStatus('analyzing', name)
|
|
30
27
|
const pkg = packages[name]
|
|
31
|
-
await contextify(pkg,
|
|
32
|
-
await analyze(pkg
|
|
28
|
+
await contextify(pkg, context)
|
|
29
|
+
await analyze(pkg)
|
|
33
30
|
report
|
|
34
31
|
.set('config', pkg.config, name)
|
|
35
32
|
.set('version', pkg.version, name)
|
|
@@ -47,17 +44,14 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
|
|
|
47
44
|
report.setStatus('skipped', name)
|
|
48
45
|
return
|
|
49
46
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (flags.dryRun) {
|
|
55
|
-
report.setStatus('
|
|
56
|
-
|
|
47
|
+
if (!flags.noBuild) {
|
|
48
|
+
report.setStatus('building', name)
|
|
49
|
+
await build(pkg)
|
|
50
|
+
}
|
|
51
|
+
if (!flags.dryRun && !flags.noPublish) {
|
|
52
|
+
report.setStatus('publishing', name)
|
|
53
|
+
await publish(pkg)
|
|
57
54
|
}
|
|
58
|
-
|
|
59
|
-
report.setStatus('publishing', name)
|
|
60
|
-
await publish(pkg)
|
|
61
55
|
|
|
62
56
|
report.setStatus('success', name)
|
|
63
57
|
}})
|
|
@@ -78,30 +72,35 @@ export const runCmd = async (pkg, name) => {
|
|
|
78
72
|
|
|
79
73
|
if (cmd) {
|
|
80
74
|
log({pkg})(`run ${name} '${cmd}'`)
|
|
81
|
-
|
|
75
|
+
return $.o({cwd: pkg.absPath, quote: v => v, preferLocal: true})`${cmd}`
|
|
82
76
|
}
|
|
83
77
|
}
|
|
84
78
|
|
|
85
|
-
const createContext = (flags, env) => {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
79
|
+
const createContext = async ({flags, env, cwd}) => {
|
|
80
|
+
const { packages, queue, root, prev, graphs } = await topo({cwd, flags})
|
|
81
|
+
const report = createReport({packages, queue, flags})
|
|
82
|
+
const run = queuefy(runCmd, flags.concurrency || os.cpus().length)
|
|
83
|
+
const _build = memoizeBy((pkg) => build(pkg, run, _build, flags))
|
|
84
|
+
const _publish = memoizeBy((pkg) => publish(pkg, run))
|
|
90
85
|
|
|
91
|
-
$.report =
|
|
92
|
-
$.env =
|
|
93
|
-
$.verbose =
|
|
86
|
+
$.report = report
|
|
87
|
+
$.env = {...process.env, ...env}
|
|
88
|
+
$.verbose = !!(flags.debug || $.env.DEBUG ) || $.verbose
|
|
94
89
|
|
|
95
90
|
return {
|
|
96
91
|
report,
|
|
97
|
-
runCmd: _runCmd,
|
|
98
92
|
build: _build,
|
|
99
|
-
publish: _publish
|
|
93
|
+
publish: _publish,
|
|
94
|
+
packages,
|
|
95
|
+
root,
|
|
96
|
+
queue,
|
|
97
|
+
prev,
|
|
98
|
+
graphs
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
// Inspired by https://docs.github.com/en/actions/learn-github-actions/contexts
|
|
104
|
-
const contextify = async (pkg, packages, root) => {
|
|
103
|
+
const contextify = async (pkg, {packages, root}) => {
|
|
105
104
|
pkg.config = await getPkgConfig(pkg.absPath, root.absPath)
|
|
106
105
|
pkg.latest = await getLatest(pkg)
|
|
107
106
|
pkg.context = {
|
|
@@ -114,13 +113,12 @@ const contextify = async (pkg, packages, root) => {
|
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
const build = async (pkg,
|
|
116
|
+
const build = async (pkg, run = runCmd, self = build, flags = {}) => within(async () => {
|
|
118
117
|
$.scope = pkg.name
|
|
119
|
-
if (pkg.built) return
|
|
120
118
|
|
|
121
119
|
await Promise.all([
|
|
122
|
-
traverseDeps(pkg, packages, async (_, {pkg}) => self(pkg,
|
|
123
|
-
pkg.changes.length === 0 && pkg.config.npmFetch
|
|
120
|
+
traverseDeps(pkg, pkg.context.packages, async (_, {pkg}) => self(pkg, run, self, flags)),
|
|
121
|
+
pkg.changes.length === 0 && pkg.config.npmFetch && !flags.noNpmFetch
|
|
124
122
|
? fetchPkg(pkg)
|
|
125
123
|
: Promise.resolve()
|
|
126
124
|
])
|
|
@@ -146,4 +144,6 @@ const publish = async (pkg, run = runCmd) => within(async () => {
|
|
|
146
144
|
ghPages(pkg),
|
|
147
145
|
run(pkg, 'publishCmd')
|
|
148
146
|
])
|
|
147
|
+
|
|
148
|
+
pkg.published = true
|
|
149
149
|
})
|
package/src/main/js/util.js
CHANGED
|
@@ -30,23 +30,10 @@ export const set = (obj, path, value) => {
|
|
|
30
30
|
|
|
31
31
|
export const msgJoin = (rest, context, def) => tpl(rest.filter(Boolean).join(' ') || def, context)
|
|
32
32
|
|
|
33
|
-
export const getPromise = () => {
|
|
34
|
-
let resolve, reject
|
|
35
|
-
const promise = new Promise((...args) => {
|
|
36
|
-
[resolve, reject] = args
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
resolve,
|
|
41
|
-
reject,
|
|
42
|
-
promise,
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
33
|
export const keyByValue = (obj, value) => Object.keys(obj).find((key) => obj[key] === value)
|
|
47
34
|
|
|
48
|
-
export const memoizeBy = (fn,
|
|
49
|
-
const key = getKey(...args)
|
|
35
|
+
export const memoizeBy = (fn, getKey = v => v, memo = new Map()) => async (...args) => {
|
|
36
|
+
const key = await getKey(...args)
|
|
50
37
|
if (memo.has(key)) {
|
|
51
38
|
return memo.get(key)
|
|
52
39
|
}
|