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.
- package/CHANGELOG.md +22 -0
- package/README.md +140 -28
- package/package.json +12 -5
- package/src/main/js/index.js +1 -1
- package/src/main/js/{processor → post}/api/gh.js +0 -27
- package/src/main/js/{processor → post}/api/git.js +2 -10
- package/src/main/js/{processor → post}/api/npm.js +4 -2
- package/src/main/js/post/courier/channels/changelog.js +29 -0
- package/src/main/js/{processor/publishers → post/courier/channels}/cmd.js +1 -0
- package/src/main/js/post/courier/channels/gh-pages.js +30 -0
- package/src/main/js/post/courier/channels/gh-release.js +35 -0
- package/src/main/js/post/courier/channels/meta.js +34 -0
- package/src/main/js/post/courier/channels/npm.js +26 -0
- package/src/main/js/post/courier/index.js +113 -0
- package/src/main/js/post/courier/parcel.js +77 -0
- package/src/main/js/{processor → post/depot}/exec.js +2 -2
- package/src/main/js/{processor → post/depot}/generators/meta.js +3 -3
- package/src/main/js/{processor → post/depot}/generators/notes.js +1 -1
- package/src/main/js/{processor → post/depot}/steps/analyze.js +2 -2
- package/src/main/js/{processor → post/depot}/steps/build.js +2 -2
- package/src/main/js/{processor → post/depot}/steps/clean.js +2 -2
- package/src/main/js/{processor → post/depot}/steps/contextify.js +3 -3
- package/src/main/js/post/depot/steps/pack.js +59 -0
- package/src/main/js/post/depot/steps/publish.js +29 -0
- package/src/main/js/{processor → post/depot}/steps/test.js +1 -1
- package/src/main/js/{processor → post}/release.js +44 -37
- package/src/main/js/post/tar.js +73 -0
- package/src/test/js/utils/gh-server.js +33 -0
- package/src/test/js/utils/mock.js +132 -0
- package/src/test/js/{test-utils.js → utils/repo.js} +3 -3
- package/src/main/js/processor/publishers/changelog.js +0 -26
- package/src/main/js/processor/publishers/gh-pages.js +0 -32
- package/src/main/js/processor/publishers/gh-release.js +0 -41
- package/src/main/js/processor/publishers/meta.js +0 -58
- package/src/main/js/processor/publishers/npm.js +0 -15
- package/src/main/js/processor/steps/publish.js +0 -39
- package/src/main/js/processor/steps/teardown.js +0 -58
- /package/src/main/js/{processor → post/depot}/deps.js +0 -0
- /package/src/main/js/{processor → post/depot}/generators/tag.js +0 -0
- /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
|
-
[](https://github.com/semrel-extra/zx-bulk-release/actions/workflows/ci.yml)
|
|
5
|
+
[](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release)
|
|
6
|
+
[](https://qlty.sh/gh/semrel-extra/projects/zx-bulk-release)
|
|
7
7
|
[](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
|
-
* [
|
|
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
|
|
42
|
-
|
|
43
|
-
| `--
|
|
44
|
-
| `--
|
|
45
|
-
| `--
|
|
46
|
-
| `--
|
|
47
|
-
| `--
|
|
48
|
-
| `--no-
|
|
49
|
-
| `--
|
|
50
|
-
| `--
|
|
51
|
-
| `--
|
|
52
|
-
| `--
|
|
53
|
-
| `--
|
|
54
|
-
| `--
|
|
55
|
-
| `--
|
|
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
|
-
- **`
|
|
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
|
-
###
|
|
191
|
-
|
|
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
|
-
|
|
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 don
|
|
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": "
|
|
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/
|
|
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/
|
|
15
|
+
"src/test/js/utils",
|
|
16
16
|
"CHANGELOG.md",
|
|
17
17
|
"LICENSE",
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "
|
|
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
|
}
|
package/src/main/js/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {run} from './
|
|
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 {
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|