zx-bulk-release 3.1.8 → 3.1.10

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,13 @@
1
+ ## [3.1.10](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.9...v3.1.10) (2026-04-13)
2
+
3
+ ### Fixes & improvements
4
+ * docs: add architecture notes ([8760bb8](https://github.com/semrel-extra/zx-bulk-release/commit/8760bb8b9a73593ae192a0d16eea10b731efd206))
5
+
6
+ ## [3.1.9](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.8...v3.1.9) (2026-04-13)
7
+
8
+ ### Fixes & improvements
9
+ * perf: replace cosmiconfig with own config reader ([f980590](https://github.com/semrel-extra/zx-bulk-release/commit/f980590edbf9aa9311110207e24849c6373c0feb))
10
+
1
11
  ## [3.1.8](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.7...v3.1.8) (2026-04-13)
2
12
 
3
13
  ### Fixes & improvements
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # zx-bulk-release
2
- > [zx](https://github.com/google/zx)-based alternative for [multi-semantic-release](https://github.com/dhoulb/multi-semantic-release)
2
+ > A [zx](https://github.com/google/zx)-based tool for [conventional-commits](https://www.conventionalcommits.org/en/v1.0.0/)-driven secure release workflow for monorepos. Inspired by [multi-semantic-release](https://github.com/dhoulb/multi-semantic-release).
3
3
 
4
4
  [![CI](https://github.com/semrel-extra/zx-bulk-release/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/semrel-extra/zx-bulk-release/actions/workflows/ci.yml)
5
5
  [![Maintainability](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release/maintainability.svg)](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release)
@@ -18,7 +18,7 @@
18
18
  * **Coordinated delivery**: multiple release agents can safely serve the same monorepo concurrently via git-tag-based semaphores.
19
19
 
20
20
  > [!NOTE]
21
- > **[Migration guide v2 → v3](./MIGRATION_V2_V3.md)** | **[Delivery specification](./DELIVER_SPEC.md)** | **[Security model](./SECURITY.md)**
21
+ > **[Migration guide v2 → v3](./docs/MIGRATION_V2_V3.md)** | **[Delivery specification](./docs/DELIVER_SPEC.md)** | **[Security model](./docs/SECURITY.md)** | **[Architecture](./docs/ARCHITECTURE.md)**
22
22
 
23
23
  ## Roadmap
24
24
  * [x] Store release metrics to `meta`.
@@ -122,7 +122,7 @@ jobs:
122
122
  ```
123
123
 
124
124
  #### Four phases: receive + pack + verify + deliver
125
- Maximum supply chain security. Each phase has strict trust boundaries. See [SECURITY.md](./SECURITY.md) for the threat model.
125
+ Maximum supply chain security. Each phase has strict trust boundaries. See [SECURITY.md](./docs/SECURITY.md) for the threat model.
126
126
 
127
127
  - **receive** — runs *before* `yarn install`, consumes rebuild signals, analyzes, writes trusted context
128
128
  - **pack** — runs *after* deps install with zero credentials, builds and packs tars
@@ -212,8 +212,8 @@ await run({
212
212
  ```
213
213
 
214
214
  ## Config
215
- ### cosmiconfig
216
- Any [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) compliant format: `.releaserc`, `.release.json`, `.release.yaml`, etc in the package root or in the repo root dir.
215
+ ### Config files
216
+ [cosmiconfig](https://github.com/davidtheclark/cosmiconfig)-compatible lookup: `.releaserc`, `.release.json`, `.release.yaml`, `.releaserc.js`, `release.config.js`, or `release` key in `package.json`. Searched from the package root up to the repo root.
217
217
  ```json
218
218
  {
219
219
  "buildCmd": "yarn && yarn build",
@@ -405,7 +405,7 @@ parcel.{sha7}.{channel}.{name}.{version}.{hash6}.tar
405
405
  ```
406
406
  The `sha7` prefix groups all parcels of one commit. `name` is the sanitized package name (`@scope/pkg` → `scope-pkg`). The `hash6` suffix is a content hash for deduplication — two builds of the same commit producing identical content yield the same filename (last-writer-wins), while different content gets a different hash.
407
407
 
408
- A **directive** meta-parcel (`parcel.{sha7}.directive.{ts}.tar`) is generated alongside regular parcels. It contains the complete delivery map: package queue, per-package channel steps, and an authoritative list of parcel filenames. The directive enables coordinated delivery — see [DELIVER_SPEC.md](./DELIVER_SPEC.md).
408
+ A **directive** meta-parcel (`parcel.{sha7}.directive.{ts}.tar`) is generated alongside regular parcels. It contains the complete delivery map: package queue, per-package channel steps, and an authoritative list of parcel filenames. The directive enables coordinated delivery — see [DELIVER_SPEC.md](./docs/DELIVER_SPEC.md).
409
409
 
410
410
  The manifest contains `${{ENV_VAR}}` placeholders that are resolved by the courier at delivery time via `resolveManifest()`. This ensures credentials never touch the build phase.
411
411
 
@@ -432,7 +432,7 @@ When multiple zbr processes target the same monorepo concurrently, delivery is c
432
432
  4. **Conflict resolution** — if `git-tag` returns `conflict`, all parcels of that package are marked, and a `zbr-rebuild.{sha7}` tag signals CI to rebuild with fresh versions.
433
433
  5. **Partial delivery** — skipped parcels (missing credentials) remain valid tarballs. Another process with the right credentials can pick them up later.
434
434
 
435
- See [DELIVER_SPEC.md](./DELIVER_SPEC.md) for the full protocol specification.
435
+ See [DELIVER_SPEC.md](./docs/DELIVER_SPEC.md) for the full protocol specification.
436
436
 
437
437
  ### Credential flow
438
438
  ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
3
  "alias": "bulk-release",
4
- "version": "3.1.8",
4
+ "version": "3.1.10",
5
5
  "description": "zx-based alternative for multi-semantic-release",
6
6
  "type": "module",
7
7
  "exports": {
@@ -19,25 +19,24 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "test": "npm run test:unit",
22
- "test:unit": "uvu ./src/test -i fixtures -i utils -i integration",
22
+ "test:unit": "vitest run --dir src/test/js --exclude '**/integration*' --exclude '**/fixtures/**' --exclude '**/utils/**'",
23
23
  "test:it": "node ./src/test/js/integration.test.js",
24
- "test:cov": "c8 sh -c 'npx uvu ./src/test -i fixtures -i utils -i integration && node ./src/test/js/integration.test.js' && c8 report -r lcov",
24
+ "test:cov": "vitest run --coverage --dir src/test/js --exclude '**/fixtures/**' --exclude '**/utils/**'",
25
25
  "docs": "mkdir -p docs && cp ./README.md ./docs/README.md",
26
26
  "publish:beta": "npm publish --tag beta --no-git-tag-version",
27
27
  "build": "esbuild src/main/js/index.js --platform=node --outdir=target --bundle --format=esm --external:typescript"
28
28
  },
29
29
  "dependencies": {
30
30
  "@semrel-extra/topo": "^1.14.1",
31
- "cosmiconfig": "^9.0.1",
32
31
  "queuefy": "^1.2.1",
33
32
  "tar-stream": "^3.1.8",
34
33
  "zx-extra": "4.0.20"
35
34
  },
36
35
  "devDependencies": {
37
- "c8": "^11.0.0",
36
+ "@vitest/coverage-v8": "^4.1.4",
38
37
  "esbuild": "^0.28.0",
39
- "uvu": "^0.5.6",
40
- "verdaccio": "6.5.0"
38
+ "verdaccio": "6.5.0",
39
+ "vitest": "^4.1.4"
41
40
  },
42
41
  "publishConfig": {
43
42
  "access": "public"
@@ -47,10 +46,5 @@
47
46
  "url": "git+https://github.com/semrel-extra/zx-bulk-release.git"
48
47
  },
49
48
  "author": "Anton Golub <antongolub@antongolub.com>",
50
- "license": "MIT",
51
- "c8": {
52
- "exclude": [
53
- "src/test/**"
54
- ]
55
- }
49
+ "license": "MIT"
56
50
  }
@@ -1,4 +1,4 @@
1
- import { cosmiconfig } from 'cosmiconfig'
1
+ import { fs, path, YAML } from 'zx'
2
2
  import { asArray, camelize, memoizeBy } from './util.js'
3
3
  import { GH_URL, resolveGhApiUrl } from './post/api/gh.js'
4
4
  import { DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL } from './post/api/git.js'
@@ -29,12 +29,41 @@ export const defaultConfig = {
29
29
  export const getPkgConfig = async (cwd, env) =>
30
30
  normalizePkgConfig((await Promise.all(asArray(cwd).map(readPkgConfig))).find(Boolean) || defaultConfig, env)
31
31
 
32
- export const readPkgConfig = memoizeBy(async (cwd) => cosmiconfig(CONFIG_NAME, {
33
- searchPlaces: CONFIG_FILES,
34
- searchStrategy: 'global', // https://github.com/cosmiconfig/cosmiconfig/releases/tag/v9.0.0
35
- })
36
- .search(cwd)
37
- .then(r => r?.config))
32
+ export const cosmiconfig = (name, {searchPlaces}) => {
33
+ const load = async (filePath) => {
34
+ if (filePath.endsWith('.js') || filePath.endsWith('.cjs'))
35
+ return (await import(filePath)).default
36
+ const raw = await fs.readFile(filePath, 'utf8')
37
+ if (filePath.endsWith('.yaml') || filePath.endsWith('.yml'))
38
+ return YAML.parse(raw)
39
+ try { return JSON.parse(raw) } catch { return YAML.parse(raw) }
40
+ }
41
+
42
+ const search = async (cwd) => {
43
+ let dir = path.resolve(cwd)
44
+ while (true) {
45
+ for (const file of searchPlaces) {
46
+ const filePath = path.resolve(dir, file)
47
+ if (!await fs.pathExists(filePath)) continue
48
+ if (file === 'package.json') {
49
+ const pkg = await fs.readJson(filePath)
50
+ if (pkg[name]) return pkg[name]
51
+ continue
52
+ }
53
+ return load(filePath)
54
+ }
55
+ const parent = path.dirname(dir)
56
+ if (parent === dir) return
57
+ dir = parent
58
+ }
59
+ }
60
+
61
+ return {search}
62
+ }
63
+
64
+ export const readPkgConfig = memoizeBy(async (cwd) =>
65
+ cosmiconfig(CONFIG_NAME, {searchPlaces: CONFIG_FILES}).search(cwd)
66
+ )
38
67
 
39
68
  export const normalizePkgConfig = (config, env) => {
40
69
  const envConfig = parseEnv(env)