zx-bulk-release 3.1.0 → 3.1.2
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 +13 -0
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/main/js/cli.js +46 -2
- package/src/main/js/index.js +1 -1
- 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/generators/meta.js +2 -2
- package/src/main/js/post/depot/reconcile.js +0 -3
- package/src/main/js/post/depot/steps/contextify.js +3 -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 +10 -3
- 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,16 @@
|
|
|
1
|
+
## [3.1.2](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.1...v3.1.2) (2026-04-13)
|
|
2
|
+
|
|
3
|
+
### Fixes & improvements
|
|
4
|
+
* perf: append pkg scope to internal zx logs ([08471b9](https://github.com/semrel-extra/zx-bulk-release/commit/08471b9c87d9ef2bf24b30a99977c308cee1feaf))
|
|
5
|
+
* perf: update default zx ctx file name ([9857dcd](https://github.com/semrel-extra/zx-bulk-release/commit/9857dcdcf9d1e5221eb3fc62f4919a8af2b885fc))
|
|
6
|
+
* refactor: enhance cli ([8909ab2](https://github.com/semrel-extra/zx-bulk-release/commit/8909ab2496402102b33fa134275ac415b6d8297a))
|
|
7
|
+
|
|
8
|
+
## [3.1.1](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.0...v3.1.1) (2026-04-12)
|
|
9
|
+
|
|
10
|
+
### Fixes & improvements
|
|
11
|
+
* perf: add `--help` ([99fcfde](https://github.com/semrel-extra/zx-bulk-release/commit/99fcfde332c7730e335e46b4c2a8c844d53f8986))
|
|
12
|
+
* refactor: separate parcel subdomain ([b168044](https://github.com/semrel-extra/zx-bulk-release/commit/b16804497693b42998cea2638ff6d11bd29eb84f))
|
|
13
|
+
|
|
1
14
|
## [3.1.0](https://github.com/semrel-extra/zx-bulk-release/compare/v3.0.5...v3.1.0) (2026-04-12)
|
|
2
15
|
|
|
3
16
|
### Features
|
package/README.md
CHANGED
|
@@ -44,10 +44,10 @@ GH_TOKEN=ghtoken GH_USER=username NPM_TOKEN=npmtoken npx zx-bulk-release [opts]
|
|
|
44
44
|
```
|
|
45
45
|
| Flag | Description | Default |
|
|
46
46
|
|------------------------------|---------------------------------------------------------------------------------------------------|------------------|
|
|
47
|
-
| `--receive` | Consume rebuild signal, analyze, preflight. Writes
|
|
47
|
+
| `--receive` | Consume rebuild signal, analyze, preflight. Writes `zbr-context.json`. Run before deps install. | |
|
|
48
48
|
| `--pack [dir]` | Pack only: build, test, and write delivery tars to `dir`. No credentials needed. | `parcels` |
|
|
49
49
|
| `--verify [dir]` | Verify untrusted parcels against context, copy valid ones to `parcels/`. Use `--context` for path. | `parcels` |
|
|
50
|
-
| `--context <path>` | Path to trusted
|
|
50
|
+
| `--context <path>` | Path to trusted `zbr-context.json` (used with `--verify`). | `zbr-context.json` |
|
|
51
51
|
| `--deliver [dir]` | Deliver only: read tars from `dir` and run delivery channels. No source code needed. | `parcels` |
|
|
52
52
|
| `--ignore` | Packages to ignore: `a, b` | |
|
|
53
53
|
| `--include-private` | Include `private` packages | `false` |
|
|
@@ -152,7 +152,7 @@ jobs:
|
|
|
152
152
|
- uses: actions/upload-artifact@v4
|
|
153
153
|
with:
|
|
154
154
|
name: context-${{ github.run_id }}
|
|
155
|
-
path:
|
|
155
|
+
path: zbr-context.json
|
|
156
156
|
|
|
157
157
|
# pack — deps installed, hostile code may run, zero credentials
|
|
158
158
|
- if: steps.receive.outputs.status == 'proceed'
|
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.2",
|
|
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": [
|
package/src/main/js/cli.js
CHANGED
|
@@ -1,7 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import {argv} from 'zx-extra'
|
|
4
|
-
import {run} from './index.js'
|
|
4
|
+
import {run, version} from './index.js'
|
|
5
5
|
import {normalizeFlags} from './config.js'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const flags = normalizeFlags(argv)
|
|
8
|
+
|
|
9
|
+
if (flags.h || flags.help) {
|
|
10
|
+
console.log(`zx-bulk-release v${version}
|
|
11
|
+
|
|
12
|
+
Usage: npx zx-bulk-release [options]
|
|
13
|
+
|
|
14
|
+
Modes:
|
|
15
|
+
(no flags) All-in-one: analyze, build, test, pack, deliver
|
|
16
|
+
--receive Analyze & preflight, write zbr-context.json. Run BEFORE deps install
|
|
17
|
+
--pack [dir] Build, test, pack tars to dir [default: parcels]
|
|
18
|
+
--verify [input-dir] Validate parcels against context, copy valid to parcels/ [default: parcels]
|
|
19
|
+
--deliver [dir] Deliver parcels through channels [default: parcels]
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--context <path> Trusted context path (with --verify) [default: zbr-context.json]
|
|
23
|
+
--dry-run, --no-publish Disable publish and any remote-mutating operations
|
|
24
|
+
--no-build Skip buildCmd
|
|
25
|
+
--no-test Skip testCmd
|
|
26
|
+
--snapshot Publish snapshot versions to npm only
|
|
27
|
+
--ignore <a,b> Packages to ignore
|
|
28
|
+
--include-private Include private packages
|
|
29
|
+
--concurrency <n> Build/publish thread limit [default: os.cpus().length]
|
|
30
|
+
--only-workspace-deps Recognize only workspace: deps as graph edges
|
|
31
|
+
--no-npm-fetch Disable npm artifact fetching
|
|
32
|
+
--report <path> Persist release state to file
|
|
33
|
+
--debug Enable verbose mode
|
|
34
|
+
-v, --version Print version
|
|
35
|
+
-h, --help Show this help`)
|
|
36
|
+
process.exit(0)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (flags.v || flags.version) {
|
|
40
|
+
console.log(version)
|
|
41
|
+
process.exit(0)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await run({flags})
|
|
46
|
+
process.exit(0)
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e?.exitCode !== undefined) process.exit(e.exitCode)
|
|
49
|
+
console.error(e)
|
|
50
|
+
process.exit(1)
|
|
51
|
+
}
|
package/src/main/js/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {run} from './post/release.js'
|
|
1
|
+
export {run, version} from './post/release.js'
|
|
@@ -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 = '
|
|
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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Meta generator: builds pkg.meta payload and resolves latest-release meta from git tags / gh assets / meta branch.
|
|
2
2
|
|
|
3
3
|
import {semver, $, fs, path} from 'zx-extra'
|
|
4
|
-
import {fetchRepo, getTags as getGitTags, getRepo} from '../../api/git.js'
|
|
4
|
+
import {fetchRepo, getTags as getGitTags, getRepo, getSha} from '../../api/git.js'
|
|
5
5
|
import {fetchManifest} from '../../api/npm.js'
|
|
6
6
|
import {ghGetAsset} from '../../api/gh.js'
|
|
7
7
|
import {parseTag} from './tag.js'
|
|
@@ -16,7 +16,7 @@ export const prepareMeta = async (pkg) => {
|
|
|
16
16
|
if (type === null) return
|
|
17
17
|
|
|
18
18
|
const {absPath: cwd} = pkg
|
|
19
|
-
const hash =
|
|
19
|
+
const hash = await getSha(cwd)
|
|
20
20
|
pkg.meta = {
|
|
21
21
|
META_VERSION: '1',
|
|
22
22
|
hash,
|
|
@@ -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
|
|
|
@@ -42,9 +42,9 @@ export const contextify = async (pkg, ctx) => {
|
|
|
42
42
|
pkg.ctx = {
|
|
43
43
|
...ctx,
|
|
44
44
|
git: {
|
|
45
|
-
sha: await getSha(
|
|
46
|
-
root: await getRoot(
|
|
47
|
-
timestamp: await getCommitTimestamp(
|
|
45
|
+
sha: await getSha(ctx.cwd),
|
|
46
|
+
root: await getRoot(ctx.cwd),
|
|
47
|
+
timestamp: await getCommitTimestamp(ctx.cwd),
|
|
48
48
|
},
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -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,14 +14,21 @@ import {runPack} from './modes/pack.js'
|
|
|
14
14
|
|
|
15
15
|
const ZBR_VERSION = createRequire(import.meta.url)('../../../../package.json').version
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
if (flags.v || flags.version)
|
|
19
|
-
return console.log(ZBR_VERSION)
|
|
17
|
+
export {ZBR_VERSION as version}
|
|
20
18
|
|
|
19
|
+
export const run = async ({cwd = process.cwd(), env: _env, flags = {}} = {}) => within(async () => {
|
|
21
20
|
const env = {...process.env, ..._env}
|
|
22
21
|
log.secret(env.GH_TOKEN, env.GITHUB_TOKEN, env.NPM_TOKEN)
|
|
23
22
|
log.info(`zx-bulk-release@${ZBR_VERSION}`)
|
|
24
23
|
|
|
24
|
+
const _log = $.log
|
|
25
|
+
$.log = (entry) => {
|
|
26
|
+
if (entry.kind === 'cmd' && $.scope) {
|
|
27
|
+
entry = {...entry, cmd: `[${$.scope}] ${entry.cmd}`}
|
|
28
|
+
}
|
|
29
|
+
_log(entry)
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
if (flags.verify) return runVerify({cwd, flags})
|
|
26
33
|
if (flags.deliver) return runDeliver({env, flags})
|
|
27
34
|
|
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
|