zx-bulk-release 3.0.4 → 3.0.5

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,8 @@
1
+ ## [3.0.5](https://github.com/semrel-extra/zx-bulk-release/compare/v3.0.4...v3.0.5) (2026-04-11)
2
+
3
+ ### Fixes & improvements
4
+ * perf: minor code imprs ([2564f3f](https://github.com/semrel-extra/zx-bulk-release/commit/2564f3fd8fdd42c08a2aa32e1e9f2cfee7d3199d))
5
+
1
6
  ## [3.0.4](https://github.com/semrel-extra/zx-bulk-release/compare/v3.0.3...v3.0.4) (2026-04-11)
2
7
 
3
8
  ### Fixes & improvements
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
3
  "alias": "bulk-release",
4
- "version": "3.0.4",
4
+ "version": "3.0.5",
5
5
  "description": "zx-based alternative for multi-semantic-release",
6
6
  "type": "module",
7
7
  "exports": {
@@ -74,8 +74,9 @@ const pool = async (tasks, concurrency, fn) => {
74
74
 
75
75
  // Parcels grouped by package, sorted by semver asc (latest last).
76
76
  // Groups run in parallel (concurrency-limited), entries within a group — sequential.
77
- export const deliver = async (tars, env = process.env, {concurrency = 4} = {}) => {
78
- const groups = new Map()
77
+ export const inspect = async (tars, env = process.env) => {
78
+ const parcels = []
79
+ const skipped = []
79
80
 
80
81
  for (const tarPath of tars) {
81
82
  const fname = path.basename(tarPath)
@@ -83,22 +84,46 @@ export const deliver = async (tars, env = process.env, {concurrency = 4} = {}) =
83
84
  const p = await openParcel(tarPath, env)
84
85
  if (!p) continue
85
86
  if (p.warn) {
86
- log.warn(`skipping ${fname}: ${p.warn}`)
87
- if (p.tarPath) await fs.writeFile(tarPath, 'skip')
87
+ skipped.push({file: fname, reason: p.warn, tarPath: p.tarPath})
88
88
  continue
89
89
  }
90
- const key = p.resolved.name || p.resolved.channel
91
- if (!groups.has(key)) groups.set(key, [])
92
- groups.get(key).push(p)
90
+ parcels.push(p)
93
91
  } catch (e) {
94
- log.warn(`skipping ${fname}: ${e.message}`)
92
+ skipped.push({file: fname, reason: e.message})
95
93
  }
96
94
  }
97
95
 
96
+ // group by package, sort by semver asc within each group
97
+ const groups = new Map()
98
+ for (const p of parcels) {
99
+ const key = p.resolved.name || p.resolved.channel
100
+ if (!groups.has(key)) groups.set(key, [])
101
+ groups.get(key).push(p)
102
+ }
98
103
  for (const g of groups.values())
99
104
  g.sort((a, b) => semver.compare(a.resolved.version || '0.0.0', b.resolved.version || '0.0.0'))
100
105
 
101
- let delivered = 0
106
+ return {groups, skipped, total: tars.length, pending: parcels.length}
107
+ }
108
+
109
+ export const deliver = async (tars, env = process.env, {concurrency = 4, dryRun = false} = {}) => {
110
+ const {groups, skipped, total, pending} = await inspect(tars, env)
111
+
112
+ for (const {file, reason, tarPath} of skipped) {
113
+ log.warn(`skipping ${file}: ${reason}`)
114
+ if (!dryRun && tarPath) await fs.writeFile(tarPath, 'skip')
115
+ }
116
+
117
+ const entries = []
118
+ const toEntry = ({resolved}) => ({channel: resolved.channel, name: resolved.name, version: resolved.version})
119
+
120
+ if (dryRun) {
121
+ for (const group of groups.values())
122
+ for (const p of group)
123
+ entries.push(toEntry(p))
124
+ return {total, pending: entries.length, delivered: 0, skipped: skipped.length, entries}
125
+ }
126
+
102
127
  await pool([...groups.values()], concurrency, async (group) => {
103
128
  for (const {ch, resolved, destDir, tarPath} of group) {
104
129
  await within(async () => {
@@ -106,9 +131,9 @@ export const deliver = async (tars, env = process.env, {concurrency = 4} = {}) =
106
131
  await ch.run(resolved, destDir)
107
132
  })
108
133
  await fs.writeFile(tarPath, 'released')
109
- delivered++
134
+ entries.push(toEntry({resolved}))
110
135
  }
111
136
  })
112
137
 
113
- return delivered
138
+ return {total, pending, delivered: entries.length, skipped: skipped.length, entries}
114
139
  }
@@ -1,11 +1,11 @@
1
1
  import os from 'node:os'
2
2
  import {createRequire} from 'node:module'
3
- import {$, within, fs, glob, path} from 'zx-extra'
3
+ import {$, within, glob, path} from 'zx-extra'
4
4
  import {queuefy} from 'queuefy'
5
- import {topo, traverseQueue} from './depot/deps.js'
5
+
6
6
  import {createReport, log} from './log.js'
7
+ import {topo, traverseQueue} from './depot/deps.js'
7
8
  import {exec} from './depot/exec.js'
8
-
9
9
  import {contextify} from './depot/steps/contextify.js'
10
10
  import {analyze} from './depot/steps/analyze.js'
11
11
  import {build} from './depot/steps/build.js'
@@ -13,48 +13,58 @@ import {pack} from './depot/steps/pack.js'
13
13
  import {publish} from './depot/steps/publish.js'
14
14
  import {clean} from './depot/steps/clean.js'
15
15
  import {test} from './depot/steps/test.js'
16
-
17
16
  import {deliver, defaultOrder as channels} from './courier/index.js'
18
17
 
19
18
  const PARCELS_DIR = 'parcels'
19
+ const ZBR_VERSION = createRequire(import.meta.url)('../../../../package.json').version
20
+
21
+ export const run = async ({cwd = process.cwd(), env: _env, flags = {}} = {}) => within(async () => {
22
+ if (flags.v || flags.version)
23
+ return console.log(ZBR_VERSION)
24
+
25
+ const env = {...process.env, ..._env}
26
+ log.secret(env.GH_TOKEN, env.GITHUB_TOKEN, env.NPM_TOKEN)
27
+ log.info(`zx-bulk-release@${ZBR_VERSION}`)
28
+
29
+ return flags.deliver
30
+ ? runDeliver({env, flags})
31
+ : runPipeline({cwd, env, flags})
32
+ })
33
+
34
+ // --deliver [dir]
35
+ const runDeliver = async ({env, flags}) => {
36
+ const dir = typeof flags.deliver === 'string' ? flags.deliver : PARCELS_DIR
37
+ const report = createReport({flags})
20
38
 
21
- export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within(async () => {
22
39
  $.memo = new Map()
40
+ $.report = report
23
41
 
24
- const {version: zbrVersion} = createRequire(import.meta.url)('../../../../package.json')
25
- if (flags.v || flags.version) {
26
- console.log(zbrVersion)
27
- return
28
- }
42
+ report.setStatus('inspecting')
29
43
 
30
- // --deliver [dir]: standalone delivery from pre-packed tars.
31
- if (flags.deliver) {
32
- const dir = typeof flags.deliver === 'string' ? flags.deliver : PARCELS_DIR
33
- const tars = await glob(path.join(dir, 'parcel.*.tar'))
34
- if (!tars.length) {
35
- log.info(`deliver: no parcels in ${dir}, nothing to do`)
36
- return
37
- }
38
- const _env = {...process.env, ...env}
39
- log.secret(_env.GH_TOKEN, _env.GITHUB_TOKEN, _env.NPM_TOKEN)
40
- log.info(`deliver: ${tars.length} tar(s) from ${dir}`)
41
- const delivered = await deliver(tars, _env)
42
- log.info(`deliver: done, ${delivered} delivered`)
43
- return
44
- }
44
+ const tars = await glob(path.join(dir, 'parcel.*.tar'))
45
+ if (!tars.length) return report.setStatus('success').log(`no parcels in ${dir}`)
46
+
47
+ report.setStatus('delivering').log(`parcels: ${tars.length}`)
48
+ const result = await deliver(tars, env, {dryRun: flags.dryRun})
49
+ report.set('delivery', result).setStatus('success')
50
+
51
+ for (const {channel, name, version} of result.entries)
52
+ log.info(`${channel} ${name}@${version}`)
53
+ log.info(`done: ${result.delivered} delivered, ${result.skipped} skipped`)
54
+ }
45
55
 
56
+ // full pipeline (legacy / --pack)
57
+ const runPipeline = async ({cwd, env, flags}) => {
46
58
  const ctx = await createContext({flags, env, cwd})
47
59
  const {report, packages, queue, prev} = ctx
48
60
 
49
61
  const forEachPkg = (cb) => traverseQueue({queue, prev, cb: (name) => within(async () => {
50
62
  $.scope = name
51
- const pkg = packages[name]
52
- await contextify(pkg, ctx)
53
- return cb(pkg)
63
+ await contextify(packages[name], ctx)
64
+ return cb(packages[name])
54
65
  })})
55
66
 
56
67
  report
57
- .log(`zx-bulk-release@${zbrVersion}`)
58
68
  .log('queue:', queue)
59
69
  .log('graphs', ctx.graphs)
60
70
 
@@ -74,36 +84,16 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
74
84
  report.setStatus('pending')
75
85
 
76
86
  await forEachPkg(async (pkg) => {
77
- if (!pkg.releaseType) {
78
- report.setStatus('skipped', pkg.name)
79
- pkg.skipped = true
80
- return
81
- }
82
- if (flags.build !== false) {
83
- report.setStatus('building', pkg.name)
84
- await build(pkg)
85
- }
86
- if (flags.test !== false) {
87
- report.setStatus('testing', pkg.name)
88
- await test(pkg)
89
- }
90
- if (flags.dryRun || flags.publish === false) {
91
- report.setStatus('success', pkg.name)
92
- return
93
- }
94
-
95
- report.setStatus('packing', pkg.name)
96
- await pack(pkg)
97
-
98
- // --pack <dir>: pack only, skip delivery.
99
- if (flags.pack) {
100
- report.setStatus('packed', pkg.name)
101
- return
102
- }
103
-
104
- report.setStatus('publishing', pkg.name)
105
- await publish(pkg)
106
- report.setStatus('success', pkg.name)
87
+ if (!pkg.releaseType) { pkg.skipped = true; return report.setStatus('skipped', pkg.name) }
88
+ if (flags.build !== false) { report.setStatus('building', pkg.name); await build(pkg) }
89
+ if (flags.test !== false) { report.setStatus('testing', pkg.name); await test(pkg) }
90
+ if (flags.dryRun || flags.publish === false) return report.setStatus('success', pkg.name)
91
+
92
+ report.setStatus('packing', pkg.name); await pack(pkg)
93
+ if (flags.pack) return report.setStatus('packed', pkg.name)
94
+
95
+ report.setStatus('publishing', pkg.name); await publish(pkg)
96
+ report.setStatus('success', pkg.name)
107
97
  })
108
98
  } catch (e) {
109
99
  report.error(e, e.stack).set('error', e).setStatus('failure')
@@ -112,23 +102,18 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
112
102
  await clean(ctx)
113
103
  }
114
104
  report.setStatus('success').log('Great success!')
115
- })
105
+ }
116
106
 
117
- export const createContext = async ({flags, env: _env, cwd}) => {
107
+ export const createContext = async ({flags, env, cwd}) => {
118
108
  const {packages, queue, root, prev, graphs} = await topo({cwd, flags})
119
109
  const report = createReport({packages, queue, flags})
120
- const env = {...process.env, ..._env}
121
-
122
- log.secret(env.GH_TOKEN, env.GITHUB_TOKEN, env.NPM_TOKEN)
123
110
 
124
111
  $.report = report
125
112
  $.env = env
126
113
  $.verbose = !!(flags.debug || env.DEBUG) || $.verbose
127
114
  $.quiet = !$.verbose
115
+ $.memo = new Map()
128
116
 
129
- return {
130
- cwd, env, flags, root, packages, queue, prev, graphs, report,
131
- channels,
132
- run: queuefy(exec, flags.concurrency || os.cpus().length),
133
- }
117
+ return {cwd, env, flags, root, packages, queue, prev, graphs, report, channels,
118
+ run: queuefy(exec, flags.concurrency || os.cpus().length)}
134
119
  }