zx-bulk-release 1.26.2 → 2.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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## [2.0.0](https://github.com/semrel-extra/zx-bulk-release/compare/v1.26.2...v2.0.0) (2023-03-21)
2
+
3
+ ### Features
4
+ * feat: replace sequent flow with concurrent ([ec35d7e](https://github.com/semrel-extra/zx-bulk-release/commit/ec35d7e337e64369f0423b71008345177f3ac949))
5
+
6
+ ### BREAKING CHANGES
7
+ * concurrent flow might cause throttling issues ([ec35d7e](https://github.com/semrel-extra/zx-bulk-release/commit/ec35d7e337e64369f0423b71008345177f3ac949))
8
+
1
9
  ## [1.26.2](https://github.com/semrel-extra/zx-bulk-release/compare/v1.26.1...v1.26.2) (2023-03-21)
2
10
 
3
11
  ### Fixes & improvements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
- "version": "1.26.2",
3
+ "version": "2.0.0",
4
4
  "description": "zx-based alternative for multi-semantic-release",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,6 +23,7 @@
23
23
  "dependencies": {
24
24
  "@semrel-extra/topo": "^1.6.0",
25
25
  "cosmiconfig": "^8.1.3",
26
+ "queuefy": "^1.2.1",
26
27
  "zx-extra": "^2.5.4"
27
28
  },
28
29
  "devDependencies": {
@@ -1,18 +1,20 @@
1
1
  import {traverseDeps} from './deps.js'
2
2
  import {fetchPkg} from './npm.js'
3
- import {runHook} from './processor.js'
3
+ import {runCmd} from './processor.js'
4
4
 
5
- export const build = async (pkg, packages) => {
5
+ export const build = async (pkg, packages, run = runCmd) => {
6
6
  if (pkg.built) return true
7
7
 
8
8
  await traverseDeps(pkg, packages, async (_, {pkg}) => build(pkg, packages))
9
9
 
10
10
  const {config} = pkg
11
11
 
12
- if (pkg.changes.length === 0 && config.npmFetch) await fetchPkg(pkg)
12
+ if (pkg.changes.length === 0 && config.npmFetch) {
13
+ await fetchPkg(pkg)
14
+ }
13
15
 
14
16
  if (!pkg.fetched && config.buildCmd) {
15
- await runHook(pkg, 'buildCmd')
17
+ await run(pkg, 'buildCmd')
16
18
  }
17
19
 
18
20
  pkg.built = true
@@ -1 +1 @@
1
- export {run} from './processor.js'
1
+ export {run} from './processor.js'
@@ -1,16 +1,19 @@
1
1
  import os from 'node:os'
2
2
  import {$, fs, within} from 'zx-extra'
3
3
  import {log} from './log.js'
4
- import {topo} from './topo.js'
4
+ import {topo, traverse} from './topo.js'
5
5
  import {contextify} from './contextify.js'
6
6
  import {analyze} from './analyze.js'
7
7
  import {build} from './build.js'
8
8
  import {publish} from './publish.js'
9
9
  import {createState} from './state.js'
10
- import {tpl} from "./util.js";
10
+ import {tpl} from './util.js'
11
+ import {queuefy} from 'queuefy'
11
12
 
12
13
  export const run = async ({cwd = process.cwd(), env, flags = {}, concurrency = os.cpus().length} = {}) => within(async () => {
13
14
  const state = createState({file: flags.report})
15
+ const _runCmd = queuefy(runCmd, concurrency)
16
+
14
17
  $.state = state
15
18
  $.env = {...process.env, ...env}
16
19
  $.verbose = !!(flags.debug || $.env.DEBUG ) || $.verbose
@@ -18,16 +21,14 @@ export const run = async ({cwd = process.cwd(), env, flags = {}, concurrency = o
18
21
  log()('zx-bulk-release')
19
22
 
20
23
  try {
21
- const {packages, queue, root} = await topo({cwd, flags})
24
+ const {packages, queue, root, sources, next, prev, nodes} = await topo({cwd, flags})
22
25
  log()('queue:', queue)
23
26
 
24
27
  state.setQueue(queue, packages)
25
- state.setStatus('pending')
26
-
27
- for (let name of queue) {
28
- const pkg = packages[name]
29
28
 
29
+ await traverse({nodes, prev, async cb(name) {
30
30
  state.setStatus('analyzing', name)
31
+ const pkg = packages[name]
31
32
  await contextify(pkg, packages, root)
32
33
  await analyze(pkg, packages)
33
34
  state.set('config', pkg.config, name)
@@ -35,25 +36,31 @@ export const run = async ({cwd = process.cwd(), env, flags = {}, concurrency = o
35
36
  state.set('prevVersion', pkg.latest.tag?.version || pkg.manifest.version, name)
36
37
  state.set('releaseType', pkg.releaseType, name)
37
38
  state.set('tag', pkg.tag, name)
39
+ }})
40
+
41
+ state.setStatus('pending')
42
+
43
+ await traverse({nodes, prev, async cb(name) {
44
+ const pkg = packages[name]
38
45
 
39
46
  if (!pkg.releaseType) {
40
47
  state.setStatus('skipped', name)
41
- continue
48
+ return
42
49
  }
43
50
 
44
51
  state.setStatus('building', name)
45
- await build(pkg, packages)
52
+ await build(pkg, packages, _runCmd)
46
53
 
47
54
  if (flags.dryRun) {
48
55
  state.setStatus('success', name)
49
- continue
56
+ return
50
57
  }
51
58
 
52
59
  state.setStatus('publishing', name)
53
- await publish(pkg)
60
+ await publish(pkg, _runCmd)
54
61
 
55
62
  state.setStatus('success', name)
56
- }
63
+ }})
57
64
  } catch (e) {
58
65
  log({level: 'error'})(e)
59
66
  state.set('error', e)
@@ -64,7 +71,7 @@ export const run = async ({cwd = process.cwd(), env, flags = {}, concurrency = o
64
71
  log()('Great success!')
65
72
  })
66
73
 
67
- export const runHook = async (pkg, name) => {
74
+ export const runCmd = async (pkg, name) => {
68
75
  const cmd = tpl(pkg.config[name], {...pkg, ...pkg.context})
69
76
 
70
77
  if (cmd) {
@@ -3,11 +3,11 @@ import {formatTag, getLatestTag, pushTag} from './tag.js'
3
3
  import {push, fetch, parseRepo} from './repo.js'
4
4
  import {parseEnv} from './config.js'
5
5
  import {fetchManifest, npmPublish} from './npm.js'
6
- import {restJoin} from './util.js'
7
- import {runHook} from './processor.js'
6
+ import {msgJoin} from './util.js'
7
+ import {runCmd} from './processor.js'
8
8
  import {log} from './log.js'
9
9
 
10
- export const publish = async (pkg) => {
10
+ export const publish = async (pkg, run = runCmd) => {
11
11
  await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
12
12
  await pushTag(pkg)
13
13
  await pushMeta(pkg)
@@ -15,7 +15,7 @@ export const publish = async (pkg) => {
15
15
  await npmPublish(pkg)
16
16
  await ghRelease(pkg)
17
17
  await ghPages(pkg)
18
- await runHook(pkg, 'publishCmd')
18
+ await run(pkg, 'publishCmd')
19
19
  }
20
20
 
21
21
  export const pushMeta = async (pkg) => {
@@ -70,7 +70,7 @@ const pushChangelog = async (pkg) => {
70
70
  ? opts.split(' ')
71
71
  : [opts.branch, opts.file, opts.msg]
72
72
  const _cwd = await fetch({cwd: pkg.absPath, branch})
73
- const msg = restJoin(_msg, pkg, 'chore: update changelog ${{name}}')
73
+ const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
74
74
  const releaseNotes = await formatReleaseNotes(pkg)
75
75
 
76
76
  await $.o({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
@@ -105,7 +105,7 @@ const ghPages = async (pkg) => {
105
105
  const [branch = 'gh-pages', from = 'docs', to = '.', ..._msg] = typeof opts === 'string'
106
106
  ? opts.split(' ')
107
107
  : [opts.branch, opts.from, opts.to, opts.msg]
108
- const msg = restJoin(_msg, pkg, 'docs: update docs ${{name}} ${{version}}')
108
+ const msg = msgJoin(_msg, pkg, 'docs: update docs ${{name}} ${{version}}')
109
109
 
110
110
  log({pkg})(`publish docs to ${branch}`)
111
111
 
@@ -1,6 +1,7 @@
1
1
  import {$, ctx, fs, path, tempy, copy} from 'zx-extra'
2
2
  import {parseEnv} from './config.js'
3
3
  import {log} from './log.js'
4
+ import {keyByValue} from './util.js'
4
5
 
5
6
  const branches = {}
6
7
  export const fetch = async ({cwd: _cwd, branch, origin: _origin}) => ctx(async ($) => {
@@ -25,28 +26,39 @@ export const fetch = async ({cwd: _cwd, branch, origin: _origin}) => ctx(async (
25
26
  })
26
27
 
27
28
  export const push = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = []}) => ctx(async ($) => {
28
- const _cwd = await fetch({cwd, branch, origin})
29
- const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
30
-
31
- for (let {relpath, contents} of files) {
32
- const _contents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2)
33
- await fs.outputFile(path.resolve(_cwd, to, relpath), _contents)
34
- }
35
- if (from) await copy({baseFrom: cwd, from, baseTo: _cwd, to, ignoreFiles, cwd})
36
-
37
- $.cwd = _cwd
38
-
39
- await $`git config user.name ${gitCommitterName}`
40
- await $`git config user.email ${gitCommitterEmail}`
41
- await $`git add .`
42
- try {
43
- await $`git commit -m ${msg}`
44
- } catch {
45
- log({level: 'warn'})(`no changes to commit to ${branch}`)
46
- return
29
+ let retries = 3
30
+ let _cwd
31
+
32
+ while (retries > 0) {
33
+ try {
34
+ const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
35
+ _cwd = await fetch({cwd, branch, origin})
36
+
37
+ for (let {relpath, contents} of files) {
38
+ const _contents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2)
39
+ await fs.outputFile(path.resolve(_cwd, to, relpath), _contents)
40
+ }
41
+ if (from) await copy({baseFrom: cwd, from, baseTo: _cwd, to, ignoreFiles, cwd})
42
+
43
+ $.cwd = _cwd
44
+
45
+ await $`git config user.name ${gitCommitterName}`
46
+ await $`git config user.email ${gitCommitterEmail}`
47
+ await $`git add .`
48
+ try {
49
+ await $`git commit -m ${msg}`
50
+ } catch {
51
+ log({level: 'warn'})(`no changes to commit to ${branch}`)
52
+ return
53
+ }
54
+
55
+ return await $.raw`git push origin HEAD:refs/heads/${branch}`
56
+ } catch (e) {
57
+ retries -= 1
58
+ branches[keyByValue(branches, _cwd)] = null
59
+ console.warn('git push failed', 'retries left', retries, e)
60
+ }
47
61
  }
48
-
49
- await $.raw`git push origin HEAD:refs/heads/${branch}`
50
62
  })
51
63
 
52
64
  const repos = {}
@@ -1,4 +1,5 @@
1
1
  import {topo as _topo} from '@semrel-extra/topo'
2
+ import {getPromise} from './util.js'
2
3
 
3
4
  export const topo = async ({flags = {}, cwd} = {}) => {
4
5
  const ignore = typeof flags.ignore === 'string'
@@ -14,4 +15,17 @@ export const topo = async ({flags = {}, cwd} = {}) => {
14
15
  !ignore.includes(name)
15
16
 
16
17
  return _topo({cwd, filter})
17
- }
18
+ }
19
+
20
+ export const traverse = async ({nodes, prev, cb}) => {
21
+ const waitings = nodes.reduce((acc, node) => {
22
+ acc[node] = getPromise()
23
+ return acc
24
+ }, {})
25
+
26
+ await Promise.all(nodes.map(async (name) => {
27
+ await Promise.all((prev.get(name) || []).map((p) => waitings[p].promise))
28
+ await cb(name)
29
+ waitings[name].resolve(true)
30
+ }))
31
+ }
@@ -1,5 +1,3 @@
1
- import {$, fs} from 'zx-extra'
2
-
3
1
  export const tpl = (str, context) =>
4
2
  str?.replace(/\$\{\{\s*([.a-z0-9]+)\s*}}/gi, (matched, key) => get(context, key) ?? '')
5
3
 
@@ -30,4 +28,19 @@ export const set = (obj, path, value) => {
30
28
  return result
31
29
  }
32
30
 
33
- export const restJoin = (rest, context, def) => tpl(rest.filter(Boolean).join(' ') || def, context)
31
+ export const msgJoin = (rest, context, def) => tpl(rest.filter(Boolean).join(' ') || def, context)
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
+ export const keyByValue = (obj, value) => Object.keys(obj).find((key) => obj[key] === value)