@entro314labs/release-kit 1.0.1 → 1.0.3

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 (3) hide show
  1. package/README.md +77 -19
  2. package/package.json +1 -1
  3. package/release.mjs +15 -0
package/README.md CHANGED
@@ -1,14 +1,67 @@
1
- # @entro314labs/release-kit
1
+ <div align="center">
2
2
 
3
- A single-file release mechanism for any JS/TS/Node project: version bump → changelog roll
4
- → commit → annotated tag → push → registry publish → GitHub release.
3
+ # 🚀 release-kit
4
+
5
+ **Single-file, zero-dependency release automation for JS/TS/Node projects.**
6
+
7
+ `version bump` → `changelog` → `commit` → `annotated tag` → `push` → `publish` → `GitHub release`
8
+
9
+ [![npm](https://img.shields.io/npm/v/@entro314labs/release-kit?logo=npm&color=cb3837)](https://www.npmjs.com/package/@entro314labs/release-kit)
10
+ [![downloads](https://img.shields.io/npm/dm/@entro314labs/release-kit?color=cb3837)](https://www.npmjs.com/package/@entro314labs/release-kit)
11
+ [![unpacked size](https://img.shields.io/npm/unpacked-size/@entro314labs/release-kit?color=blueviolet)](https://www.npmjs.com/package/@entro314labs/release-kit?activeTab=code)
12
+ [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](#-requirements)
13
+ [![node](https://img.shields.io/badge/node-%E2%89%A5%2018-339933?logo=node.js&logoColor=white)](#-requirements)
14
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
15
+
16
+ </div>
5
17
 
6
18
  `release.mjs` imports nothing but `node:*`. No dependencies, no build step, no config
7
19
  required — the file _is_ the tool. That is why it can be installed as a package, run
8
20
  straight from the registry, or vendored into a project as a plain file, with no difference
9
21
  in behaviour between them.
10
22
 
11
- ## Install
23
+ ```console
24
+ $ pnpm release minor
25
+
26
+ acme-toolkit release
27
+ 2.4.0 → 2.5.0 tag v2.5.0 dist-tag latest
28
+
29
+ [1] Preflight
30
+ ok version 2.4.0 → 2.5.0
31
+ ok working tree clean
32
+ ok on main
33
+ ok remote origin
34
+ ok up to date with origin/main
35
+ ok tag v2.5.0 is free
36
+ ok gh authenticated (octocat)
37
+ ok npm authenticated (octocat)
38
+ ok CHANGELOG.md: [Unreleased] will become [2.5.0]
39
+
40
+ [2] Write version 2.5.0
41
+ [3] Roll CHANGELOG.md to 2.5.0
42
+ [4] Commit
43
+ [5] Annotated tag v2.5.0
44
+ [6] Push branch and tag to origin
45
+ [7] Publish to the registry (dist-tag latest)
46
+ [8] GitHub release v2.5.0
47
+
48
+ Released v2.5.0
49
+ ```
50
+
51
+ ## Contents
52
+
53
+ | | |
54
+ | ----------------------------------------------------------------------- | --------------------------------------- |
55
+ | [📦 Install](#-install) | package, `npx`, or vendored file |
56
+ | [⚡ Usage](#-usage) | targets, bumps, flags |
57
+ | [🔁 What a release does](#-what-a-release-does) | the seven steps, notes, dist-tags |
58
+ | [✅ Preflight](#-preflight) | what is checked before anything mutates |
59
+ | [♻️ Recovering from a failed run](#️-recovering-from-a-failed-run) | why re-running is safe |
60
+ | [⚙️ Configuration](#️-configuration) | `release.config.json`, publishing, auth |
61
+ | [🔄 Keeping vendored copies in sync](#-keeping-vendored-copies-in-sync) | `--sync` |
62
+ | [📋 Requirements](#-requirements) | Node, `git`, `gh` |
63
+
64
+ ## 📦 Install
12
65
 
13
66
  **As a devDependency** — the normal choice. Updates arrive through your package manager.
14
67
 
@@ -48,9 +101,9 @@ npx @entro314labs/release-kit --sync .
48
101
  ```
49
102
 
50
103
  All three run the same file. Zero-config works on the conventions below; add a
51
- [`release.config.json`](#configuration) only for what differs.
104
+ [`release.config.json`](#️-configuration) only for what differs.
52
105
 
53
- ## Usage
106
+ ## ⚡ Usage
54
107
 
55
108
  ```sh
56
109
  pnpm release # release the version already in package.json
@@ -76,7 +129,8 @@ already says — which is the mode to use when a version bump landed in an earli
76
129
 
77
130
  A `major`/`minor`/`patch` bump off a prerelease releases that prerelease's base version
78
131
  when the base already satisfies the bump, so promoting a release candidate is a plain
79
- `patch`. The arithmetic matches `semver.inc` exactly.
132
+ `patch`. The arithmetic matches [`semver.inc`](https://github.com/npm/node-semver#functions)
133
+ exactly, and precedence follows the [SemVer spec](https://semver.org/#spec-item-11).
80
134
 
81
135
  Prerelease bumps need `--preid` unless the current version already carries one to infer.
82
136
 
@@ -93,7 +147,7 @@ Prerelease bumps need `--preid` unless the current version already carries one t
93
147
  | `--sync <dir>...` | Copy this script into other projects and exit. Touches no git state. |
94
148
  | `--help`, `-h` | Full flag list. |
95
149
 
96
- ## What a release does
150
+ ## 🔁 What a release does
97
151
 
98
152
  Steps that do not apply are skipped silently — a project with no changelog, or with
99
153
  `publish` disabled, simply has fewer steps.
@@ -102,7 +156,8 @@ Steps that do not apply are skipped silently — a project with no changelog, or
102
156
  is replaced in place, so key order, indentation, and trailing newline all survive. A
103
157
  `package-lock.json` is resynced, because it embeds the root version twice.
104
158
  2. **Roll the changelog**: `## [Unreleased]` becomes `## [x.y.z] - YYYY-MM-DD`, with a
105
- fresh empty `## [Unreleased]` reopened above it for the next cycle.
159
+ fresh empty `## [Unreleased]` reopened above it for the next cycle — the
160
+ [Keep a Changelog](https://keepachangelog.com/) convention.
106
161
  3. **Commit** the files that actually changed.
107
162
  4. **Tag**, annotated, with the release notes as the annotation — so a CI workflow can
108
163
  read the notes straight off the tag instead of re-deriving them.
@@ -127,7 +182,8 @@ changelog entry. It is written once and lands in three places.
127
182
 
128
183
  ### npm dist-tags
129
184
 
130
- The dist-tag is derived from the version, never guessed:
185
+ The [dist-tag](https://docs.npmjs.com/cli/commands/npm-dist-tag) is derived from the
186
+ version, never guessed:
131
187
 
132
188
  | Version | dist-tag |
133
189
  | ---------------------- | ------------------------------------------------------------- |
@@ -140,7 +196,7 @@ The refusal is deliberate: an unrecognised prerelease identifier has no safe cha
140
196
  falling through to `latest` would put a prerelease on the stable line where every
141
197
  `npm install` picks it up. Pass `--tag <dist-tag>` to choose a channel explicitly.
142
198
 
143
- ## Preflight
199
+ ## ✅ Preflight
144
200
 
145
201
  Every check runs and every failure is reported before it aborts once with the whole list,
146
202
  rather than stopping at the first problem.
@@ -159,7 +215,7 @@ rather than stopping at the first problem.
159
215
  Under `--dry-run` the failures are reported and then the remaining steps are shown anyway,
160
216
  so you can see the whole plan without fixing the blockers first.
161
217
 
162
- ## Recovering from a failed run
218
+ ## ♻️ Recovering from a failed run
163
219
 
164
220
  Re-run the same command. Every step is idempotent:
165
221
 
@@ -177,7 +233,7 @@ it stopped. There is no cleanup step, no `--resume`, and nothing to remember.
177
233
  The one case that is not recoverable by re-running is a tag that exists at a _different_
178
234
  commit than `HEAD`. That is a genuine conflict, and it aborts rather than guessing.
179
235
 
180
- ## Configuration
236
+ ## ⚙️ Configuration
181
237
 
182
238
  `release.config.json`, beside `package.json`. Every key is optional; unknown keys abort
183
239
  rather than being silently ignored.
@@ -217,9 +273,11 @@ to introspect.
217
273
  Two npm behaviours are handled automatically:
218
274
 
219
275
  - **`npm login` issues a two-hour session**, not a durable token. Classic tokens were
220
- permanently revoked in December 2025. A login from earlier in the day has expired, and
276
+ [permanently revoked in December 2025](https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/).
277
+ A login from earlier in the day has expired, and
221
278
  the preflight failure says so rather than implying you never logged in.
222
- - **Trusted publishing (OIDC) carries no token at all.** In GitHub Actions with
279
+ - **[Trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) carries no token
280
+ at all.** In GitHub Actions with
223
281
  `id-token: write`, or GitLab CI/CircleCI with `NPM_ID_TOKEN`, `whoami` fails while
224
282
  `publish` succeeds. That environment is detected and the auth check is skipped, so a
225
283
  valid CI release is not aborted over a missing token it does not need.
@@ -255,7 +313,7 @@ A project releasing off a non-default branch with a different tag scheme:
255
313
  }
256
314
  ```
257
315
 
258
- ## Keeping vendored copies in sync
316
+ ## 🔄 Keeping vendored copies in sync
259
317
 
260
318
  Installed as a dependency, updates come from your package manager and there is nothing to
261
319
  sync. For projects using the vendored file, `--sync` pushes the current version out — to
@@ -270,7 +328,7 @@ if missing, skips directories with no `package.json`, and warns when a target la
270
328
  `release` npm script. It runs before any git resolution, so it works from anywhere,
271
329
  including a directory that is not a repository.
272
330
 
273
- ## Requirements
331
+ ## 📋 Requirements
274
332
 
275
333
  - Node 18+ (uses `node:readline/promises` and `Array.prototype.at`)
276
334
  - `git`
@@ -278,11 +336,11 @@ including a directory that is not a repository.
278
336
  - Whatever the `publish` command needs — for the default, a live `npm login` session
279
337
  (two hours) or an OIDC trusted-publishing environment
280
338
 
281
- ## Contributing
339
+ ## 🤝 Contributing
282
340
 
283
341
  The tool releases itself, so a change ships the same way it would in any consuming project:
284
342
  add a `## [Unreleased]` entry to `CHANGELOG.md`, then run `pnpm release <bump>` from a clone.
285
343
 
286
- ## License
344
+ ## 📄 License
287
345
 
288
346
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entro314labs/release-kit",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",
package/release.mjs CHANGED
@@ -480,6 +480,21 @@ const target = argv.find((a) => !a.startsWith('-') && a !== explicitDistTag && a
480
480
 
481
481
  const root = tryRead('git', ['rev-parse', '--show-toplevel'])
482
482
  if (!root) abort('not inside a git repository')
483
+
484
+ // A release is scoped to the repository: the version, the tag and the push all belong to
485
+ // one git history, so the package released is the one at the git root. Refuse when invoked
486
+ // from a nested package instead — silently releasing the parent is the worse outcome.
487
+ const localManifest = resolve('package.json')
488
+ const rootManifest = join(root, 'package.json')
489
+ if (existsSync(localManifest) && localManifest !== rootManifest) {
490
+ abort(
491
+ `${relative(root, localManifest)} is a nested package, but a release covers the whole ` +
492
+ `repository.\n\n Running here would release ${
493
+ existsSync(rootManifest) ? readJson(rootManifest).name : 'the repository root'
494
+ } instead.\n` +
495
+ ' release-kit handles one package per repository; it does not release workspace members.',
496
+ )
497
+ }
483
498
  process.chdir(root)
484
499
 
485
500
  if (!existsSync('package.json')) abort(`no package.json at ${root}`)