zx-bulk-release 2.21.0 → 3.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +140 -28
  3. package/package.json +12 -5
  4. package/src/main/js/index.js +1 -1
  5. package/src/main/js/{processor → post}/api/gh.js +0 -27
  6. package/src/main/js/{processor → post}/api/git.js +2 -10
  7. package/src/main/js/{processor → post}/api/npm.js +4 -2
  8. package/src/main/js/post/courier/channels/changelog.js +29 -0
  9. package/src/main/js/{processor/publishers → post/courier/channels}/cmd.js +1 -0
  10. package/src/main/js/post/courier/channels/gh-pages.js +30 -0
  11. package/src/main/js/post/courier/channels/gh-release.js +35 -0
  12. package/src/main/js/post/courier/channels/meta.js +34 -0
  13. package/src/main/js/post/courier/channels/npm.js +26 -0
  14. package/src/main/js/post/courier/index.js +113 -0
  15. package/src/main/js/post/courier/parcel.js +77 -0
  16. package/src/main/js/{processor → post/depot}/exec.js +2 -2
  17. package/src/main/js/{processor → post/depot}/generators/meta.js +3 -3
  18. package/src/main/js/{processor → post/depot}/generators/notes.js +1 -1
  19. package/src/main/js/{processor → post/depot}/steps/analyze.js +2 -2
  20. package/src/main/js/{processor → post/depot}/steps/build.js +2 -2
  21. package/src/main/js/{processor → post/depot}/steps/clean.js +2 -2
  22. package/src/main/js/{processor → post/depot}/steps/contextify.js +3 -3
  23. package/src/main/js/post/depot/steps/pack.js +59 -0
  24. package/src/main/js/post/depot/steps/publish.js +29 -0
  25. package/src/main/js/{processor → post/depot}/steps/test.js +1 -1
  26. package/src/main/js/{processor → post}/release.js +44 -37
  27. package/src/main/js/post/tar.js +73 -0
  28. package/src/test/js/utils/gh-server.js +33 -0
  29. package/src/test/js/utils/mock.js +132 -0
  30. package/src/test/js/{test-utils.js → utils/repo.js} +3 -3
  31. package/src/main/js/processor/publishers/changelog.js +0 -26
  32. package/src/main/js/processor/publishers/gh-pages.js +0 -32
  33. package/src/main/js/processor/publishers/gh-release.js +0 -41
  34. package/src/main/js/processor/publishers/meta.js +0 -58
  35. package/src/main/js/processor/publishers/npm.js +0 -15
  36. package/src/main/js/processor/steps/publish.js +0 -39
  37. package/src/main/js/processor/steps/teardown.js +0 -58
  38. /package/src/main/js/{processor → post/depot}/deps.js +0 -0
  39. /package/src/main/js/{processor → post/depot}/generators/tag.js +0 -0
  40. /package/src/main/js/{processor → post}/log.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [3.0.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.21.1...v3.0.0) (2026-04-11)
2
+
3
+ ### Fixes & improvements
4
+ * refactor: enhance courier inners ([0a75468](https://github.com/semrel-extra/zx-bulk-release/commit/0a75468a1736291d194e307c8fef39cbaae55948))
5
+ * refactor: use `parcels` as default artifacts dir ([b1c589e](https://github.com/semrel-extra/zx-bulk-release/commit/b1c589edfcdfba2c548c09f35950b058e6cc2f8c))
6
+ * refactor: simplify parcel contracts ([0a93948](https://github.com/semrel-extra/zx-bulk-release/commit/0a93948b2b4013ad3a620bcf93cf468651c0380b))
7
+ * refactor: move git-tag phase to depot ([a736ead](https://github.com/semrel-extra/zx-bulk-release/commit/a736ead83a75014f4bf592b9b60d5a5073bb4381))
8
+ * refactor: extract pack step from publish ([213a286](https://github.com/semrel-extra/zx-bulk-release/commit/213a28603ccfd3a9deed0d48652bca2864e17ef9))
9
+ * refactor: lift api/ to post/ level as shared ([bc51e2c](https://github.com/semrel-extra/zx-bulk-release/commit/bc51e2c8e9517261de5107a7b67680018f8fe71d))
10
+ * refactor: extract courier module with sealed directive ([20617d8](https://github.com/semrel-extra/zx-bulk-release/commit/20617d83f87b0409963b178b8a34fd11ad22df33))
11
+
12
+ ### BREAKING CHANGES
13
+ * - --recover flag removed. Use --deliver <dir> to re-deliver pre-packed tars. ([c45ab47](https://github.com/semrel-extra/zx-bulk-release/commit/c45ab47a853a887f45abe6a0fa27eade0f71c4dc))
14
+
15
+ ### Features
16
+ * feat: seal delivery flow — tar containers, template credentials ([2ba819a](https://github.com/semrel-extra/zx-bulk-release/commit/2ba819aef7e2d74f55db6bd6fc05f6589ddbc700))
17
+
18
+ ## [2.21.1](https://github.com/semrel-extra/zx-bulk-release/compare/v2.21.0...v2.21.1) (2026-04-11)
19
+
20
+ ### Fixes & improvements
21
+ * docs: add snapshot flow example ([5b2c2b5](https://github.com/semrel-extra/zx-bulk-release/commit/5b2c2b5faac712892c9feeea7ebae6e1dd36e93f))
22
+
1
23
  ## [2.21.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.20.0...v2.21.0) (2026-04-10)
2
24
 
3
25
  ### Fixes & improvements
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # zx-bulk-release
2
2
  > [zx](https://github.com/google/zx)-based alternative for [multi-semantic-release](https://github.com/dhoulb/multi-semantic-release)
3
3
 
4
- [![CI](https://github.com/semrel-extra/zx-bulk-release/workflows/CI/badge.svg?branch=master)](https://github.com/semrel-extra/zx-bulk-release/actions)
5
- [![Maintainability](https://qlty.sh/badges/semrel-extra/zx-bulk-release/maintainability.svg)](https://qlty.sh/gh/semrel-extra/zx-bulk-release)
6
- [![Test Coverage](https://qlty.sh/badges/semrel-extra/zx-bulk-release/test_coverage.svg)](https://qlty.sh/gh/semrel-extra/zx-bulk-release)
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
+ [![Maintainability](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release/maintainability.svg)](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release)
6
+ [![Code Coverage](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release/coverage.svg)](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release)
7
7
  [![npm (tag)](https://img.shields.io/npm/v/zx-bulk-release)](https://www.npmjs.com/package/zx-bulk-release)
8
8
 
9
9
  ## Key features
@@ -14,10 +14,11 @@
14
14
  * Pkg changelogs go to `changelog` branch (configurable).
15
15
  * Docs are published to `gh-pages` branch (configurable).
16
16
  * No extra builds. The required deps are fetched from the pkg registry (`npmFetch` config opt).
17
+ * **Two-phase pipeline**: build and delivery can run as separate jobs with isolated credentials.
17
18
 
18
19
  ## Roadmap
19
20
  * [x] Store release metrics to `meta`.
20
- * [ ] ~~Self-repair. Restore broken/missing metadata from external registries (npm, pypi, m2)~~. Tags should be the only source of truth
21
+ * [x] Two-phase pipeline (pack / deliver).
21
22
  * [ ] Multistack. Add support for java/kt/py.
22
23
  * [ ] Semaphore. Let several release agents to serve the monorepo at the same time.
23
24
 
@@ -25,7 +26,6 @@
25
26
  * macOS / linux
26
27
  * Node.js >= 16.0.0
27
28
  * npm >=7 / yarn >= 3
28
- * ~~wget~~
29
29
  * tar
30
30
  * git
31
31
 
@@ -38,21 +38,56 @@ yarn add zx-bulk-release
38
38
  ```shell
39
39
  GH_TOKEN=ghtoken GH_USER=username NPM_TOKEN=npmtoken npx zx-bulk-release [opts]
40
40
  ```
41
- | Flag | Description | Default |
42
- |------------------------------|---------------------------------------------------------------------------------------------------------------------------|------------------|
43
- | `--ignore` | Packages to ignore: `a, b` | |
44
- | `--include-private` | Include `private` packages | `false` |
45
- | `--concurrency` | `build/publish` threads limit | `os.cpus.length` |
46
- | `--no-build` | Skip `buildCmd` invoke | |
47
- | `--no-test` | Disable `testCmd` run | |
48
- | `--no-npm-fetch` | Disable npm artifacts fetching | |
49
- | `--only-workspace-deps` | Recognize only `workspace:` deps as graph edges | |
50
- | `--dry-run` / `--no-publish` | Disable any publish logic | |
51
- | `--report` | Persist release state to file | |
52
- | `--snapshot` | Disable any publishing steps except of `npm` and `publishCmd` (if defined), then push packages to the `snapshot` channel | |
53
- | `--recover` | Remove orphan git tags (tag exists but npm publish failed) and exit. Re-run without this flag to release. | |
54
- | `--debug` | Enable [zx](https://github.com/google/zx#verbose) verbose mode | |
55
- | `--version` / `-v` | Print own version | |
41
+ | Flag | Description | Default |
42
+ |------------------------------|---------------------------------------------------------------------------------------------------|------------------|
43
+ | `--pack [dir]` | Pack only: build, test, and write delivery tars to `dir`. No credentials needed. | `parcels` |
44
+ | `--deliver [dir]` | Deliver only: read tars from `dir` and run delivery channels. No source code needed. | `parcels` |
45
+ | `--ignore` | Packages to ignore: `a, b` | |
46
+ | `--include-private` | Include `private` packages | `false` |
47
+ | `--concurrency` | `build/publish` threads limit | `os.cpus.length` |
48
+ | `--no-build` | Skip `buildCmd` invoke | |
49
+ | `--no-test` | Disable `testCmd` run | |
50
+ | `--no-npm-fetch` | Disable npm artifacts fetching | |
51
+ | `--only-workspace-deps` | Recognize only `workspace:` deps as graph edges | |
52
+ | `--dry-run` / `--no-publish` | Disable any publish logic | |
53
+ | `--report` | Persist release state to file | |
54
+ | `--snapshot` | Publish only to `npm` snapshot channel and run `publishCmd` (if defined), skip everything else | |
55
+ | `--debug` | Enable [zx](https://github.com/google/zx#verbose) verbose mode | |
56
+ | `--version` / `-v` | Print own version | |
57
+
58
+ ### Two-phase pipeline
59
+ By default, zbr runs the full pipeline in a single process. For better security isolation, split build and delivery into separate CI jobs:
60
+
61
+ ```yaml
62
+ # Job 1: build (minimal privileges — source code access only)
63
+ jobs:
64
+ pack:
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ with: { fetch-depth: 0 }
69
+ - run: npx zx-bulk-release --pack
70
+ - uses: actions/upload-artifact@v4
71
+ with: { name: parcels, path: parcels, retention-days: 1 }
72
+
73
+ # Job 2: deliver (only delivery credentials, no source code)
74
+ deliver:
75
+ needs: pack
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/download-artifact@v4
79
+ with: { name: parcels, path: parcels }
80
+ - run: npx zx-bulk-release --deliver
81
+ env:
82
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
83
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
84
+ - uses: actions/upload-artifact@v4
85
+ with: { name: parcels, path: parcels, overwrite: true, retention-days: 1 }
86
+ ```
87
+
88
+ After delivery, each tar is replaced with a marker (`released` or `skip`). The final `upload-artifact` syncs these markers back to CI storage, so a re-run of the deliver job will skip already-delivered parcels. Artifacts expire naturally via retention policy.
89
+
90
+ **Recovery** is simply re-running the deliver job. Only undelivered tars (not yet replaced with markers) will be processed. No rebuild required.
56
91
 
57
92
  ### JS API
58
93
  ```js
@@ -147,6 +182,50 @@ OIDC mode is also auto-detected when `NPM_TOKEN` is not set and `ACTIONS_ID_TOKE
147
182
 
148
183
  When OIDC is active, `NPM_TOKEN` and `NPMRC` are ignored for publishing and `--provenance` is enabled automatically.
149
184
 
185
+ ### Snapshot releases from PRs
186
+ The `--snapshot` flag publishes packages to the `snapshot` npm dist-tag with a pre-release version like `1.2.1-snap.a3f0c12`. This is useful for testing changes from a feature branch before merging.
187
+
188
+ **What snapshot does differently:**
189
+ - Version gets a `-snap.<short-sha>` suffix instead of a clean bump
190
+ - Git release tags are **not** pushed
191
+ - Only `npm` and `publishCmd` channels run (no gh-release, no changelog, no gh-pages, no meta)
192
+ - npm tag is `snapshot` instead of `latest`
193
+
194
+ **Workflow example** (`.github/workflows/snapshot.yml`):
195
+ ```yaml
196
+ name: Snapshot
197
+ on:
198
+ pull_request:
199
+ types: [labeled]
200
+
201
+ jobs:
202
+ snapshot:
203
+ if: github.event.label.name == 'snapshot'
204
+ runs-on: ubuntu-latest
205
+ permissions:
206
+ contents: read
207
+ id-token: write # for OIDC trusted publishing
208
+ steps:
209
+ - uses: actions/checkout@v4
210
+ with:
211
+ fetch-depth: 0
212
+ - uses: actions/setup-node@v4
213
+ with:
214
+ node-version: 20
215
+ - run: yarn install
216
+ - run: npx zx-bulk-release --snapshot
217
+ env:
218
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
219
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
220
+ ```
221
+
222
+ **How to use:**
223
+ 1. Push a feature branch and open a PR.
224
+ 2. Add the `snapshot` label to the PR.
225
+ 3. The workflow publishes snapshot versions to npm.
226
+ 4. Consumers install snapshots via `npm install yourpkg@snapshot`.
227
+ 5. After merge, the regular release flow on `master` publishes clean versions to `latest`.
228
+
150
229
  ### Selective testing along the change graph
151
230
  In a monorepo, `--dry-run` combined with `--no-build` lets you run tests only for packages affected by the current changes — following the dependency graph, without publishing anything. This gives you a precise CI check scoped to what actually changed:
152
231
  ```shell
@@ -160,10 +239,22 @@ See [antongolub/misc](https://github.com/antongolub/misc) for a real-world examp
160
239
  * [antongolub/misc](https://github.com/antongolub/misc)
161
240
 
162
241
  ## Implementation notes
242
+ ### Architecture
243
+ The release pipeline is split into three subsystems under `post/`:
244
+
245
+ ```
246
+ post/
247
+ depot/ — preparation: analysis, versioning, building, testing, tar packing
248
+ courier/ — sealed delivery: receives self-describing tars and delivers through channels
249
+ api/ — shared infrastructure wrappers (git, npm, gh)
250
+ ```
251
+
252
+ This separation ensures that courier never touches the project directory — it works only with pre-packed tars and credentials resolved at delivery time. The two subsystems can run in separate CI jobs with different privilege levels.
253
+
163
254
  ### Flow
164
255
  ```
165
- topo ─► contextify ─► analyze ──► build ──► test ──► publish ─► clean
166
- (per pkg) (per pkg) (per pkg) (per pkg) (per pkg)
256
+ topo ─► contextify ─► analyze ──► build ──► test ──► pack ──► publish ─► clean
257
+ (per pkg) (per pkg) (per pkg) (per pkg) (per pkg) (per pkg)
167
258
  ```
168
259
  [`@semrel-extra/topo`](https://github.com/semrel-extra/topo) resolves the release queue respecting dependency graphs. The graph allows parallel execution where the dependency tree permits; `memoizeBy` prevents duplicate work when a package is reached by multiple paths.
169
260
 
@@ -175,7 +266,8 @@ Each step has a uniform signature `(pkg, ctx)`:
175
266
  - **`analyze`** — determines semantic changes, release type, and next version.
176
267
  - **`build`** — runs `buildCmd` (with dep traversal and optional npm artifact fetch).
177
268
  - **`test`** — runs `testCmd`.
178
- - **`publish`** — orchestrates the publisher registry: prepare (serial) run (parallel) rollback on failure.
269
+ - **`pack`** — stages delivery artifacts into self-describing tar containers (`npm pack`, docs copy, assets, release notes). Each tar is named `{tag}.{channel}.{hash8}.tar` and contains a `manifest.json` with channel name, delivery instructions, and template credentials (`${{ENV_VAR}}`). After this step, everything the courier needs is outside the project dir.
270
+ - **`publish`** — pushes the release tag (commitment point), hands tars to courier's `deliver()`, runs `cmd` channel separately.
179
271
  - **`clean`** — restores `package.json` files and unsets git user config.
180
272
 
181
273
  Set `config.releaseRules` to override the default rules preset:
@@ -187,19 +279,39 @@ Set `config.releaseRules` to override the default rules preset:
187
279
  ]
188
280
  ```
189
281
 
190
- ### Publishers
191
- Publish targets are a registry of `{name, when, prepare?, run, undo?, snapshot?}` objects:
282
+ ### Tar containers
283
+ Each delivery artifact is a self-describing tar archive:
284
+ ```
285
+ {tag}.{channel}.{hash8}.tar
286
+ manifest.json — channel name, delivery params, template credentials
287
+ package.tgz — (npm channel) npm tarball
288
+ assets/ — (gh-release channel) release assets
289
+ docs/ — (gh-pages channel) documentation files
290
+ ```
291
+
292
+ 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.
293
+
294
+ ### Channels
295
+ Delivery channels are a registry of `{name, when, prepare?, run, requires?, snapshot?, transport?}` objects:
192
296
  - **meta** — pushes release metadata to the `meta` branch (or as a GH release asset).
193
297
  - **npm** — publishes to the npm registry.
194
298
  - **gh-release** — creates a GitHub release with optional file assets.
195
299
  - **gh-pages** — pushes docs to a `gh-pages` branch.
196
300
  - **changelog** — pushes a changelog entry to a `changelog` branch.
197
- - **cmd** — runs a custom `publishCmd`.
301
+ - **cmd** — runs a custom `publishCmd` (depot-side, not through courier; `transport: false`).
302
+
303
+ Each channel declares `requires` — a list of manifest fields that must be present after credential resolution. Courier validates all parcels before running any channel (fail-fast).
198
304
 
199
- Teardown walks the registry in reverse, calling `undo()` on each publisher for rollback/recovery.
305
+ ### Credential flow
306
+ ```
307
+ depot (build phase) courier (deliver phase)
308
+ manifest: { token: '${{NPM_TOKEN}}' } → resolveManifest(manifest, env)
309
+ → { token: 'actual-secret' }
310
+ ```
311
+ Template credentials (`${{ENV_VAR}}`) are written into manifests at pack time. Courier resolves them from `process.env` at delivery time. This means the build phase never sees real secrets.
200
312
 
201
313
  ### Tags
202
- [Lerna](https://github.com/lerna/lerna) tags (like `@pkg/name@v1.0.0-beta.0`) are suitable for monorepos, but they dont follow [semver spec](https://semver.org/). Therefore, we propose another contract:
314
+ [Lerna](https://github.com/lerna/lerna) tags (like `@pkg/name@v1.0.0-beta.0`) are suitable for monorepos, but they don't follow [semver spec](https://semver.org/). Therefore, we propose another contract:
203
315
  ```js
204
316
  '2022.6.13-optional-org.pkg-name.v1.0.0-beta.1+sha.1-f0'
205
317
  // date name version format
package/package.json CHANGED
@@ -1,25 +1,27 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
3
  "alias": "bulk-release",
4
- "version": "2.21.0",
4
+ "version": "3.0.0",
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
- "./test-utils": "./src/test/js/test-utils.js",
9
+ "./test-utils": "./src/test/js/utils/repo.js",
10
10
  "./meta": "./src/main/js/processor/generators/meta.js"
11
11
  },
12
12
  "bin": "./src/main/js/cli.js",
13
13
  "files": [
14
14
  "src/main/js",
15
- "src/test/js/test-utils.js",
15
+ "src/test/js/utils",
16
16
  "CHANGELOG.md",
17
17
  "LICENSE",
18
18
  "README.md"
19
19
  ],
20
20
  "scripts": {
21
- "test": "c8 uvu ./src/test -i fixtures -i utils && c8 report -r lcov",
21
+ "test": "npm run test:unit",
22
+ "test:unit": "uvu ./src/test -i fixtures -i utils -i integration",
22
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",
23
25
  "docs": "mkdir -p docs && cp ./README.md ./docs/README.md",
24
26
  "publish:beta": "npm publish --tag beta --no-git-tag-version",
25
27
  "build": "esbuild src/main/js/index.js --platform=node --outdir=target --bundle --format=esm --external:typescript"
@@ -45,5 +47,10 @@
45
47
  "url": "git+https://github.com/semrel-extra/zx-bulk-release.git"
46
48
  },
47
49
  "author": "Anton Golub <antongolub@antongolub.com>",
48
- "license": "MIT"
50
+ "license": "MIT",
51
+ "c8": {
52
+ "exclude": [
53
+ "src/test/**"
54
+ ]
55
+ }
49
56
  }
@@ -1 +1 @@
1
- export {run} from './processor/release.js'
1
+ export {run} from './post/release.js'
@@ -40,15 +40,6 @@ export const ghCreateRelease = async ({ghApiUrl, ghToken, repoName, tag, body})
40
40
  return res
41
41
  }
42
42
 
43
- // https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#delete-a-release
44
- export const ghDeleteReleaseByTag = async ({ghApiUrl, ghToken, repoName, tag}) => {
45
- const res = await attempt2(() => ghFetch(`${ghApiUrl}/repos/${repoName}/releases/tags/${tag}`, {ghToken}))
46
- if (!res.ok) return false
47
- const {id} = await res.json()
48
- await attempt2(() => ghFetch(`${ghApiUrl}/repos/${repoName}/releases/${id}`, {ghToken, method: 'DELETE'}))
49
- return true
50
- }
51
-
52
43
  // https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset
53
44
  export const ghPrepareAssets = async (assets, _cwd) => {
54
45
  const temp = tempy.temporaryDirectory()
@@ -83,24 +74,6 @@ export const ghPrepareAssets = async (assets, _cwd) => {
83
74
  return temp
84
75
  }
85
76
 
86
- export const ghUploadAssets = async ({ghToken, ghAssets, uploadUrl, cwd}) => {
87
- const temp = await ghPrepareAssets(ghAssets, cwd)
88
-
89
- return Promise.all(ghAssets.map(async ({name}) => {
90
- const url = `${uploadUrl}?name=${name}`
91
- const res = await ghFetch(url, {
92
- ghToken,
93
- method: 'POST',
94
- headers: {'Content-Type': 'application/octet-stream'},
95
- body: await fs.readFile(path.join(temp, name)),
96
- })
97
- if (!res.ok) {
98
- throw new Error(`gh asset upload failed for '${name}': ${res.status}`)
99
- }
100
- return res
101
- }))
102
- }
103
-
104
77
  export const ghGetAsset = async ({repoName, tag, name, ghUrl}) => {
105
78
  const url = `${ghUrl || 'https://github.com'}/${repoName}/releases/download/${tag.ref || tag}/${name}`
106
79
  const res = await attempt2(() => fetch(url))
@@ -3,6 +3,7 @@ import {log} from '../log.js'
3
3
  import {attempt2, attempt3, memoizeBy} from '../../util.js'
4
4
 
5
5
  export const fetchRepo = memoizeBy(async ({cwd: _cwd, branch, origin: _origin, basicAuth}) => {
6
+ if (!_origin && !_cwd) throw new Error('fetchRepo requires either origin or cwd')
6
7
  const origin = _origin || (await getRepo(_cwd, {basicAuth})).repoAuthedUrl
7
8
  const cwd = tempy.temporaryDirectory()
8
9
  const _$ = $({cwd})
@@ -15,7 +16,7 @@ export const fetchRepo = memoizeBy(async ({cwd: _cwd, branch, origin: _origin, b
15
16
  }
16
17
 
17
18
  return cwd
18
- }, async ({cwd, branch}) => `${await getRoot(cwd)}:${branch}`)
19
+ }, async ({cwd, branch, origin}) => origin ? `${origin}:${branch}` : `${await getRoot(cwd)}:${branch}`)
19
20
 
20
21
  export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = [], basicAuth, gitCommitterEmail, gitCommitterName}) => {
21
22
  const _cwd = await fetchRepo({cwd, branch, origin, basicAuth})
@@ -106,15 +107,6 @@ export const pushTag = async ({cwd, tag, gitCommitterName, gitCommitterEmail}) =
106
107
  git push origin ${tag}`
107
108
  }
108
109
 
109
- export const fetchTags = async (cwd) =>
110
- $({cwd})`git fetch --tags`
111
-
112
- export const deleteRemoteTag = async ({cwd, tag}) => {
113
- log.info(`rolling back remote tag '${tag}'`)
114
- await $({cwd, nothrow: true})`git push origin :refs/tags/${tag}`
115
- await $({cwd, nothrow: true})`git tag -d ${tag}`
116
- }
117
-
118
110
  // Memoize prevents .git/config lock
119
111
  // https://github.com/qiwi/packasso/actions/runs/4539987310/jobs/8000403413#step:7:282
120
112
  export const setUserConfig = memoizeBy(async(cwd, gitCommitterName, gitCommitterEmail) => $({cwd})`
@@ -77,7 +77,7 @@ export const npmRestore = async (pkg) => {
77
77
  }
78
78
 
79
79
  export const npmPublish = async (pkg) => {
80
- const {absPath: cwd, name, version, manifest, config: {npmPublish, npmRegistry, npmToken, npmConfig, npmProvenance, npmOidc}} = pkg
80
+ const {name, version, manifest, config: {npmPublish, npmRegistry, npmToken, npmConfig, npmProvenance, npmOidc}} = pkg
81
81
 
82
82
  if (manifest.private || npmPublish === false) return
83
83
 
@@ -105,7 +105,9 @@ export const npmPublish = async (pkg) => {
105
105
  if (npmProvenance) npmFlags.push('--provenance')
106
106
  }
107
107
 
108
- await $({cwd})`npm publish ${npmFlags.filter(Boolean)}`
108
+ // Publish from pre-built tarball (courier mode) or project dir (legacy).
109
+ const target = pkg.npmTarball || pkg.absPath
110
+ await $`npm publish ${target} ${npmFlags.filter(Boolean)}`
109
111
  }
110
112
 
111
113
  export const getNpmrc = memoizeBy(async ({npmConfig, npmToken, npmRegistry}) => {
@@ -0,0 +1,29 @@
1
+ import {$} from 'zx-extra'
2
+ import {queuefy} from 'queuefy'
3
+ import {fetchRepo, pushCommit} from '../../api/git.js'
4
+ import {log} from '../../log.js'
5
+
6
+ const run = queuefy(async (manifest, dir) => {
7
+ const {releaseNotes, branch, file, msg, repoAuthedUrl, gitCommitterEmail, gitCommitterName, ghBasicAuth} = manifest
8
+
9
+ log.info('push changelog')
10
+
11
+ const _cwd = await fetchRepo({branch, origin: repoAuthedUrl, basicAuth: ghBasicAuth})
12
+
13
+ await $({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
14
+ await pushCommit({
15
+ branch,
16
+ msg,
17
+ origin: repoAuthedUrl,
18
+ gitCommitterEmail,
19
+ gitCommitterName,
20
+ basicAuth: ghBasicAuth,
21
+ })
22
+ })
23
+
24
+ export default {
25
+ name: 'changelog',
26
+ requires: ['repoAuthedUrl'],
27
+ when: (pkg) => !!pkg.config.changelog,
28
+ run,
29
+ }
@@ -3,4 +3,5 @@ export default {
3
3
  when: (pkg) => !!(pkg.ctx?.flags?.publishCmd ?? pkg.config.publishCmd),
4
4
  run: (pkg, exec) => exec(pkg, 'publishCmd'),
5
5
  snapshot: true,
6
+ transport: false,
6
7
  }
@@ -0,0 +1,30 @@
1
+ import {path} from 'zx-extra'
2
+ import {queuefy} from 'queuefy'
3
+ import {log} from '../../log.js'
4
+ import {pushCommit} from '../../api/git.js'
5
+
6
+ const run = queuefy(async (manifest, dir) => {
7
+ const {branch, to, msg, repoAuthedUrl, gitCommitterEmail, gitCommitterName, ghBasicAuth} = manifest
8
+ const docsDir = path.join(dir, 'docs')
9
+
10
+ log.info(`publish docs to ${branch}`)
11
+
12
+ await pushCommit({
13
+ cwd: docsDir,
14
+ from: '.',
15
+ to,
16
+ branch,
17
+ origin: repoAuthedUrl,
18
+ msg,
19
+ gitCommitterEmail,
20
+ gitCommitterName,
21
+ basicAuth: ghBasicAuth,
22
+ })
23
+ })
24
+
25
+ export default {
26
+ name: 'gh-pages',
27
+ requires: ['repoAuthedUrl'],
28
+ when: (pkg) => !!pkg.config.ghPages,
29
+ run,
30
+ }
@@ -0,0 +1,35 @@
1
+ import {fs, path} from 'zx-extra'
2
+ import {log} from '../../log.js'
3
+ import {ghCreateRelease, ghFetch} from '../../api/gh.js'
4
+
5
+ const run = async (manifest, dir) => {
6
+ const {tag, token, apiUrl, repoName, releaseNotes, assets} = manifest
7
+ if (!token) return null
8
+
9
+ log.info('create gh release')
10
+ const now = Date.now()
11
+
12
+ const res = await ghCreateRelease({ghApiUrl: apiUrl, ghToken: token, repoName, tag, body: releaseNotes})
13
+
14
+ if (assets?.length) {
15
+ const uploadUrl = res.upload_url.slice(0, res.upload_url.indexOf('{'))
16
+ const assetsDir = path.join(dir, 'assets')
17
+ if (await fs.pathExists(assetsDir)) {
18
+ await Promise.all(assets.map(async ({name}) => {
19
+ const url = `${uploadUrl}?name=${name}`
20
+ const body = await fs.readFile(path.join(assetsDir, name))
21
+ const r = await ghFetch(url, {ghToken: token, method: 'POST', headers: {'Content-Type': 'application/octet-stream'}, body})
22
+ if (!r.ok) throw new Error(`gh asset upload failed for '${name}': ${r.status}`)
23
+ }))
24
+ }
25
+ }
26
+
27
+ log.info(`duration gh release: ${Date.now() - now}`)
28
+ }
29
+
30
+ export default {
31
+ name: 'gh-release',
32
+ requires: ['token', 'repoName'],
33
+ when: (pkg) => !!pkg.config.ghToken,
34
+ run,
35
+ }
@@ -0,0 +1,34 @@
1
+ import {queuefy} from 'queuefy'
2
+ import {log} from '../../log.js'
3
+ import {pushCommit} from '../../api/git.js'
4
+ import {getArtifactPath, isAssetMode} from '../../depot/generators/meta.js'
5
+ import {prepareMeta} from '../../depot/generators/meta.js'
6
+
7
+ const pushMetaBranch = queuefy(async (manifest, dir) => {
8
+ const {name, version, tag, type, data: meta, repoAuthedUrl, gitCommitterEmail, gitCommitterName, ghBasicAuth} = manifest
9
+ if (type === null || isAssetMode(type)) return
10
+
11
+ log.info('push artifact to branch \'meta\'')
12
+
13
+ const msg = `chore: release meta ${name} ${version}`
14
+ const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
15
+
16
+ await pushCommit({
17
+ to: '.',
18
+ branch: 'meta',
19
+ msg,
20
+ files,
21
+ origin: repoAuthedUrl,
22
+ gitCommitterEmail,
23
+ gitCommitterName,
24
+ basicAuth: ghBasicAuth,
25
+ })
26
+ })
27
+
28
+ export default {
29
+ name: 'meta',
30
+ requires: ['repoAuthedUrl'],
31
+ when: (pkg) => pkg.config.meta.type !== null,
32
+ prepare: prepareMeta,
33
+ run: pushMetaBranch,
34
+ }
@@ -0,0 +1,26 @@
1
+ import {path} from 'zx-extra'
2
+ import {npmPublish} from '../../api/npm.js'
3
+
4
+ export const isNpmPublished = (pkg) =>
5
+ !pkg.manifest.private && pkg.config.npmPublish !== false
6
+
7
+ export default {
8
+ name: 'npm',
9
+ requires: ['token'],
10
+ when: isNpmPublished,
11
+ run: (manifest, dir) => npmPublish({
12
+ name: manifest.name,
13
+ version: manifest.version,
14
+ npmTarball: path.join(dir, 'package.tgz'),
15
+ preversion: manifest.preversion,
16
+ manifest: {},
17
+ config: {
18
+ npmRegistry: manifest.registry,
19
+ npmToken: manifest.token,
20
+ npmConfig: manifest.config,
21
+ npmProvenance: manifest.provenance,
22
+ npmOidc: manifest.oidc,
23
+ },
24
+ }),
25
+ snapshot: true,
26
+ }