zx-bulk-release 3.1.0 → 3.1.1
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 +6 -0
- package/package.json +2 -2
- package/src/main/js/post/api/npm.js +5 -5
- package/src/main/js/post/courier/channels/changelog.js +4 -2
- package/src/main/js/post/courier/channels/git-tag.js +1 -2
- package/src/main/js/post/courier/channels/meta.js +1 -2
- package/src/main/js/post/courier/index.js +8 -18
- package/src/main/js/post/depot/context.js +17 -16
- package/src/main/js/post/depot/reconcile.js +0 -3
- package/src/main/js/post/depot/steps/pack.js +4 -10
- package/src/main/js/post/modes/deliver.js +1 -2
- package/src/main/js/post/modes/pack.js +1 -3
- package/src/main/js/post/modes/receive.js +4 -3
- package/src/main/js/post/modes/verify.js +8 -50
- package/src/main/js/post/parcel/index.js +4 -0
- package/src/main/js/post/parcel/verify.js +46 -0
- package/src/main/js/post/release.js +32 -0
- package/src/main/js/util.js +16 -0
- /package/src/main/js/post/{courier/parcel.js → parcel/build.js} +0 -0
- /package/src/main/js/post/{courier → parcel}/directive.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [3.1.1](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.0...v3.1.1) (2026-04-12)
|
|
2
|
+
|
|
3
|
+
### Fixes & improvements
|
|
4
|
+
* perf: add `--help` ([99fcfde](https://github.com/semrel-extra/zx-bulk-release/commit/99fcfde332c7730e335e46b4c2a8c844d53f8986))
|
|
5
|
+
* refactor: separate parcel subdomain ([b168044](https://github.com/semrel-extra/zx-bulk-release/commit/b16804497693b42998cea2638ff6d11bd29eb84f))
|
|
6
|
+
|
|
1
7
|
## [3.1.0](https://github.com/semrel-extra/zx-bulk-release/compare/v3.0.5...v3.1.0) (2026-04-12)
|
|
2
8
|
|
|
3
9
|
### Features
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zx-bulk-release",
|
|
3
3
|
"alias": "bulk-release",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"description": "zx-based alternative for multi-semantic-release",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/main/js/index.js",
|
|
9
9
|
"./test-utils": "./src/test/js/utils/repo.js",
|
|
10
|
-
"./meta": "./src/main/js/
|
|
10
|
+
"./meta": "./src/main/js/post/depot/generators/meta.js"
|
|
11
11
|
},
|
|
12
12
|
"bin": "./src/main/js/cli.js",
|
|
13
13
|
"files": [
|
|
@@ -146,18 +146,18 @@ const unzip = (stream, {pick, omit, cwd = process.cwd(), strip = 0} = {}) => new
|
|
|
146
146
|
extract.on('entry', ({name, type}, stream, cb) => {
|
|
147
147
|
const _name = safePath(strip ? name.split('/').slice(strip).join('/') : name)
|
|
148
148
|
const fp = _path.join(cwd, _name)
|
|
149
|
+
const skip = type !== 'file' || omit?.includes(_name) || (pick && !pick.includes(_name))
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
const chunks = []
|
|
151
152
|
stream.on('data', (chunk) => {
|
|
152
|
-
if (
|
|
153
|
-
data += chunk
|
|
153
|
+
if (!skip) chunks.push(chunk)
|
|
154
154
|
})
|
|
155
155
|
|
|
156
156
|
stream.on('end', () => {
|
|
157
|
-
if (
|
|
157
|
+
if (chunks.length) {
|
|
158
158
|
results.push(
|
|
159
159
|
_fs.mkdir(_path.dirname(fp), {recursive: true})
|
|
160
|
-
.then(() => _fs.writeFile(fp,
|
|
160
|
+
.then(() => _fs.writeFile(fp, Buffer.concat(chunks)))
|
|
161
161
|
)
|
|
162
162
|
}
|
|
163
163
|
cb()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {fs, path} from 'zx-extra'
|
|
2
2
|
import {queuefy} from 'queuefy'
|
|
3
3
|
import {fetchRepo, pushCommit} from '../../api/git.js'
|
|
4
4
|
import {log} from '../../log.js'
|
|
@@ -15,8 +15,10 @@ const run = queuefy(async (manifest, dir) => {
|
|
|
15
15
|
log.info('push changelog')
|
|
16
16
|
|
|
17
17
|
const _cwd = await fetchRepo({branch, origin: repoAuthedUrl, basicAuth: ghBasicAuth})
|
|
18
|
+
const filePath = path.resolve(_cwd, file)
|
|
19
|
+
const prev = await fs.readFile(filePath, 'utf8').catch(() => '')
|
|
20
|
+
await fs.outputFile(filePath, releaseNotes + '\n' + prev)
|
|
18
21
|
|
|
19
|
-
await $({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
|
|
20
22
|
await pushCommit({
|
|
21
23
|
branch,
|
|
22
24
|
msg,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {pushTag} from '../../api/git.js'
|
|
2
|
-
import {DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL} from '../../api/git.js'
|
|
1
|
+
import {pushTag, DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL} from '../../api/git.js'
|
|
3
2
|
|
|
4
3
|
export const isTagConflict = (e) =>
|
|
5
4
|
/already exists|updates were rejected|failed to push/i.test(e?.message || e?.stderr || '')
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {queuefy} from 'queuefy'
|
|
2
2
|
import {log} from '../../log.js'
|
|
3
3
|
import {pushCommit} from '../../api/git.js'
|
|
4
|
-
import {getArtifactPath, isAssetMode} from '../../depot/generators/meta.js'
|
|
5
|
-
import {prepareMeta} from '../../depot/generators/meta.js'
|
|
4
|
+
import {getArtifactPath, isAssetMode, prepareMeta} from '../../depot/generators/meta.js'
|
|
6
5
|
import {hasHigherVersion} from '../seniority.js'
|
|
7
6
|
|
|
8
7
|
const pushMetaBranch = queuefy(async (manifest, dir) => {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {$, tempy, within, path, semver, fs} from 'zx-extra'
|
|
2
2
|
import {unpackTar} from '../tar.js'
|
|
3
3
|
import {log} from '../log.js'
|
|
4
|
-
import {
|
|
4
|
+
import {pool} from '../../util.js'
|
|
5
|
+
import {scanDirectives, invalidateOrphans} from '../parcel/directive.js'
|
|
5
6
|
import {tryLock, unlock, signalRebuild} from './semaphore.js'
|
|
6
7
|
import gitTag from './channels/git-tag.js'
|
|
7
8
|
import meta from './channels/meta.js'
|
|
@@ -11,11 +12,16 @@ import ghPages from './channels/gh-pages.js'
|
|
|
11
12
|
import changelog from './channels/changelog.js'
|
|
12
13
|
import cmd from './channels/cmd.js'
|
|
13
14
|
|
|
14
|
-
export {buildParcels} from './parcel.js'
|
|
15
15
|
|
|
16
16
|
export const channels = {'git-tag': gitTag, meta, npm, 'gh-release': ghRelease, 'gh-pages': ghPages, changelog, cmd}
|
|
17
17
|
export const defaultOrder = ['git-tag', 'meta', 'npm', 'gh-release', 'gh-pages', 'changelog', 'cmd']
|
|
18
18
|
|
|
19
|
+
export const getActiveChannels = (pkg, channelNames, snapshot) =>
|
|
20
|
+
channelNames.filter(n => {
|
|
21
|
+
const ch = channels[n]
|
|
22
|
+
return ch && ch.transport !== false && (!snapshot || ch.snapshot) && ch.when(pkg)
|
|
23
|
+
})
|
|
24
|
+
|
|
19
25
|
export const prepare = async (names, pkg) => {
|
|
20
26
|
for (const n of names) await channels[n]?.prepare?.(pkg)
|
|
21
27
|
}
|
|
@@ -61,22 +67,6 @@ const openParcel = async (tarPath, env) => {
|
|
|
61
67
|
return {ch, resolved, destDir, tarPath}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
const pool = async (tasks, concurrency, fn) => {
|
|
65
|
-
const active = new Set()
|
|
66
|
-
let i = 0
|
|
67
|
-
await new Promise((resolve, reject) => {
|
|
68
|
-
const next = () => {
|
|
69
|
-
if (i >= tasks.length && active.size === 0) return resolve()
|
|
70
|
-
while (active.size < concurrency && i < tasks.length) {
|
|
71
|
-
const t = tasks[i++]
|
|
72
|
-
const p = fn(t).then(() => { active.delete(p); next() }, reject)
|
|
73
|
-
active.add(p)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
next()
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
70
|
export const inspect = async (tars, env = process.env) => {
|
|
81
71
|
const parcels = []
|
|
82
72
|
const skipped = []
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {fs, path} from 'zx-extra'
|
|
2
|
-
import {channels as channelRegistry} from '../courier/index.js'
|
|
3
2
|
|
|
4
|
-
const CONTEXT_FILE = '.zbr-context.json'
|
|
3
|
+
export const CONTEXT_FILE = '.zbr-context.json'
|
|
5
4
|
|
|
6
5
|
export const writeContext = async (cwd, context) => {
|
|
7
6
|
const filePath = path.resolve(cwd, CONTEXT_FILE)
|
|
@@ -9,22 +8,24 @@ export const writeContext = async (cwd, context) => {
|
|
|
9
8
|
return filePath
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
export const readContext = async (
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
|
|
16
|
-
} catch {
|
|
17
|
-
return null
|
|
11
|
+
export const readContext = async (filePath) => {
|
|
12
|
+
let data
|
|
13
|
+
try { data = await fs.readJson(filePath) } catch {
|
|
14
|
+
throw new Error(`context not found: ${filePath}`)
|
|
18
15
|
}
|
|
19
|
-
}
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
if (!data || typeof data !== 'object') throw new Error(`context is not an object: ${filePath}`)
|
|
18
|
+
if (!data.status) throw new Error(`context missing status: ${filePath}`)
|
|
19
|
+
if (data.status === 'proceed') {
|
|
20
|
+
if (!data.sha || !data.sha7) throw new Error(`context missing sha: ${filePath}`)
|
|
21
|
+
if (!data.packages || typeof data.packages !== 'object')
|
|
22
|
+
throw new Error(`context missing packages: ${filePath}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
26
27
|
|
|
27
|
-
export const buildContext = (packages, queue, sha, {
|
|
28
|
+
export const buildContext = (packages, queue, sha, {getChannels} = {}) => {
|
|
28
29
|
const pkgs = {}
|
|
29
30
|
for (const name of queue) {
|
|
30
31
|
const pkg = packages[name]
|
|
@@ -32,7 +33,7 @@ export const buildContext = (packages, queue, sha, {channelNames = [], snapshot
|
|
|
32
33
|
pkgs[name] = {
|
|
33
34
|
version: pkg.version,
|
|
34
35
|
tag: pkg.tag,
|
|
35
|
-
channels:
|
|
36
|
+
channels: getChannels ? getChannels(pkg) : [],
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -4,9 +4,6 @@ import {getRemoteTagSha, clearTagsCache} from '../api/git.js'
|
|
|
4
4
|
import {formatTag} from './generators/tag.js'
|
|
5
5
|
import {resolvePkgVersion} from './steps/analyze.js'
|
|
6
6
|
|
|
7
|
-
export const isTagConflict = (e) =>
|
|
8
|
-
/already exists|updates were rejected|failed to push/i.test(e?.message || e?.stderr || '')
|
|
9
|
-
|
|
10
7
|
export const preflight = async (pkg, ctx) => {
|
|
11
8
|
if (!pkg.tag) return 'ok'
|
|
12
9
|
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
import {$, tempy, fs, path} from 'zx-extra'
|
|
2
2
|
import {memoizeBy, asTuple} from '../../../util.js'
|
|
3
|
-
import {
|
|
3
|
+
import {prepare, getActiveChannels} from '../../courier/index.js'
|
|
4
|
+
import {buildParcels, PARCELS_DIR} from '../../parcel/index.js'
|
|
4
5
|
import {npmPersist} from '../../api/npm.js'
|
|
5
6
|
import {getRepo} from '../../api/git.js'
|
|
6
7
|
import {formatReleaseNotes} from '../generators/notes.js'
|
|
7
8
|
import {ghPrepareAssets} from '../../api/gh.js'
|
|
8
9
|
import {packTar, hashFile} from '../../tar.js'
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
const filterActive = (names, pkg, {snapshot = false} = {}) =>
|
|
12
|
-
names.filter(n => {
|
|
13
|
-
const ch = channels[n]
|
|
14
|
-
return ch && ch.transport !== false && (!snapshot || ch.snapshot) && ch.when(pkg)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
11
|
export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
18
12
|
const {channels: channelNames = [], flags} = ctx
|
|
19
13
|
const snapshot = !!flags.snapshot
|
|
20
|
-
const active =
|
|
14
|
+
const active = getActiveChannels(pkg, channelNames, snapshot)
|
|
21
15
|
|
|
22
16
|
await prepare(active, pkg)
|
|
23
17
|
await npmPersist(pkg)
|
|
24
18
|
|
|
25
|
-
const outputDir = flags.pack ? path.resolve(ctx.git.root, typeof flags.pack === 'string' ? flags.pack :
|
|
19
|
+
const outputDir = flags.pack ? path.resolve(ctx.git.root, typeof flags.pack === 'string' ? flags.pack : PARCELS_DIR) : null
|
|
26
20
|
const stageDir = outputDir || tempy.temporaryDirectory()
|
|
27
21
|
if (outputDir) await fs.ensureDir(outputDir)
|
|
28
22
|
const {repoName, repoHost, originUrl} = await getRepo(pkg.absPath, {basicAuth: pkg.config.ghBasicAuth})
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {$, glob, path} from 'zx-extra'
|
|
2
2
|
import {createReport, log} from '../log.js'
|
|
3
3
|
import {deliver} from '../courier/index.js'
|
|
4
|
-
|
|
5
|
-
const PARCELS_DIR = 'parcels'
|
|
4
|
+
import {PARCELS_DIR} from '../parcel/index.js'
|
|
6
5
|
|
|
7
6
|
export const runDeliver = async ({env, flags}) => {
|
|
8
7
|
const dir = typeof flags.deliver === 'string' ? flags.deliver : PARCELS_DIR
|
|
@@ -10,9 +10,7 @@ import {publish} from '../depot/steps/publish.js'
|
|
|
10
10
|
import {clean} from '../depot/steps/clean.js'
|
|
11
11
|
import {test} from '../depot/steps/test.js'
|
|
12
12
|
import {preflight} from '../depot/reconcile.js'
|
|
13
|
-
import {buildDirective} from '../
|
|
14
|
-
|
|
15
|
-
const PARCELS_DIR = 'parcels'
|
|
13
|
+
import {buildDirective, PARCELS_DIR} from '../parcel/index.js'
|
|
16
14
|
|
|
17
15
|
export const runPack = async ({cwd, env, flags}, ctx) => {
|
|
18
16
|
const {report, packages, queue, prev} = ctx
|
|
@@ -7,6 +7,7 @@ import {analyze} from '../depot/steps/analyze.js'
|
|
|
7
7
|
import {clean} from '../depot/steps/clean.js'
|
|
8
8
|
import {preflight} from '../depot/reconcile.js'
|
|
9
9
|
import {consumeRebuildSignal} from '../courier/semaphore.js'
|
|
10
|
+
import {getActiveChannels} from '../courier/index.js'
|
|
10
11
|
import {writeContext, buildContext} from '../depot/context.js'
|
|
11
12
|
import {getSha} from '../api/git.js'
|
|
12
13
|
import {setOutput, isRebuildTrigger} from '../api/gh.js'
|
|
@@ -17,7 +18,7 @@ export const runReceive = async ({cwd, env, flags}, ctx) => {
|
|
|
17
18
|
const sha = await getSha(cwd)
|
|
18
19
|
const sha7 = sha.slice(0, 7)
|
|
19
20
|
|
|
20
|
-
if (isRebuildTrigger(env)) {
|
|
21
|
+
if (isRebuildTrigger(env) && !flags.dryRun) {
|
|
21
22
|
const result = await consumeRebuildSignal(cwd, sha)
|
|
22
23
|
if (result?.exitCode !== 0 && result?.stderr?.includes('remote ref does not exist')) {
|
|
23
24
|
log.info(`rebuild signal already consumed by another process`)
|
|
@@ -45,9 +46,9 @@ export const runReceive = async ({cwd, env, flags}, ctx) => {
|
|
|
45
46
|
if (await preflight(pkg, pkg.ctx) === 'skip') { pkg.skipped = true; return }
|
|
46
47
|
})
|
|
47
48
|
|
|
49
|
+
const snapshot = !!flags.snapshot
|
|
48
50
|
const context = buildContext(packages, queue, sha, {
|
|
49
|
-
|
|
50
|
-
snapshot: !!flags.snapshot,
|
|
51
|
+
getChannels: (pkg) => getActiveChannels(pkg, ctx.channels, snapshot),
|
|
51
52
|
})
|
|
52
53
|
await writeContext(cwd, context)
|
|
53
54
|
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import {glob, path, fs} from 'zx-extra'
|
|
2
2
|
import {log} from '../log.js'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const PARCELS_DIR = 'parcels'
|
|
3
|
+
import {PARCELS_DIR, verifyParcels} from '../parcel/index.js'
|
|
4
|
+
import {CONTEXT_FILE, readContext} from '../depot/context.js'
|
|
6
5
|
|
|
7
6
|
export const runVerify = async ({cwd, flags}) => {
|
|
8
7
|
const inputDir = typeof flags.verify === 'string' ? flags.verify : PARCELS_DIR
|
|
9
|
-
const contextPath = typeof flags.context === 'string' ? flags.context : path.resolve(cwd,
|
|
8
|
+
const contextPath = typeof flags.context === 'string' ? flags.context : path.resolve(cwd, CONTEXT_FILE)
|
|
10
9
|
const outputDir = path.resolve(cwd, PARCELS_DIR)
|
|
11
10
|
|
|
12
11
|
log.info(`verifying parcels in ${inputDir} against ${contextPath}`)
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const context = await readContext(contextPath)
|
|
14
|
+
if (context.status !== 'proceed') {
|
|
15
|
+
log.info(`context status is '${context.status}', nothing to verify`)
|
|
16
|
+
return
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
const tars = await glob(path.join(inputDir, 'parcel.*.tar'))
|
|
@@ -23,54 +22,13 @@ export const runVerify = async ({cwd, flags}) => {
|
|
|
23
22
|
return
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
const {
|
|
27
|
-
const errors = []
|
|
28
|
-
const verified = []
|
|
29
|
-
|
|
30
|
-
for (const tarPath of tars) {
|
|
31
|
-
const name = path.basename(tarPath)
|
|
32
|
-
|
|
33
|
-
// sha7 prefix must match
|
|
34
|
-
if (!name.startsWith(`parcel.${sha7}.`)) {
|
|
35
|
-
errors.push(`sha mismatch: ${name}`)
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const channel = parcelChannel(name)
|
|
40
|
-
if (!channel) {
|
|
41
|
-
errors.push(`malformed name: ${name}`)
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (channel === 'directive') {
|
|
46
|
-
verified.push(tarPath)
|
|
47
|
-
continue
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// match to an expected package by tag
|
|
51
|
-
const belongsTo = Object.entries(expected).find(([, pkg]) =>
|
|
52
|
-
pkg.tag && name.includes(`.${pkg.tag}.`)
|
|
53
|
-
)
|
|
54
|
-
if (!belongsTo) {
|
|
55
|
-
errors.push(`unexpected parcel (no matching package): ${name}`)
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const [pkgName, pkg] = belongsTo
|
|
60
|
-
if (!pkg.channels.includes(channel)) {
|
|
61
|
-
errors.push(`unexpected channel '${channel}' for ${pkgName}: ${name}`)
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
verified.push(tarPath)
|
|
66
|
-
}
|
|
25
|
+
const {verified, errors} = verifyParcels(tars, context)
|
|
67
26
|
|
|
68
27
|
if (errors.length) {
|
|
69
28
|
for (const e of errors) log.error(`verify: ${e}`)
|
|
70
29
|
throw new Error(`parcel verification failed: ${errors.length} error(s)`)
|
|
71
30
|
}
|
|
72
31
|
|
|
73
|
-
// copy verified parcels to output
|
|
74
32
|
if (path.resolve(inputDir) !== outputDir) {
|
|
75
33
|
await fs.ensureDir(outputDir)
|
|
76
34
|
for (const tarPath of verified) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {path} from 'zx-extra'
|
|
2
|
+
import {parcelChannel} from './directive.js'
|
|
3
|
+
|
|
4
|
+
export const verifyParcels = (tars, context) => {
|
|
5
|
+
const {sha7, packages: expected} = context
|
|
6
|
+
const errors = []
|
|
7
|
+
const verified = []
|
|
8
|
+
|
|
9
|
+
for (const tarPath of tars) {
|
|
10
|
+
const name = path.basename(tarPath)
|
|
11
|
+
|
|
12
|
+
if (!name.startsWith(`parcel.${sha7}.`)) {
|
|
13
|
+
errors.push(`sha mismatch: ${name}`)
|
|
14
|
+
continue
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const channel = parcelChannel(name)
|
|
18
|
+
if (!channel) {
|
|
19
|
+
errors.push(`malformed name: ${name}`)
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (channel === 'directive') {
|
|
24
|
+
verified.push(tarPath)
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const belongsTo = Object.entries(expected).find(([, pkg]) =>
|
|
29
|
+
pkg.tag && name.includes(`.${pkg.tag}.`)
|
|
30
|
+
)
|
|
31
|
+
if (!belongsTo) {
|
|
32
|
+
errors.push(`unexpected parcel (no matching package): ${name}`)
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [pkgName, pkg] = belongsTo
|
|
37
|
+
if (!pkg.channels.includes(channel)) {
|
|
38
|
+
errors.push(`unexpected channel '${channel}' for ${pkgName}: ${name}`)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
verified.push(tarPath)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {verified, errors}
|
|
46
|
+
}
|
|
@@ -14,7 +14,39 @@ import {runPack} from './modes/pack.js'
|
|
|
14
14
|
|
|
15
15
|
const ZBR_VERSION = createRequire(import.meta.url)('../../../../package.json').version
|
|
16
16
|
|
|
17
|
+
const HELP = `
|
|
18
|
+
zx-bulk-release v${ZBR_VERSION}
|
|
19
|
+
|
|
20
|
+
Usage: npx zx-bulk-release [options]
|
|
21
|
+
|
|
22
|
+
Modes:
|
|
23
|
+
(no flags) All-in-one: analyze, build, test, pack, deliver
|
|
24
|
+
--receive Analyze & preflight. Writes .zbr-context.json. Run BEFORE deps install.
|
|
25
|
+
--pack [dir] Build, test, pack tars to dir. [default: parcels]
|
|
26
|
+
--verify [dir] Validate parcels against context, copy to parcels/. [default: parcels]
|
|
27
|
+
--deliver [dir] Deliver parcels through channels. [default: parcels]
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--context <path> Path to .zbr-context.json (with --verify). [default: .zbr-context.json]
|
|
31
|
+
--dry-run, --no-publish Disable any publish / remote-mutating logic.
|
|
32
|
+
--no-build Skip buildCmd.
|
|
33
|
+
--no-test Skip testCmd.
|
|
34
|
+
--snapshot Publish snapshot versions to npm only.
|
|
35
|
+
--ignore <a,b> Packages to ignore.
|
|
36
|
+
--include-private Include private packages.
|
|
37
|
+
--concurrency <n> Build/publish thread limit. [default: os.cpus().length]
|
|
38
|
+
--only-workspace-deps Recognize only workspace: deps as graph edges.
|
|
39
|
+
--no-npm-fetch Disable npm artifact fetching.
|
|
40
|
+
--report <path> Persist release state to file.
|
|
41
|
+
--debug Enable verbose mode.
|
|
42
|
+
-v, --version Print version.
|
|
43
|
+
-h, --help Show this help.
|
|
44
|
+
`.trim()
|
|
45
|
+
|
|
17
46
|
export const run = async ({cwd = process.cwd(), env: _env, flags = {}} = {}) => within(async () => {
|
|
47
|
+
if (flags.h || flags.help)
|
|
48
|
+
return console.log(HELP)
|
|
49
|
+
|
|
18
50
|
if (flags.v || flags.version)
|
|
19
51
|
return console.log(ZBR_VERSION)
|
|
20
52
|
|
package/src/main/js/util.js
CHANGED
|
@@ -72,3 +72,19 @@ export const memoizeBy = (fn, getKey = v => v) => {
|
|
|
72
72
|
export const camelize = s => s.replace(/-./g, x => x[1].toUpperCase())
|
|
73
73
|
|
|
74
74
|
export const asArray = v => Array.isArray(v) ? v : [v]
|
|
75
|
+
|
|
76
|
+
export const pool = async (tasks, concurrency, fn) => {
|
|
77
|
+
const active = new Set()
|
|
78
|
+
let i = 0
|
|
79
|
+
await new Promise((resolve, reject) => {
|
|
80
|
+
const next = () => {
|
|
81
|
+
if (i >= tasks.length && active.size === 0) return resolve()
|
|
82
|
+
while (active.size < concurrency && i < tasks.length) {
|
|
83
|
+
const t = tasks[i++]
|
|
84
|
+
const p = fn(t).then(() => { active.delete(p); next() }, reject)
|
|
85
|
+
active.add(p)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
next()
|
|
89
|
+
})
|
|
90
|
+
}
|
|
File without changes
|
|
File without changes
|