@entro314labs/release-kit 2.8.0 → 2.9.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/README.md +256 -45
- package/TRAIN.md +13 -0
- package/package.json +1 -1
- package/release.mjs +994 -116
- package/train.mjs +18 -5
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://www.npmjs.com/package/@entro314labs/release-kit)
|
|
11
11
|
[](https://www.npmjs.com/package/@entro314labs/release-kit?activeTab=code)
|
|
12
12
|
[](#-requirements)
|
|
13
|
-
[](#-requirements)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
@@ -56,6 +56,7 @@ Released v2.5.0
|
|
|
56
56
|
| [⚡ Usage](#-usage) | targets, bumps, flags |
|
|
57
57
|
| [🧩 Steps](#-steps) | the seven steps and how to select them |
|
|
58
58
|
| [📚 Libraries versus apps](#-libraries-versus-apps) | which steps you want, and why |
|
|
59
|
+
| [🪝 Hooks](#-hooks) | commands between the steps |
|
|
59
60
|
| [🤖 Assistant](#-assistant-optional) | optional AI drafting |
|
|
60
61
|
| [🌍 Any language](#-any-language) | Rust, Python, tag-only, anything |
|
|
61
62
|
| [🚂 Release trains](#-release-trains) | monorepos and multi-repo workspaces |
|
|
@@ -126,7 +127,7 @@ Pin the URL to a tag, never `main`: piping an unpinned remote script into an int
|
|
|
126
127
|
means whatever is at that URL runs against your repository and your credentials. `--sync` is
|
|
127
128
|
the one thing that does not work this way — copying itself needs a file on disk.
|
|
128
129
|
|
|
129
|
-
> **All five paths run the same file and need Node
|
|
130
|
+
> **All five paths run the same file and need Node 22+.** That includes the Rust, Python and
|
|
130
131
|
> Go projects: `release-kit` is a Node program regardless of what it is releasing.
|
|
131
132
|
|
|
132
133
|
Zero-config works on the conventions below; add a [`release.config.json`](#️-configuration)
|
|
@@ -143,6 +144,16 @@ pnpm release -- --dry-run # print every step, execute nothing
|
|
|
143
144
|
pnpm release -- --help
|
|
144
145
|
```
|
|
145
146
|
|
|
147
|
+
`next` answers "what version would this release?" and stops. Only the version reaches
|
|
148
|
+
stdout, so it substitutes into a command; everything it would otherwise narrate goes to
|
|
149
|
+
stderr, where a human still reads it.
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
release-kit next # the version already in the manifest
|
|
153
|
+
release-kit next minor # what a minor bump would release
|
|
154
|
+
release-kit next auto # what the commits imply — the same inference the release uses
|
|
155
|
+
```
|
|
156
|
+
|
|
146
157
|
The target is optional. With no target it releases whatever version `package.json`
|
|
147
158
|
already says — which is the mode to use when a version bump landed in an earlier commit.
|
|
148
159
|
|
|
@@ -231,7 +242,7 @@ them execute, it never reorders them.
|
|
|
231
242
|
| `version` | on | Write the version into `package.json` and `versionFiles` |
|
|
232
243
|
| `changelog` | on | Roll `[Unreleased]` into the version, or add drafted notes |
|
|
233
244
|
| `tag` | on | Annotated git tag carrying the release notes |
|
|
234
|
-
| `push` | on | Push the branch and tag
|
|
245
|
+
| `push` | on | Push the branch and tag as one transaction (`--follow-tags --atomic`) |
|
|
235
246
|
| `publish` | on | Run the configured `publish` command |
|
|
236
247
|
| `release` | on | Create the GitHub release |
|
|
237
248
|
|
|
@@ -275,10 +286,24 @@ Notes resolve in this order:
|
|
|
275
286
|
bullet links to its commit, and `closes #12` / `fixes #34` in a message becomes a link to
|
|
276
287
|
the issue. A `BREAKING CHANGE:` footer is used in place of the subject, since it explains
|
|
277
288
|
the break. A commit reverted within the same release drops out along with its revert.
|
|
278
|
-
|
|
279
|
-
than
|
|
289
|
+
A **New Contributors** section names anyone whose first commit to the repository is in
|
|
290
|
+
this release — derived from the git history rather than a forge API, so it needs no
|
|
291
|
+
token and works offline, and it is skipped on a first release where everyone would be
|
|
292
|
+
new. All deterministic and needing nothing installed, so decent notes are the default
|
|
293
|
+
rather than something that requires an assistant.
|
|
280
294
|
4. Otherwise GitHub generates them from the commits since the previous tag.
|
|
281
295
|
|
|
296
|
+
**Which tag the history is read from** is the highest version tag carrying the configured
|
|
297
|
+
prefix that is reachable from `HEAD` — not the nearest tag. A repository carrying tags that
|
|
298
|
+
are not releases (a rolling `latest-beta` marker, a `nightly`) is unaffected by them, and a
|
|
299
|
+
patch tagged on top of a later minor does not drag the baseline backwards.
|
|
300
|
+
|
|
301
|
+
**A stable release absorbs the candidates that led to it.** Releasing `2.0.0` after
|
|
302
|
+
`2.0.0-rc.1` and `-rc.2` reads history from the last _stable_ tag, so the notes describe
|
|
303
|
+
everything the release ships rather than the gap between the last two candidates — which
|
|
304
|
+
is usually just the release commit. Releasing a candidate is unchanged: each one's notes
|
|
305
|
+
say what changed in that candidate.
|
|
306
|
+
|
|
282
307
|
`--notes <source>` forces one instead of walking that list: `changelog`, `assistant`,
|
|
283
308
|
`commits`, or `github`. A named source that produces nothing is an error rather than a
|
|
284
309
|
quiet fall-through — asking for one thing and being given another is worse than being told
|
|
@@ -290,6 +315,14 @@ does not make it preferred, because a hand-written changelog entry should still
|
|
|
290
315
|
The same text becomes the tag annotation, the GitHub release body, and (when rolled) the
|
|
291
316
|
changelog entry. It is written once and lands in three places.
|
|
292
317
|
|
|
318
|
+
**Keep a Changelog link definitions are maintained.** `## [1.2.3]` is a markdown link
|
|
319
|
+
_reference_, and renders as literal bracketed text without a matching definition at the
|
|
320
|
+
foot of the file. Every bracketed heading in the document gets one — not just the version
|
|
321
|
+
being released — so a changelog that never had them is repaired in one release: each
|
|
322
|
+
version compares against the version below it, the oldest links to its own tag, and
|
|
323
|
+
`[Unreleased]` compares the newest version against `HEAD`. Definitions for labels that are
|
|
324
|
+
not headings are left alone, since those are yours.
|
|
325
|
+
|
|
293
326
|
### npm dist-tags
|
|
294
327
|
|
|
295
328
|
The [dist-tag](https://docs.npmjs.com/cli/commands/npm-dist-tag) is derived from the
|
|
@@ -358,24 +391,26 @@ Only one step is Node-specific: `publish`. Committing, changelog rolling, taggin
|
|
|
358
391
|
and GitHub releases are the same everywhere, so `versionFile` points at wherever a project
|
|
359
392
|
keeps its version and the rest works unchanged.
|
|
360
393
|
|
|
361
|
-
| Project | Config
|
|
362
|
-
| ------------------------------ |
|
|
363
|
-
| Node (npm) | nothing — `package.json` and `npm publish` are the defaults
|
|
364
|
-
| Node (pnpm / bun) | `{"publish": "pnpm publish --tag %d"}` or `{"publish": "bun publish --tag %d"}`
|
|
365
|
-
| Rust | `
|
|
366
|
-
|
|
|
367
|
-
|
|
|
368
|
-
|
|
|
369
|
-
|
|
|
394
|
+
| Project | Config |
|
|
395
|
+
| ------------------------------ | -------------------------------------------------------------------------------------- |
|
|
396
|
+
| Node (npm) | nothing — `package.json` and `npm publish` are the defaults |
|
|
397
|
+
| Node (pnpm / bun) | `{"publish": "pnpm publish --tag %d"}` or `{"publish": "bun publish --tag %d"}` |
|
|
398
|
+
| Rust | nothing — `Cargo.toml` and `cargo publish` are detected |
|
|
399
|
+
| Rust + npm (a Tauri plugin) | nothing — both manifests bump, both registries publish |
|
|
400
|
+
| Python | `{"versionFile": "pyproject.toml", "publish": "uv publish"}` |
|
|
401
|
+
| Go | `{"publish": "go list -m %n@%t"}` — the tag is the release; a `version.go` is detected |
|
|
402
|
+
| Anything with a `VERSION` file | `{"versionFile": "VERSION", "publish": null}` |
|
|
403
|
+
| Versioned only by tag | `{"versionFile": null}`, then `release-kit 1.2.3` |
|
|
370
404
|
|
|
371
405
|
The publish step also gets a preflight when the command is one it recognises:
|
|
372
406
|
|
|
373
|
-
| Publish command | Authentication
|
|
374
|
-
| --------------- |
|
|
375
|
-
| `npm` / `pnpm` | `whoami`
|
|
376
|
-
| `bun` | `bun pm whoami`
|
|
377
|
-
| `uv` | `UV_PUBLISH_TOKEN` in the environment
|
|
378
|
-
| `
|
|
407
|
+
| Publish command | Authentication | Already published? |
|
|
408
|
+
| --------------- | ----------------------------------------------------------- | ----------------------------------- |
|
|
409
|
+
| `npm` / `pnpm` | `whoami` | `view <name>@<version>` |
|
|
410
|
+
| `bun` | `bun pm whoami` | `bun pm view <name>@<version>` |
|
|
411
|
+
| `uv` | `UV_PUBLISH_TOKEN` in the environment | none — `uv` skips duplicates itself |
|
|
412
|
+
| `cargo` | `CARGO_REGISTRY_TOKEN`, or `cargo login`'s credentials file | `cargo info <crate>@<version>` |
|
|
413
|
+
| `go` | none needed | `go list -m <module>@<tag>` |
|
|
379
414
|
|
|
380
415
|
Anything else runs as written with no preflight. The project name comes from the manifest —
|
|
381
416
|
`name` in `package.json`, `Cargo.toml` or `pyproject.toml`, `module` in `go.mod` — falling
|
|
@@ -384,7 +419,95 @@ back to the repository directory.
|
|
|
384
419
|
`publish` is detected too, but only where one ecosystem obviously owns it: `package.json`
|
|
385
420
|
gets `npm publish`, `Cargo.toml` gets `cargo publish`. Python has several publishers (uv,
|
|
386
421
|
twine, poetry, flit) and Go has none, so those get nothing rather than a guess — publishing
|
|
387
|
-
to the wrong registry is a far worse failure than being asked to configure it.
|
|
422
|
+
to the wrong registry is a far worse failure than being asked to configure it. A manifest
|
|
423
|
+
that forbids publishing is honoured: a `"private": true` package.json, a crate with
|
|
424
|
+
`publish = false`, and a crate built only as a `cdylib` — a native extension module rather
|
|
425
|
+
than a library — get no command at all.
|
|
426
|
+
|
|
427
|
+
### One repository, two registries
|
|
428
|
+
|
|
429
|
+
A Tauri plugin is a crate and its npm bindings built from one source tree; a maturin
|
|
430
|
+
project is a crate and a wheel. Both manifests sit at the repository root and carry the
|
|
431
|
+
same version, and both publish on the same release. That needs no config:
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
tauri-plugin-demo/
|
|
435
|
+
package.json @tauri-apps/plugin-demo 1.4.0
|
|
436
|
+
Cargo.toml tauri-plugin-demo 1.4.0
|
|
437
|
+
Cargo.lock
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
```sh
|
|
441
|
+
release-kit minor --yes
|
|
442
|
+
# also versioned in Cargo.toml, Cargo.lock (detected)
|
|
443
|
+
# publish: npm publish --tag latest
|
|
444
|
+
# publish: cargo publish
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
The second manifest is detected only when it **already carries the same version**. Two
|
|
448
|
+
manifests on different numbers are two independent release lines, and dragging one to the
|
|
449
|
+
other's number is a silent, wrong release — that case says so and leaves the file alone,
|
|
450
|
+
pointing at `versionFiles` for a project that really does want them joined. A project that
|
|
451
|
+
wrote its own `versionFile` or `versionFiles` is never extended behind its back.
|
|
452
|
+
|
|
453
|
+
`publish` therefore takes an array as well as a string, for any project releasing to more
|
|
454
|
+
than one registry:
|
|
455
|
+
|
|
456
|
+
```json
|
|
457
|
+
{ "publish": ["npm publish --tag %d", "cargo publish"] }
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
They run in order, each with its own authentication check and its own already-published
|
|
461
|
+
check, so a re-run after a half-finished publish completes the other half instead of
|
|
462
|
+
failing. The detected order puts npm before cargo deliberately: npm allows an unpublish for
|
|
463
|
+
72 hours and crates.io never does, so a publish that fails part-way through has not already
|
|
464
|
+
made the permanent half.
|
|
465
|
+
|
|
466
|
+
Names are resolved per registry rather than once: the crate is looked up under the `name`
|
|
467
|
+
in `Cargo.toml`, not the npm package name, since `@tauri-apps/plugin-demo` and
|
|
468
|
+
`tauri-plugin-demo` are the same release under two names.
|
|
469
|
+
|
|
470
|
+
### Languages that have no version of their own
|
|
471
|
+
|
|
472
|
+
A Go module has only `go.mod`, and `go.mod` carries no version — `go get` resolves a tag.
|
|
473
|
+
The same is true of a Swift package or a plain C library. There is nothing to bump, so the
|
|
474
|
+
tag is the version and `versionFile` resolves to `null` on its own.
|
|
475
|
+
|
|
476
|
+
Those projects still usually keep a number in source, so `mytool --version` prints
|
|
477
|
+
something:
|
|
478
|
+
|
|
479
|
+
```go
|
|
480
|
+
package main
|
|
481
|
+
|
|
482
|
+
const Version = "1.2.0"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
`version.go`, `internal/version/version.go` and `pkg/version/version.go` are detected and
|
|
486
|
+
rewritten on release, with no config — `const Version`, `var Version` and
|
|
487
|
+
`var Version string` are all read. The tag stays the source of truth; the constant is a
|
|
488
|
+
mirror kept in step with it.
|
|
489
|
+
|
|
490
|
+
Detection adopts a file only when it **already carries the current version**. That rules
|
|
491
|
+
out the placeholder a build replaces at link time:
|
|
492
|
+
|
|
493
|
+
```go
|
|
494
|
+
var Version = "dev" // -ldflags "-X main.Version=$(git describe)" — left alone
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
A mismatch here is skipped silently rather than warned about, because a placeholder is a
|
|
498
|
+
normal thing to find rather than a mistake — unlike two manifests on different versions.
|
|
499
|
+
|
|
500
|
+
For a constant somewhere else, or in a language whose convention is not in that list, name
|
|
501
|
+
it with a `pattern`:
|
|
502
|
+
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"versionFiles": [{ "path": "src/version.h", "pattern": "^#define VERSION \"(.+)\"" }]
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`versionFiles` is written whether or not the project has a `versionFile`, so this works for
|
|
510
|
+
a repository that versions by tag alone.
|
|
388
511
|
|
|
389
512
|
With no `versionFile` configured it is detected from the repository — `package.json`,
|
|
390
513
|
`pyproject.toml`, `Cargo.toml`, then `VERSION` — so most projects need no config for it at
|
|
@@ -398,8 +521,60 @@ because the TOML match is anchored to the start of a line, a dependency's
|
|
|
398
521
|
|
|
399
522
|
**Lockfiles are scoped automatically.** A `Cargo.lock` records a version for every
|
|
400
523
|
dependency — hundreds of them — so matching the first `version = "…"` would rewrite an
|
|
401
|
-
unrelated crate. Listing one rewrites only the `[[package]]`
|
|
402
|
-
crate in the sibling `Cargo.toml
|
|
524
|
+
unrelated crate. Listing one rewrites only the `[[package]]` blocks this project owns: the
|
|
525
|
+
crate named in the sibling `Cargo.toml`, or — for a workspace root, which has no
|
|
526
|
+
`[package]` of its own — every member that inherits the version with
|
|
527
|
+
`version.workspace = true`. A member pinned to its own number is versioned separately and
|
|
528
|
+
is left alone. With nothing beside it to read, it refuses rather than guesses.
|
|
529
|
+
|
|
530
|
+
`package-lock.json`, `npm-shrinkwrap.json` and `uv.lock` record the project's own version
|
|
531
|
+
too, and are refreshed by the tool that owns them rather than rewritten by pattern — the
|
|
532
|
+
version sits in more than one place and the formats change shape between tool versions.
|
|
533
|
+
Each is scoped to its manifest, so a `uv.lock` for a component this release is not
|
|
534
|
+
versioning stays out of the release commit, and a missing tool warns rather than aborting.
|
|
535
|
+
`pnpm-lock.yaml` records no root version, so it never goes stale.
|
|
536
|
+
|
|
537
|
+
### Marking the line instead of writing a pattern
|
|
538
|
+
|
|
539
|
+
The files that most want keeping in step — a README install line, a badge URL, a
|
|
540
|
+
Dockerfile tag — are the ones where a regex is fiddliest to get right. A comment on the
|
|
541
|
+
line says which of a file's numbers is the version, so nothing outside the file has to
|
|
542
|
+
describe where it sits:
|
|
543
|
+
|
|
544
|
+
````md
|
|
545
|
+
Install with `npm i acme@1.2.3` <!-- x-release-kit-version -->
|
|
546
|
+
|
|
547
|
+
```dockerfile
|
|
548
|
+
FROM acme:1.2 # x-release-kit-minor
|
|
549
|
+
```
|
|
550
|
+
````
|
|
551
|
+
|
|
552
|
+
`-major`, `-minor`, `-patch`, `-date` and `-version-date` write a piece of the release
|
|
553
|
+
instead of the whole version — `-version-date` covers both on one line, which is the shape
|
|
554
|
+
of an AppStream `<release version="…" date="…"/>` tag — and `x-release-kit-start-<scope>` … `x-release-kit-end` covers a run of lines
|
|
555
|
+
rather than commenting each one. Just list the file:
|
|
556
|
+
|
|
557
|
+
```json
|
|
558
|
+
{ "versionFiles": ["README.md", "Dockerfile"] }
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
A file listed with no marker, no `pattern` and no recognised extension is treated as
|
|
562
|
+
containing nothing but the version — right for a `VERSION` file, and refused for anything
|
|
563
|
+
else rather than replacing its contents with the number.
|
|
564
|
+
|
|
565
|
+
**Paths may contain `*`**, matching within one path segment, so the per-platform configs a
|
|
566
|
+
desktop app carries do not have to be written out one by one:
|
|
567
|
+
|
|
568
|
+
```json
|
|
569
|
+
{ "versionFiles": ["src-tauri/tauri.*.conf.json"] }
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
A pattern matching no files aborts: it was written to keep files in step, and silently
|
|
573
|
+
keeping none of them in step is the failure it was meant to prevent. A file the glob
|
|
574
|
+
matched that carries no version is skipped, though — a glob says "every file of this
|
|
575
|
+
shape", and some of them legitimately have none, as a Tauri per-OS overlay does. A file
|
|
576
|
+
you named on purpose is different: being unable to write it fails preflight, before
|
|
577
|
+
anything mutates.
|
|
403
578
|
|
|
404
579
|
For anything else, give a pattern with one capture group around the version. `versionFiles`
|
|
405
580
|
takes the same entries, so several files stay in sync across formats:
|
|
@@ -419,10 +594,7 @@ in one release, across three formats, with no scripting:
|
|
|
419
594
|
{
|
|
420
595
|
"versionFiles": [
|
|
421
596
|
"apps/desktop/package.json",
|
|
422
|
-
"apps/desktop/src-tauri/tauri
|
|
423
|
-
"apps/desktop/src-tauri/tauri.macos.conf.json",
|
|
424
|
-
"apps/desktop/src-tauri/tauri.windows.conf.json",
|
|
425
|
-
"apps/desktop/src-tauri/tauri.linux.conf.json",
|
|
597
|
+
"apps/desktop/src-tauri/tauri.*conf.json",
|
|
426
598
|
"apps/desktop/src-tauri/Cargo.toml",
|
|
427
599
|
"apps/desktop/src-tauri/Cargo.lock"
|
|
428
600
|
],
|
|
@@ -431,6 +603,10 @@ in one release, across three formats, with no scripting:
|
|
|
431
603
|
}
|
|
432
604
|
```
|
|
433
605
|
|
|
606
|
+
The glob covers `tauri.conf.json` and every per-OS overlay beside it, including ones added
|
|
607
|
+
later — and the overlays that carry no `version` of their own are skipped rather than
|
|
608
|
+
failing the release.
|
|
609
|
+
|
|
434
610
|
Stopping at `push` because the tag is what triggers the build pipeline — see
|
|
435
611
|
[Libraries versus apps](#-libraries-versus-apps).
|
|
436
612
|
|
|
@@ -459,22 +635,23 @@ execution is not wired up yet.
|
|
|
459
635
|
`release.config.json`, beside `package.json`. Every key is optional; unknown keys abort
|
|
460
636
|
rather than being silently ignored.
|
|
461
637
|
|
|
462
|
-
| Key | Default
|
|
463
|
-
| --------------- |
|
|
464
|
-
| `steps` | all but `commit`
|
|
465
|
-
| `tagPrefix` | `"v"`
|
|
466
|
-
| `branch` | `"main"`
|
|
467
|
-
| `remote` | `"origin"`
|
|
468
|
-
| `changelog` | `"CHANGELOG.md"`
|
|
469
|
-
| `versionFile` | detected
|
|
470
|
-
| `versionFiles` |
|
|
471
|
-
| `publish` |
|
|
472
|
-
| `versioning` | `"conventional"`
|
|
473
|
-
| `verify` | `null`
|
|
474
|
-
| `
|
|
475
|
-
| `
|
|
476
|
-
| `
|
|
477
|
-
| `
|
|
638
|
+
| Key | Default | Meaning |
|
|
639
|
+
| --------------- | ---------------------- | --------------------------------------------------------------------- |
|
|
640
|
+
| `steps` | all but `commit` | Which steps run; the order is fixed |
|
|
641
|
+
| `tagPrefix` | `"v"` | Prepended to the version to form the tag |
|
|
642
|
+
| `branch` | `"main"` | The only branch a release may run from; `null` allows any |
|
|
643
|
+
| `remote` | `"origin"` | Git remote to push to |
|
|
644
|
+
| `changelog` | `"CHANGELOG.md"` | Changelog path; `null` for a project without one |
|
|
645
|
+
| `versionFile` | detected | Where the version lives; `null` versions by tag alone |
|
|
646
|
+
| `versionFiles` | detected | Further files kept in sync; a path or `{ path, pattern }` |
|
|
647
|
+
| `publish` | detected | Publish command, or an array of them; `null` publishes nothing |
|
|
648
|
+
| `versioning` | `"conventional"` | How `auto` infers; or `always-patch` / `-minor` / `-major` |
|
|
649
|
+
| `verify` | `null` | Command run during preflight; non-zero aborts before anything mutates |
|
|
650
|
+
| `hooks` | `{}` | Commands run between the steps — see [Hooks](#-hooks) |
|
|
651
|
+
| `assistant` | `null` | Drafting CLI: a name, `"auto"`, or `{ tool, model, effort }` |
|
|
652
|
+
| `commitMessage` | `"chore(release): %t"` | Release commit subject |
|
|
653
|
+
| `releaseTitle` | `"%t"` | GitHub release title |
|
|
654
|
+
| `assets` | `[]` | Files attached to the GitHub release |
|
|
478
655
|
|
|
479
656
|
Command and message strings expand four tokens: `%v` version, `%t` tag, `%n` package
|
|
480
657
|
name, `%d` npm dist-tag. In the `publish` command line the substituted values are
|
|
@@ -599,6 +776,40 @@ A project releasing off a non-default branch with a different tag scheme:
|
|
|
599
776
|
}
|
|
600
777
|
```
|
|
601
778
|
|
|
779
|
+
## 🪝 Hooks
|
|
780
|
+
|
|
781
|
+
`verify` covers the gate that matters most — the project's own tests, run during preflight
|
|
782
|
+
before anything mutates. What it cannot express is work that has to happen _between_ the
|
|
783
|
+
release's own steps.
|
|
784
|
+
|
|
785
|
+
```json
|
|
786
|
+
{
|
|
787
|
+
"hooks": {
|
|
788
|
+
"afterVersion": "cargo build --release",
|
|
789
|
+
"beforePublish": "pnpm build",
|
|
790
|
+
"afterRelease": "curl -X POST -d 'shipped %t' https://hooks.example/deploy"
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
| Hook | Runs |
|
|
796
|
+
| --------------- | ------------------------------------------------------- |
|
|
797
|
+
| `beforeVersion` | After preflight, before any file is written |
|
|
798
|
+
| `afterVersion` | After the version is on disk, before the release commit |
|
|
799
|
+
| `beforePublish` | After the tag is pushed, before anything is published |
|
|
800
|
+
| `afterPublish` | After a publish actually ran |
|
|
801
|
+
| `afterRelease` | After the GitHub release is created |
|
|
802
|
+
|
|
803
|
+
Each takes a command line or an array of them, expanding the same `%v` `%t` `%n` `%d`
|
|
804
|
+
tokens `publish` does, shell-quoted on substitution. A non-zero exit aborts the release
|
|
805
|
+
where it happened. An unknown hook name aborts rather than silently never running.
|
|
806
|
+
|
|
807
|
+
`afterVersion` sits where it does for a reason: **anything it changes is staged for the
|
|
808
|
+
release commit**, so a file regenerated from the version rides in that commit instead of
|
|
809
|
+
being left behind in the working tree. `afterPublish` runs only when a publish actually
|
|
810
|
+
happened, so a re-run that skipped an already-published target does not tell something
|
|
811
|
+
downstream a lie it may act on.
|
|
812
|
+
|
|
602
813
|
## 🤖 Assistant (optional)
|
|
603
814
|
|
|
604
815
|
An assistant is an AI CLI already installed on your machine. When one is configured,
|
|
@@ -757,7 +968,7 @@ including a directory that is not a repository.
|
|
|
757
968
|
|
|
758
969
|
## 📋 Requirements
|
|
759
970
|
|
|
760
|
-
- **Node
|
|
971
|
+
- **Node 22+ — including for Rust, Python and Go projects.** `release-kit` is a Node
|
|
761
972
|
program whatever it releases; there is no standalone binary.
|
|
762
973
|
- `git`
|
|
763
974
|
- `gh`, authenticated — only when creating GitHub releases
|
|
@@ -773,7 +984,7 @@ with the reasoning — are in [ROADMAP.md](ROADMAP.md).
|
|
|
773
984
|
|
|
774
985
|
```sh
|
|
775
986
|
pnpm install
|
|
776
|
-
pnpm test #
|
|
987
|
+
pnpm test # 188 tests, node --test, no framework
|
|
777
988
|
pnpm check # format + lint + tests, the same gate CI runs
|
|
778
989
|
```
|
|
779
990
|
|
package/TRAIN.md
CHANGED
|
@@ -269,6 +269,19 @@ Seeding refuses, per member and without touching it, when:
|
|
|
269
269
|
The orchestrator's graph is built from npm-style manifests today. Non-npm members still
|
|
270
270
|
participate:
|
|
271
271
|
|
|
272
|
+
- **Rust**: `Cargo.toml` versions and `cargo publish`, both already handled per package.
|
|
273
|
+
What the orchestrator would additionally need is the range rewrite: a crate depending on
|
|
274
|
+
a sibling by `path` also carries a `version` for it, and crates.io rejects a publish
|
|
275
|
+
whose path dependency has no version — so the dependent's manifest must move to the
|
|
276
|
+
dependency's new number before it publishes. release-please's `cargo-toml.ts` is the
|
|
277
|
+
worked reference: it rewrites `version` under `dependencies`, `dev-dependencies`,
|
|
278
|
+
`build-dependencies` and every `target.<cfg>` table, skipping entries with no `path`
|
|
279
|
+
(a real crates.io dependency, not a sibling) and no `version` (a path-only dependency,
|
|
280
|
+
which needs nothing). Workspace inheritance moves the problem rather than removing it:
|
|
281
|
+
`version.workspace = true` in a member points at `[workspace.package]`, which is one
|
|
282
|
+
place to rewrite instead of many. Not built in `release.mjs`, where a single package has
|
|
283
|
+
no internal ranges to rewrite and the code would have no caller.
|
|
284
|
+
|
|
272
285
|
- **Go**: no manifest version; the tag is the release. release-kit already handles it
|
|
273
286
|
(`versionFile: null`). It has no npm-visible dependents, so no registry wait.
|
|
274
287
|
- **Python / PHP**: `pyproject.toml` / `composer.json` versions, publish via the package's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entro314labs/release-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Single-file, zero-dependency release mechanism for JS/TS/Node projects: version bump, changelog roll, commit, annotated tag, push, publish, GitHub release",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|