keepchanges 1.0.2 → 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.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # keepchanges
2
2
 
3
- Generate and maintain `CHANGELOG.md` from Conventional Commits.
3
+ A release CLI for Conventional Commits. Generate and maintain a changelog, optionally bump
4
+ the project version, create a release commit and Git tag, publish a GitHub or Gitea Release,
5
+ and upload GitHub Release assets.
4
6
 
5
7
  Inspired by [changelogithub](https://github.com/antfu-collective/changelogithub).
6
8
 
@@ -8,17 +10,20 @@ Inspired by [changelogithub](https://github.com/antfu-collective/changelogithub)
8
10
 
9
11
  ## Features
10
12
 
11
- - Generates release notes from Conventional Commits between the latest Git tag and `HEAD`
12
- - Reads the complete Git history for a first release
13
- - Inserts a new release before existing content in `CHANGELOG.md`
14
- - Updates `package.json#version` for npm projects
15
- - Adds repository commit links, comparison links, authors, and co-authors
16
- - Can commit, tag, push, and publish a GitHub or Gitea Release
17
- - Provides a manual release URL when no GitHub/Gitea token is available
13
+ - Generates release notes from Conventional Commits between Git tags
14
+ - Inserts new releases into an existing `CHANGELOG.md` and updates `package.json#version`
15
+ for npm projects
16
+ - Disables version bumps and changelog writes independently with `--no-bump` and
17
+ `--no-changelog`
18
+ - Creates release commits and annotated tags, then pushes them to the remote repository
19
+ - Supports building artifacts from a fixed release commit before publishing
20
+ - Creates or updates GitHub and Gitea Releases
21
+ - Uploads files, directories, or glob matches as GitHub Release assets
22
+ - Adds repository links for commits, pull requests, comparisons, authors, and co-authors
18
23
 
19
24
  The current release includes `feat`, `fix`, `perf`, breaking changes marked with `!`,
20
- and commits with a `BREAKING CHANGE` or `BREAKING-CHANGE` trailer. Other commit
21
- types are ignored.
25
+ and commits with a `BREAKING CHANGE` or `BREAKING-CHANGE` trailer. Other commit types
26
+ are ignored.
22
27
 
23
28
  ## Quick start
24
29
 
@@ -36,50 +41,90 @@ The version may also include a leading `v`:
36
41
  npx keepchanges v1.1.0
37
42
  ```
38
43
 
39
- By default, the command writes `CHANGELOG.md`. For an npm package, it also
40
- updates `package.json#version` to `1.1.0`, but it does not create a Git commit.
44
+ By default, the command writes `CHANGELOG.md`. For an npm package, it also updates
45
+ `package.json#version` to `1.1.0`, but it does not create a Git commit.
41
46
 
42
- ## Command
47
+ ## Release workflows
43
48
 
44
- ```text
45
- npx keepchanges <version> [options]
49
+ Combine the release stages to match how your project manages versions and builds artifacts:
50
+
51
+ | Workflow | Bump version | Write changelog | Build timing | Release sequence |
52
+ | --- | --- | --- | --- | --- |
53
+ | Full version release | Yes | Yes | After creating the release commit | `--tag`, build, then `--release --asset` |
54
+ | Externally managed version | No | Yes | Before release | Build, then `--release --no-bump --asset` |
55
+ | Artifacts only | No | No | Before release | Build, then `--release --no-bump --no-changelog --asset` |
56
+
57
+ ### Full version release
58
+
59
+ Let keepchanges update the version and changelog, then create the release commit and tag.
60
+ Build from that exact commit before publishing the same tag and uploading its artifacts:
61
+
62
+ ```bash
63
+ npx keepchanges 1.1.0 --tag
64
+ pnpm build
65
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release --asset dist
46
66
  ```
47
67
 
48
- ### Arguments and options
68
+ The second command reuses the existing tag. It does not move the tag or commit the artifacts.
49
69
 
50
- | Argument or option | Default | Description |
51
- | --- | --- | --- |
52
- | `<version>` | Required | Version to generate. Accepts `1.1.0` or `v1.1.0`. A version containing `-`, such as `1.1.0-beta.1`, is treated as a prerelease. |
53
- | `--from <ref>` | Inferred from target | Overrides the exclusive starting Git ref used to read commits. Without it, the latest matching tag is used for `HEAD`, or the previous matching tag relative to an explicit `--to`. If no previous tag exists, history starts at the first commit. A version such as `1.0.0` uses the configured tag prefix. |
54
- | `--to <ref>` | `HEAD` | Sets the ending Git ref. A version such as `1.1.0` uses the configured tag prefix. It cannot be combined with `--release`, and must resolve to the current `HEAD` when used with `--commit`. |
55
- | `--repository <source>` | Auto-detected | Sets a GitHub `owner/repo` slug or a complete GitHub/Gitea URL. It takes precedence over `package.json` and `origin`. |
56
- | `--output <path>` | `CHANGELOG.md` | Sets the changelog file path. Relative paths are resolved from the current working directory. |
57
- | `--dry` | `false` | Prints the current release preview without writing files or performing commit, tag, push, or release API mutations. |
58
- | `--commit` | `false` | Creates a Git commit after writing. It commits only the changelog and detected version file, using `chore(release): v<version>` by default. |
59
- | `--release` | `false` | Runs the complete release flow: writes files, creates or reuses a release commit, creates an annotated tag, pushes `HEAD` and the tag, then creates or updates the repository Release. This implies `--commit`. |
60
- | `--author <author>` | Release bot | Sets the generated release commit author in `"Name <email>"` format; requires `--commit` or `--release`. |
61
- | `-t, --token <token>` | Environment | Resolves authors and publishes Releases. GitHub precedence is `--token`, `GITHUB_TOKEN`, then `GH_TOKEN`; Gitea uses `GITEA_TOKEN`. |
62
- | `--tag-prefix <prefix>` | `v` | Sets the prefix used to find and create version tags, such as `package@`. |
63
- | `--no-tag-prefix` | `false` | Finds and creates version tags without a prefix. |
64
- | `--name <name>` | Version tag | Sets the remote Release name; only valid with `--release`. |
65
- | `-d, --draft` | `false` | Creates a draft Release; only valid with `--release`. |
66
- | `--prerelease` | Inferred | Explicitly marks a prerelease. By default it is inferred from `-` in the version; only valid with `--release`. |
67
- | `--emoji` | `true` | Controls section title emojis. As with changelogithub, CAC's `--no-emoji` form disables them. |
68
- | `--capitalize` | `true` | Controls entry capitalization; `--no-capitalize` disables it. |
69
- | `--group` | `true` | Groups repeated scopes; `--no-group` disables it. |
70
+ ### Changelog without a version bump
70
71
 
71
- ## Examples
72
+ If another tool or workflow manages the project version, build first, then let keepchanges
73
+ write the changelog, create the commit and tag, and publish the assets:
72
74
 
73
- Write to a different file:
75
+ ```bash
76
+ pnpm build
77
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release \
78
+ --no-bump \
79
+ --asset dist
80
+ ```
81
+
82
+ ### Artifacts only
83
+
84
+ If the release needs neither a version update nor a changelog write, build first, then create
85
+ the tag and Release from the current `HEAD`:
86
+
87
+ ```bash
88
+ pnpm build
89
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release \
90
+ --no-bump \
91
+ --no-changelog \
92
+ --asset dist
93
+ ```
94
+
95
+ ## GitHub Release assets
96
+
97
+ `--asset` accepts a file, directory, or glob. Directories are traversed recursively, and the
98
+ option is repeatable, so one release can include multiple folders or match sets:
99
+
100
+ ```bash
101
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release \
102
+ --asset 'packages/*/dist' \
103
+ --asset artifacts \
104
+ --asset checksums.txt
105
+ ```
106
+
107
+ Quote globs so keepchanges, rather than the shell, expands them consistently. Before publishing,
108
+ keepchanges verifies that every path exists, every glob matches at least one file, and all final
109
+ file names are unique. Updating an existing Release replaces assets with the same name.
110
+
111
+ Asset uploads currently support GitHub only and require a GitHub token. Token precedence is
112
+ `--token`, `GITHUB_TOKEN`, then `GH_TOKEN`. Without `--asset`, a regular `--release` can run
113
+ without a token: it commits, tags, and pushes before printing a manual Release URL.
114
+
115
+ ## Common tasks
116
+
117
+ Write to a different changelog file:
74
118
 
75
119
  ```bash
76
120
  npx keepchanges 1.1.0 --output docs/CHANGELOG.md
77
121
  ```
78
122
 
79
- Preview the current release without changing files:
123
+ Preview without changing files or the remote repository:
80
124
 
81
125
  ```bash
82
126
  npx keepchanges 1.1.0 --dry
127
+ npx keepchanges 1.1.0 --release --dry
83
128
  ```
84
129
 
85
130
  Write the changelog, update the version, and create a commit:
@@ -95,42 +140,71 @@ npx keepchanges 1.1.0 --commit \
95
140
  --author "Release Author <release@example.com>"
96
141
  ```
97
142
 
98
- Create a GitHub Release:
143
+ Create a GitHub Release without assets:
99
144
 
100
145
  ```bash
101
146
  GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release
102
147
  ```
103
148
 
104
- Use version tags without a prefix:
149
+ Use version tags without a prefix or with a package-specific prefix:
105
150
 
106
151
  ```bash
107
152
  npx keepchanges 1.1.0 --no-tag-prefix
108
- ```
109
-
110
- Use a package-specific tag prefix:
111
-
112
- ```bash
113
153
  npx keepchanges 1.1.0 --tag-prefix 'package@'
114
154
  ```
115
155
 
116
- Rebuild a historical release without specifying its previous tag:
156
+ Regenerate a historical release:
117
157
 
118
158
  ```bash
119
159
  npx keepchanges 1.0.0 --to 1.0.0 --dry
120
160
  ```
121
161
 
122
- Preview a Release without writing, committing, tagging, pushing, or calling the
123
- release API:
162
+ ## Command reference
124
163
 
125
- ```bash
126
- npx keepchanges 1.1.0 --release --dry
164
+ ```text
165
+ npx keepchanges <version> [options]
127
166
  ```
128
167
 
129
- ## Generation and release behavior
168
+ | Argument or option | Default | Description |
169
+ | --- | --- | --- |
170
+ | `<version>` | Required | Version to generate, such as `1.1.0`, `v1.1.0`, or `1.1.0-beta.1`. |
171
+ | `--from <ref>` | Inferred from target | Sets the exclusive starting Git ref used to read commits. |
172
+ | `--to <ref>` | `HEAD` | Sets the ending Git ref. Cannot be combined with `--tag` or `--release`. |
173
+ | `--repository <source>` | Auto-detected | Sets a GitHub `owner/repo` slug or complete GitHub/Gitea URL. |
174
+ | `--output <path>` | `CHANGELOG.md` | Sets the changelog file path. |
175
+ | `--dry` | `false` | Prints a preview without writing files or performing commit, tag, push, or release API mutations. |
176
+ | `--commit` | `false` | Creates a Git commit after writing. |
177
+ | `--tag` | `false` | Writes, commits, creates an annotated tag, and pushes without creating a repository Release. |
178
+ | `--release` | `false` | Commits, tags, pushes, and creates or updates a repository Release; implies `--commit`. |
179
+ | `--asset <path>` | None | Uploads GitHub Release assets from files, directories, or globs. Repeatable; requires `--release` and a GitHub token. |
180
+ | `--bump` / `--no-bump` | `true` | Controls whether the detected project version file is updated. |
181
+ | `--changelog` / `--no-changelog` | `true` | Controls whether the changelog is written. Release notes are generated either way. |
182
+ | `--author <author>` | Release bot | Sets the release commit author in `"Name <email>"` format. |
183
+ | `-t, --token <token>` | Environment | Sets the repository access token used to resolve authors and publish Releases. |
184
+ | `--tag-prefix <prefix>` | `v` | Sets the prefix used to find and create version tags. |
185
+ | `--no-tag-prefix` | `false` | Finds and creates version tags without a prefix. |
186
+ | `--name <name>` | Version tag | Sets the remote Release name; only valid with `--release`. |
187
+ | `-d, --draft` | `false` | Creates a draft Release; only valid with `--release`. |
188
+ | `--prerelease` | Inferred | Marks a prerelease; only valid with `--release`. |
189
+ | `--emoji` / `--no-emoji` | `true` | Controls section title emojis. |
190
+ | `--capitalize` / `--no-capitalize` | `true` | Controls changelog entry capitalization. |
191
+ | `--group` / `--no-group` | `true` | Controls grouping for repeated scopes. |
130
192
 
131
- The repository is read from `--repository`, then `package.json#repository`, and
132
- finally the Git `origin`. GitHub URLs are detected automatically. A self-hosted Gitea repository
133
- must be declared explicitly in `package.json`:
193
+ ## Behavior
194
+
195
+ ### Changelog and repository metadata
196
+
197
+ Without `--from`, keepchanges finds the nearest earlier version tag that matches the configured
198
+ tag prefix. If no earlier tag exists, it reads from the first reachable commit. Stable releases
199
+ compare with the previous stable tag; prereleases compare with the nearest previous tag.
200
+
201
+ The repository is resolved from `--repository`, then `package.json#repository`, and finally the
202
+ Git `origin`. When a repository is available, entries include commit and pull request links, and
203
+ the release ends with a version comparison link. Entries use Git author names and include
204
+ `Co-Authored-By` participants. With a provider token, keepchanges attempts to resolve email
205
+ addresses to usernames. Bot accounts are omitted.
206
+
207
+ A self-hosted Gitea repository must be declared explicitly in `package.json`:
134
208
 
135
209
  ```json
136
210
  {
@@ -142,56 +216,38 @@ must be declared explicitly in `package.json`:
142
216
  }
143
217
  ```
144
218
 
145
- When a repository is detected, entries include commit and pull request links,
146
- and the release ends with a version comparison link. GitHub and Gitea both
147
- support author resolution and Release publishing. Gitea uses `GITEA_TOKEN` to
148
- resolve primary commit authors and publish Releases.
149
-
150
- Version tags use the `v` prefix by default. The configured prefix applies to
151
- new tags, existing-tag lookup, version-shaped `--from` and `--to` values,
152
- comparison links, and repository Release names. Branch names, commit hashes,
153
- `HEAD`, and other non-version refs are used unchanged. Tag lookup only considers
154
- the configured prefix, so independent tag sequences do not affect each other.
155
- When `--to` is provided without `--from`, the CLI finds the previous matching
156
- tag relative to that target instead of the current `HEAD`. For the first tag in
157
- a sequence, it reads all commits reachable from the target.
219
+ GitHub and Gitea both support author resolution and Release publishing. Gitea uses
220
+ `GITEA_TOKEN`, but does not currently support asset uploads.
158
221
 
159
- Entries use Git author names by default and include `Co-Authored-By`
160
- participants. Bot accounts are omitted. With the corresponding provider token,
161
- the CLI attempts to resolve email addresses to usernames.
222
+ ### Tags and history ranges
162
223
 
163
- `--commit` commits only the changelog and detected version file. Other staged
164
- and unstaged changes remain untouched.
224
+ Version tags use the `v` prefix by default. The configured prefix applies to tag creation and
225
+ lookup, version-shaped `--from` and `--to` values, comparison links, and Release names. Branch
226
+ names, commit hashes, `HEAD`, and other non-version refs remain unchanged. Tag sequences with
227
+ different prefixes are independent.
165
228
 
166
- When the tag does not exist, `--release`:
229
+ When `--to` is provided without `--from`, keepchanges finds the previous matching tag relative
230
+ to that target instead of the current `HEAD`. With `--commit`, `--to` must resolve to the current
231
+ `HEAD`.
167
232
 
168
- 1. Writes the changelog and updates the version file.
169
- 2. Creates or reuses a release commit.
170
- 3. Creates an annotated tag.
171
- 4. Pushes `HEAD` and the tag to `origin`.
172
- 5. Creates a repository Release, or prints a manual URL without a GitHub/Gitea token.
233
+ ### Commit and release safety
173
234
 
174
- When the tag already exists, `--release` never moves it. It regenerates Release
175
- notes from the previous version to the existing tag and creates or updates the
176
- Release. A remote-only tag is fetched, while a local-only tag is pushed. The
177
- command stops without force-updating when local and remote tags point to
178
- different commits.
235
+ `--commit` commits only the changelog and detected version file. Other staged and unstaged
236
+ changes remain untouched.
179
237
 
180
- Stable releases compare with the previous stable tag. Prereleases compare with
181
- the nearest previous tag. With `--release`, `--dry` takes precedence and
182
- prevents all file writes and remote mutations. Its manual URL opens a new
183
- Release for a new tag and the edit page for an existing tag.
238
+ `--release` reuses an existing tag, regenerates its Release notes, and creates or updates the
239
+ Release. A remote-only tag is fetched, while a local-only tag is pushed. If local and remote
240
+ tags point to different commits, the command stops without force-updating or moving the tag.
184
241
 
185
- > [!NOTE]
186
- > A regular `--release` without a token still writes files, commits, tags, and
187
- > pushes before providing the manual Release URL. Use `--release --dry`
188
- > for a completely non-mutating preview.
242
+ `--dry` takes precedence over other options and prevents all file writes and remote mutations.
243
+ Without `--asset` and a provider token, `--release` still writes locally, commits, tags, and
244
+ pushes before printing a URL for manually creating or editing the Release.
189
245
 
190
246
  ## Programmatic API
191
247
 
192
- The package root exports `generateChangelog`, the commit parser, default config,
193
- and core types. The CLI is only available as the `keepchanges` binary, while
194
- programmatic callers can provide changelog style overrides:
248
+ The package root exports `generateChangelog`, the commit parser, default config, and core types.
249
+ The CLI is available only as the `keepchanges` binary. Programmatic callers can override the
250
+ changelog style:
195
251
 
196
252
  ```ts
197
253
  import { generateChangelog } from 'keepchanges'
package/README.zh-CN.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # keepchanges
2
2
 
3
- 基于 Conventional Commits 生成并维护 `CHANGELOG.md`。
3
+ 面向 Conventional Commits 的发布 CLI:生成并维护 changelog,并按需更新项目版本、
4
+ 创建 release commit 和 Git tag、发布 GitHub/Gitea Release,以及上传 GitHub Release 附件。
4
5
 
5
6
  灵感来自 [changelogithub](https://github.com/antfu-collective/changelogithub)。
6
7
 
@@ -8,13 +9,14 @@
8
9
 
9
10
  ## 功能
10
11
 
11
- - 根据最近的 Git tag 到 `HEAD` 之间的 Conventional Commits 生成变更记录
12
- - 首次发布时读取完整 Git 历史
13
- - 将新版本插入 `CHANGELOG.md` 的已有内容之前
14
- - npm 项目同步更新 `package.json` 中的 `version`
15
- - 为提交、对比页面、作者和共同作者生成仓库平台信息
16
- - 可选择自动提交、创建 tag、推送并发布 GitHub/Gitea Release
17
- - 没有 GitHub/Gitea token 时提供手动发布链接
12
+ - 根据 Git tag 之间的 Conventional Commits 生成 Release notes
13
+ - 将新版本插入现有 `CHANGELOG.md`,并为 npm 项目更新 `package.json#version`
14
+ - 可分别通过 `--no-bump` 和 `--no-changelog` 关闭版本更新与 changelog 写入
15
+ - 可自动创建 release commit、annotated tag 并推送到远程仓库
16
+ - 支持先固定 release commit,再基于该 commit 构建产物
17
+ - 可创建或更新 GitHub/Gitea Release
18
+ - 可将文件、目录或 glob 匹配结果上传为 GitHub Release 附件
19
+ - 为 commit、PR、版本对比、作者和共同作者生成仓库平台信息
18
20
 
19
21
  当前会收录 `feat`、`fix`、`perf`、带 `!` 的破坏性变更,以及包含
20
22
  `BREAKING CHANGE` 或 `BREAKING-CHANGE` trailer 的提交。其他提交类型会被忽略。
@@ -29,56 +31,95 @@
29
31
  npx keepchanges 1.1.0
30
32
  ```
31
33
 
32
- 也可以使用带 `v` 前缀的版本号:
34
+ 版本号也可以包含 `v` 前缀:
33
35
 
34
36
  ```bash
35
37
  npx keepchanges v1.1.0
36
38
  ```
37
39
 
38
- 默认行为会写入 `CHANGELOG.md`。如果当前项目是 npm 包,还会将
39
- `package.json#version` 更新为 `1.1.0`,但不会自动创建 Git commit。
40
+ 默认会写入 `CHANGELOG.md`。如果当前项目是 npm 包,还会将
41
+ `package.json#version` 更新为 `1.1.0`,但不会创建 Git commit。
40
42
 
41
- ## 命令格式
43
+ ## 发布工作流
42
44
 
43
- ```text
44
- npx keepchanges <version> [options]
45
+ keepchanges 可以根据项目的版本管理和构建方式组合发布阶段:
46
+
47
+ | 场景 | 更新版本 | 写入 changelog | 构建时机 | 发布方式 |
48
+ | --- | --- | --- | --- | --- |
49
+ | 完整版本发布 | 是 | 是 | 创建 release commit 后 | `--tag`,构建,再 `--release --asset` |
50
+ | 外部管理版本 | 否 | 是 | 发布前 | 构建,再 `--release --no-bump --asset` |
51
+ | 仅发布构建产物 | 否 | 否 | 发布前 | 构建,再 `--release --no-bump --no-changelog --asset` |
52
+
53
+ ### 完整版本发布
54
+
55
+ 先由 keepchanges 更新版本和 changelog、创建 commit 和 tag,再基于该 commit
56
+ 构建产物。随后发布同一个 tag,并上传构建产物:
57
+
58
+ ```bash
59
+ npx keepchanges 1.1.0 --tag
60
+ pnpm build
61
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release --asset dist
45
62
  ```
46
63
 
47
- ### 参数
64
+ 第二次执行会复用已有 tag,不会移动 tag 或重新提交构建产物。
48
65
 
49
- | 参数 | 默认值 | 说明 |
50
- | --- | --- | --- |
51
- | `<version>` | 必填 | 要生成的版本号。可以传入 `1.1.0` 或 `v1.1.0`。包含 `-` 的版本会作为预发布版本,例如 `1.1.0-beta.1`。 |
52
- | `--from <ref>` | 根据目标推断 | 指定读取 commit 时不包含在结果中的起始 Git ref,并覆盖自动选择结果。省略时,目标为 `HEAD` 会使用匹配前缀的最近 tag;显式传入 `--to` 会查找该目标之前匹配前缀的 tag。如果不存在前一个 tag,则从首个 commit 开始读取。`1.0.0` 这类版本会自动应用配置的 tag 前缀。 |
53
- | `--to <ref>` | `HEAD` | 指定读取 commit 的结束 Git ref。`1.1.0` 这类版本会自动应用配置的 tag 前缀。不能与 `--release` 一起使用;与 `--commit` 一起使用时必须指向当前 `HEAD`。 |
54
- | `--repository <source>` | 自动检测 | 指定 `owner/repo` 格式的 GitHub 仓库或 GitHub/Gitea 完整 URL。优先级高于 `package.json` 和 `origin`。 |
55
- | `--output <path>` | `CHANGELOG.md` | 指定 changelog 文件路径。相对路径以当前工作目录为基准。 |
56
- | `--dry` | `false` | 输出当前版本预览,不写入文件,也不执行 commit、tag、push 或发布 API。 |
57
- | `--commit` | `false` | 写入文件后创建 Git commit。只提交 changelog 和检测到的版本文件,默认提交信息为 `chore(release): v<version>`。 |
58
- | `--release` | `false` | 执行完整发布流程:写入文件、创建或复用 release commit、创建 annotated tag、推送 `HEAD` 和 tag,然后创建或更新仓库 Release。该参数隐含 `--commit`。 |
59
- | `--author <author>` | release bot | 设置自动创建的 release commit 作者,格式必须为 `"Name <email>"`;需要与 `--commit` 或 `--release` 一起使用。 |
60
- | `-t, --token <token>` | 环境变量 | 仓库访问令牌,用于解析作者及发布 Release。GitHub 优先级为 `--token`、`GITHUB_TOKEN`、`GH_TOKEN`;Gitea 使用 `GITEA_TOKEN`。 |
61
- | `--tag-prefix <prefix>` | `v` | 设置查找和创建版本 tag 时使用的前缀,例如 `package@`。 |
62
- | `--no-tag-prefix` | `false` | 查找和创建不带前缀的版本 tag。 |
63
- | `--name <name>` | 版本 tag | 设置远程 Release 名称;仅适用于 `--release`。 |
64
- | `-d, --draft` | `false` | 创建 draft Release;仅适用于 `--release`。 |
65
- | `--prerelease` | 根据版本推断 | 显式标记为 prerelease;默认根据版本是否包含 `-` 推断,仅适用于 `--release`。 |
66
- | `--emoji` | `true` | 控制 section 标题 emoji。与 changelogithub 一样,可使用 CAC 的 `--no-emoji` 形式关闭。 |
67
- | `--capitalize` | `true` | 控制 changelog 条目首字母大写;可使用 `--no-capitalize` 关闭。 |
68
- | `--group` | `true` | 当 scope 重复时分组;可使用 `--no-group` 关闭。 |
66
+ ### 保留 changelog,跳过版本更新
67
+
68
+ 如果版本由其他工具或流程管理,可以先构建,再让 keepchanges 写入 changelog、
69
+ 创建 commit tag,并发布附件:
69
70
 
70
- ## 常见用法
71
+ ```bash
72
+ pnpm build
73
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release \
74
+ --no-bump \
75
+ --asset dist
76
+ ```
77
+
78
+ ### 只发布构建产物
79
+
80
+ 如果不需要版本更新和 changelog,可以先构建,再从当前 `HEAD` 创建 tag 和 Release:
81
+
82
+ ```bash
83
+ pnpm build
84
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release \
85
+ --no-bump \
86
+ --no-changelog \
87
+ --asset dist
88
+ ```
89
+
90
+ ## GitHub Release 附件
71
91
 
72
- 指定输出文件:
92
+ `--asset` 支持单个文件、目录和 glob。目录会递归展开;该参数可以重复使用,
93
+ 因此可以同时上传多个文件夹或匹配结果:
94
+
95
+ ```bash
96
+ GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release \
97
+ --asset 'packages/*/dist' \
98
+ --asset artifacts \
99
+ --asset checksums.txt
100
+ ```
101
+
102
+ 建议始终给 glob 加引号,确保由 keepchanges 而不是 shell 展开。发布前会验证所有
103
+ 附件:路径必须存在、glob 必须至少匹配一个文件,并且最终文件名必须唯一。
104
+ 更新已有 Release 时,同名附件会被替换。
105
+
106
+ 附件上传目前只支持 GitHub,并且必须提供 GitHub token。token 的读取顺序为
107
+ `--token`、`GITHUB_TOKEN`、`GH_TOKEN`。如果没有指定 `--asset`,则普通
108
+ `--release` 可以在没有 token 时完成 commit、tag 和 push,随后输出手动发布链接。
109
+
110
+ ## 常见任务
111
+
112
+ 指定 changelog 文件:
73
113
 
74
114
  ```bash
75
115
  npx keepchanges 1.1.0 --output docs/CHANGELOG.md
76
116
  ```
77
117
 
78
- 预览当前版本,不修改文件:
118
+ 预览结果,不修改文件或远程仓库:
79
119
 
80
120
  ```bash
81
121
  npx keepchanges 1.1.0 --dry
122
+ npx keepchanges 1.1.0 --release --dry
82
123
  ```
83
124
 
84
125
  写入 changelog、更新版本并创建 commit:
@@ -87,47 +128,77 @@ npx keepchanges 1.1.0 --dry
87
128
  npx keepchanges 1.1.0 --commit
88
129
  ```
89
130
 
90
- 指定 release commit 的作者:
131
+ 指定 release commit 作者:
91
132
 
92
133
  ```bash
93
134
  npx keepchanges 1.1.0 --commit \
94
135
  --author "Release Author <release@example.com>"
95
136
  ```
96
137
 
97
- 创建 GitHub Release:
138
+ 创建不带附件的 GitHub Release:
98
139
 
99
140
  ```bash
100
141
  GITHUB_TOKEN=github_pat_xxx npx keepchanges 1.1.0 --release
101
142
  ```
102
143
 
103
- 使用无前缀版本 tag:
144
+ 使用无前缀或 package 专属版本 tag:
104
145
 
105
146
  ```bash
106
147
  npx keepchanges 1.1.0 --no-tag-prefix
107
- ```
108
-
109
- 使用 package 专属 tag 前缀:
110
-
111
- ```bash
112
148
  npx keepchanges 1.1.0 --tag-prefix 'package@'
113
149
  ```
114
150
 
115
- 不指定前一个 tag,重新生成历史版本:
151
+ 重新生成历史版本:
116
152
 
117
153
  ```bash
118
154
  npx keepchanges 1.0.0 --to 1.0.0 --dry
119
155
  ```
120
156
 
121
- 预览 Release,不写文件、不提交、不创建 tag、不推送且不调用发布 API:
157
+ ## 命令参考
122
158
 
123
- ```bash
124
- npx keepchanges 1.1.0 --release --dry
159
+ ```text
160
+ npx keepchanges <version> [options]
125
161
  ```
126
162
 
127
- ## 生成和发布行为
163
+ | 参数 | 默认值 | 说明 |
164
+ | --- | --- | --- |
165
+ | `<version>` | 必填 | 要生成的版本号,例如 `1.1.0`、`v1.1.0` 或 `1.1.0-beta.1`。 |
166
+ | `--from <ref>` | 根据目标推断 | 指定读取 commit 时不包含在结果中的起始 Git ref。 |
167
+ | `--to <ref>` | `HEAD` | 指定结束 Git ref。不能与 `--tag` 或 `--release` 一起使用。 |
168
+ | `--repository <source>` | 自动检测 | 指定 GitHub `owner/repo` 或 GitHub/Gitea 完整 URL。 |
169
+ | `--output <path>` | `CHANGELOG.md` | 指定 changelog 文件路径。 |
170
+ | `--dry` | `false` | 输出预览,不执行文件写入、commit、tag、push 或发布 API。 |
171
+ | `--commit` | `false` | 写入后创建 Git commit。 |
172
+ | `--tag` | `false` | 写入、commit、创建 annotated tag 并 push,但不创建仓库 Release。 |
173
+ | `--release` | `false` | 完成 commit、tag、push,并创建或更新仓库 Release;隐含 `--commit`。 |
174
+ | `--asset <path>` | 无 | 上传 GitHub Release 附件;支持文件、目录、glob 和重复传参。需要 `--release` 和 GitHub token。 |
175
+ | `--bump` / `--no-bump` | `true` | 控制是否更新检测到的项目版本文件。 |
176
+ | `--changelog` / `--no-changelog` | `true` | 控制是否写入 changelog;关闭后仍会生成 Release notes。 |
177
+ | `--author <author>` | release bot | 指定 release commit 作者,格式为 `"Name <email>"`。 |
178
+ | `-t, --token <token>` | 环境变量 | 指定仓库访问令牌,用于解析作者和发布 Release。 |
179
+ | `--tag-prefix <prefix>` | `v` | 指定查找和创建版本 tag 时使用的前缀。 |
180
+ | `--no-tag-prefix` | `false` | 查找和创建不带前缀的版本 tag。 |
181
+ | `--name <name>` | 版本 tag | 指定远程 Release 名称;仅适用于 `--release`。 |
182
+ | `-d, --draft` | `false` | 创建 draft Release;仅适用于 `--release`。 |
183
+ | `--prerelease` | 根据版本推断 | 标记 prerelease;仅适用于 `--release`。 |
184
+ | `--emoji` / `--no-emoji` | `true` | 控制 section 标题 emoji。 |
185
+ | `--capitalize` / `--no-capitalize` | `true` | 控制 changelog 条目首字母大写。 |
186
+ | `--group` / `--no-group` | `true` | 控制重复 scope 分组。 |
187
+
188
+ ## 行为说明
189
+
190
+ ### Changelog 和仓库信息
128
191
 
129
- 仓库地址优先读取 `--repository`,其次读取 `package.json#repository`,最后读取 Git `origin`。
130
- GitHub 地址会被自动识别。自托管 Gitea 需要在 `package.json` 中显式声明:
192
+ 省略 `--from` 时,keepchanges 会查找目标之前匹配 tag 前缀的最近版本 tag。
193
+ 如果不存在前一个 tag,则从首个可达 commit 开始读取。稳定版本与前一个稳定 tag
194
+ 对比,预发布版本与最近的前一个 tag 对比。
195
+
196
+ 仓库地址依次读取 `--repository`、`package.json#repository` 和 Git `origin`。
197
+ 识别仓库后,记录会包含 commit 和 PR 链接,末尾会包含版本对比链接。默认使用
198
+ Git 作者名并包含 `Co-Authored-By` 参与者;提供平台 token 后,会尝试将邮箱解析为
199
+ 用户名。bot 账号会被忽略。
200
+
201
+ 自托管 Gitea 仓库需要在 `package.json` 中显式声明:
131
202
 
132
203
  ```json
133
204
  {
@@ -139,50 +210,36 @@ GitHub 地址会被自动识别。自托管 Gitea 需要在 `package.json` 中
139
210
  }
140
211
  ```
141
212
 
142
- 识别到仓库后,每条记录会包含 commit PR 链接,末尾会包含版本对比链接。
143
- GitHub 和 Gitea 均支持作者解析和 Release 发布;Gitea 使用 `GITEA_TOKEN`
144
- 解析 commit 主作者并发布 Release。
213
+ GitHubGitea 均支持作者解析与 Release 发布。Gitea 使用 `GITEA_TOKEN`,
214
+ 但目前不支持附件上传。
145
215
 
146
- 版本 tag 默认使用 `v` 前缀。配置的前缀会统一应用于新 tag、已有 tag 查找、
147
- 版本形式的 `--from` 和 `--to`、比较链接及仓库 Release 名称。分支名、commit
148
- hash、`HEAD` 等非版本 ref 保持不变。自动查找 tag 时只考虑配置的前缀,因此
149
- 彼此独立的 tag 序列不会相互影响。
150
- 省略 `--from` 并传入 `--to` 时,CLI 会相对于该目标查找匹配前缀的前一个 tag,
151
- 而不是使用当前 `HEAD` 的最近 tag。如果目标是该序列中的首个 tag,则读取该目标
152
- 可达的全部 commit。
216
+ ### Tag 和历史范围
153
217
 
154
- 默认使用 Git 提交中的作者名,并将 `Co-Authored-By` 参与者一起写入记录,bot
155
- 账号会被忽略。提供对应平台的 token 后,会尝试将邮箱解析为用户名。
218
+ 版本 tag 默认使用 `v` 前缀。配置的前缀会应用于 tag 创建和查找、版本形式的
219
+ `--from` `--to`、比较链接及 Release 名称。分支名、commit hash 和 `HEAD`
220
+ 等非版本 ref 保持不变。不同前缀的 tag 序列彼此独立。
156
221
 
157
- `--commit` 只提交 changelog 和检测到的版本文件。其他已暂存或未暂存的改动会
158
- 保持原状。
222
+ 显式传入 `--to` 而不传 `--from` 时,会相对于该目标查找前一个匹配的 tag,
223
+ 而不是使用当前 `HEAD` 的最近 tag。与 `--commit` 一起使用时,`--to` 必须指向
224
+ 当前 `HEAD`。
159
225
 
160
- `--release` tag 不存在时:
226
+ ### Commit 和发布安全
161
227
 
162
- 1. 写入 changelog 并更新版本文件。
163
- 2. 创建或复用 release commit。
164
- 3. 创建 annotated tag。
165
- 4. 将 `HEAD` 和 tag 推送到 `origin`。
166
- 5. 创建仓库 Release;GitHub/Gitea 没有 token 时输出手动发布链接。
167
-
168
- `--release` 在 tag 已存在时不会移动 tag。它会根据前一个版本到现有 tag
169
- 重新生成 Release notes,并创建或更新 Release。只有远程存在 tag 时会先将它
170
- 拉取到本地;只有本地存在 tag 时会推送该 tag。本地与远程 tag 指向不同 commit
171
- 时会停止,不会强制覆盖。
228
+ `--commit` 只提交 changelog 和检测到的版本文件,其他已暂存或未暂存的改动会
229
+ 保持原状。
172
230
 
173
- 稳定版本会与前一个稳定 tag 对比,预发布版本会与最近的前一个 tag 对比。与
174
- `--release` 搭配的 `--dry` 优先级最高,不会执行任何写入或远程修改。手动链接
175
- 会为新 tag 打开创建 Release 页面,为已有 tag 打开编辑页面。
231
+ `--release` 会复用已有 tag,重新生成该版本的 Release notes,并创建或更新
232
+ Release。远程独有的 tag 会被拉取,本地独有的 tag 会被推送;本地和远程 tag
233
+ 指向不同 commit 时会停止,不会强制覆盖或移动 tag
176
234
 
177
- > [!NOTE]
178
- > 没有 token 的普通 `--release` 仍会执行本地写入、commit、tag 和 push
179
- > 然后提供手动创建 Release 的链接。如需完全无副作用的预览,请使用
180
- > `--release --dry`。
235
+ `--dry` 优先于其他选项,不会执行任何文件写入或远程修改。未指定 `--asset` 且
236
+ 没有平台 token 时,`--release` 仍会执行本地写入、commit、tag 和 push,随后
237
+ 提供手动创建或编辑 Release 的链接。
181
238
 
182
239
  ## 程序化 API
183
240
 
184
241
  包根入口导出 `generateChangelog`、commit parser、默认配置及核心类型。
185
- CLI 仅作为 `keepchanges` 二进制命令。程序化调用可以传入 changelog 样式覆盖:
242
+ CLI 仅作为 `keepchanges` 二进制命令。程序化调用可以覆盖 changelog 样式:
186
243
 
187
244
  ```ts
188
245
  import { generateChangelog } from 'keepchanges'
package/dist/cli.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { a as insertRelease, c as defaultConfig, i as hasRelease, n as parseCommits, o as readChangelog, r as generateChangelog, s as writeChangelog } from "./commit-DBopyhs1.mjs";
3
- import { readFile, writeFile } from "node:fs/promises";
2
+ import { a as insertRelease, c as defaultConfig, i as hasRelease, n as parseCommits, o as readChangelog, r as generateChangelog, s as writeChangelog } from "./commit-DswG6QDH.mjs";
3
+ import { lstat, readFile, readdir, writeFile } from "node:fs/promises";
4
4
  import { normalizeFull } from "verkit";
5
5
  import process from "node:process";
6
6
  import { cac } from "cac";
7
- import { resolve } from "node:path";
7
+ import { basename, resolve } from "node:path";
8
8
  import ansis from "ansis";
9
+ import { glob, isDynamicPattern } from "tinyglobby";
9
10
  import { x } from "tinyexec";
10
11
  //#region package.json
11
- var version = "1.0.2";
12
+ var version = "1.0.3";
12
13
  //#endregion
13
14
  //#region src/git.ts
14
15
  function versionTagPattern(tagPrefix) {
@@ -166,6 +167,7 @@ const giteaRepository = {
166
167
  const githubRepository = {
167
168
  name: "GitHub",
168
169
  tokenEnv: "GITHUB_TOKEN",
170
+ supportsReleaseAssets: true,
169
171
  parse(source) {
170
172
  const match = /github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/.exec(source);
171
173
  if (!match) return;
@@ -224,10 +226,13 @@ const githubRepository = {
224
226
  let url = releasesUrl;
225
227
  let method = "POST";
226
228
  let action = "created";
229
+ let existingAssets = [];
227
230
  if (existing.ok) {
228
- url = `${releasesUrl}/${(await existing.json()).id}`;
231
+ const data = await existing.json();
232
+ url = `${releasesUrl}/${data.id}`;
229
233
  method = "PATCH";
230
234
  action = "updated";
235
+ existingAssets = data.assets ?? [];
231
236
  } else if (existing.status !== 404) throw new Error(`GitHub release lookup failed (${existing.status})`);
232
237
  const response = await fetch(url, {
233
238
  method,
@@ -241,8 +246,33 @@ const githubRepository = {
241
246
  })
242
247
  });
243
248
  if (!response.ok) throw new Error(`GitHub release publishing failed (${response.status})`);
249
+ const data = await response.json();
250
+ if (release.assets?.length) {
251
+ if (!data.upload_url) throw new Error("GitHub release response did not include an upload URL");
252
+ const uploadUrl = data.upload_url.replace(/\{.*$/, "");
253
+ for (const asset of release.assets) {
254
+ const existingAsset = existingAssets.find((candidate) => candidate.name === asset.name);
255
+ if (existingAsset) {
256
+ const deleteResponse = await fetch(`https://api.github.com/repos/${repository.path}/releases/assets/${existingAsset.id}`, {
257
+ method: "DELETE",
258
+ headers
259
+ });
260
+ if (!deleteResponse.ok) throw new Error(`GitHub release asset replacement failed for ${asset.name} (${deleteResponse.status})`);
261
+ }
262
+ const uploadResponse = await fetch(`${uploadUrl}?name=${encodeURIComponent(asset.name)}`, {
263
+ method: "POST",
264
+ headers: {
265
+ "accept": "application/vnd.github+json",
266
+ "authorization": `Bearer ${token}`,
267
+ "content-type": "application/octet-stream"
268
+ },
269
+ body: asset.data
270
+ });
271
+ if (!uploadResponse.ok) throw new Error(`GitHub release asset upload failed for ${asset.name} (${uploadResponse.status})`);
272
+ }
273
+ }
244
274
  return {
245
- url: (await response.json()).html_url,
275
+ url: data.html_url,
246
276
  action
247
277
  };
248
278
  }
@@ -332,7 +362,7 @@ function printPublishedRelease(provider, result, stdout, colors) {
332
362
  //#endregion
333
363
  //#region src/cli/createChanges.ts
334
364
  async function createChanges(options, environment) {
335
- if (options.commit && options.to !== defaultConfig.cli.to) {
365
+ if ((options.commit || options.tag || options.release) && options.to !== defaultConfig.cli.to) {
336
366
  const [toCommit, headCommit] = await Promise.all([git(environment.cwd, "rev-parse", options.to).then((value) => value.trim()), git(environment.cwd, "rev-parse", "HEAD").then((value) => value.trim())]);
337
367
  if (toCommit !== headCommit) throw new Error("--to must resolve to HEAD when used with --commit");
338
368
  }
@@ -343,9 +373,12 @@ async function createChanges(options, environment) {
343
373
  const repository = await resolveRepository(environment.cwd, options.repository);
344
374
  const token = repository?.provider.token(options.token, env);
345
375
  validateReleaseSupport(options, repository);
346
- let taggedCommit = options.release ? await getTagCommit(environment.cwd, tag) : void 0;
376
+ if (options.release && !options.dry && options.assets.length && !token) throw new Error(`A ${repository.provider.name} token is required to upload release assets`);
377
+ const releaseAssets = options.release && !options.dry && options.assets.length ? await readReleaseAssets(environment.cwd, options.assets) : [];
378
+ const createsTag = options.tag || options.release;
379
+ let taggedCommit = createsTag ? await getTagCommit(environment.cwd, tag) : void 0;
347
380
  let releaseRef = taggedCommit ? tag : void 0;
348
- const remoteTaggedCommit = options.release ? await getRemoteTagCommit(environment.cwd, tag) : void 0;
381
+ const remoteTaggedCommit = createsTag ? await getRemoteTagCommit(environment.cwd, tag) : void 0;
349
382
  if (taggedCommit && remoteTaggedCommit && taggedCommit !== remoteTaggedCommit) throw new Error(`Tag ${tag} differs between local and origin`);
350
383
  if (remoteTaggedCommit && !taggedCommit) if (options.dry) {
351
384
  taggedCommit = remoteTaggedCommit;
@@ -396,38 +429,45 @@ async function createChanges(options, environment) {
396
429
  }
397
430
  return;
398
431
  }
399
- if (options.release && taggedCommit) {
432
+ if (createsTag && taggedCommit) {
400
433
  if (!remoteTaggedCommit) await git(environment.cwd, "push", "origin", `refs/tags/${tag}`);
401
- await publishRelease(repository, repositoryRelease, token, preview, environment, "edit");
434
+ if (!options.release) return;
435
+ await publishRelease(repository, repositoryRelease, token, preview, environment, releaseAssets, "edit");
402
436
  return;
403
437
  }
404
- const outputPath = resolve(environment.cwd, options.output);
405
- const currentChangelog = await readChangelog(outputPath);
406
- const releaseExists = hasRelease(currentChangelog, options.version);
407
- await writeChangelog(outputPath, insertRelease(currentChangelog, release));
408
- const versionPath = await updateVersion(environment.cwd, options.version);
409
- if (options.commit || options.release) await commitReleaseFiles(options, environment.cwd, outputPath, versionPath, releaseExists, tag);
410
- if (options.release) {
438
+ let outputPath;
439
+ let releaseExists = false;
440
+ if (options.changelog) {
441
+ outputPath = resolve(environment.cwd, options.output);
442
+ const currentChangelog = await readChangelog(outputPath);
443
+ releaseExists = hasRelease(currentChangelog, options.version);
444
+ await writeChangelog(outputPath, insertRelease(currentChangelog, release));
445
+ }
446
+ const versionPath = options.bump ? await updateVersion(environment.cwd, options.version) : void 0;
447
+ if (options.commit || createsTag) await commitReleaseFiles(options, environment.cwd, outputPath, versionPath, releaseExists, tag);
448
+ if (createsTag) {
411
449
  const gitIdentity = resolveGitIdentity(options.author);
412
450
  await git(environment.cwd, ...gitIdentity, "tag", "-a", tag, "-m", tag);
413
451
  await git(environment.cwd, "push", "origin", "HEAD", `refs/tags/${tag}`);
414
- await publishRelease(repository, repositoryRelease, token, preview, environment);
452
+ if (options.release) await publishRelease(repository, repositoryRelease, token, preview, environment, releaseAssets);
415
453
  }
416
454
  }
417
455
  function validateReleaseSupport(options, repository) {
418
456
  if (!options.release) return;
419
457
  if (!repository) throw new Error("A supported repository is required to release");
420
458
  if (!repository.provider.publishRelease && !repository.provider.manualReleaseUrl) throw new Error(`${repository.provider.name} does not support releases`);
459
+ if (options.assets.length && !repository.provider.supportsReleaseAssets) throw new Error(`${repository.provider.name} does not support release assets`);
421
460
  }
422
461
  async function commitReleaseFiles(options, cwd, outputPath, versionPath, releaseExists, tag) {
423
462
  const releasePaths = [outputPath, versionPath].filter((path) => path !== void 0);
463
+ if (!releasePaths.length) return;
424
464
  if (!(await git(cwd, "status", "--porcelain", "--", ...releasePaths)).trim()) return;
425
465
  await git(cwd, "add", "--", ...releasePaths);
426
466
  const versionChanges = versionPath ? await git(cwd, "status", "--porcelain", "--", versionPath) : "";
427
467
  const commitMessage = versionPath && !versionChanges.trim() ? `docs(changelog): ${releaseExists ? "update" : "add"} ${tag} release notes` : `chore(release): ${tag}`;
428
468
  await git(cwd, ...resolveGitIdentity(options.author), "commit", "-m", commitMessage, ...options.author ? ["--author", options.author] : [], "--only", "--", ...releasePaths);
429
469
  }
430
- async function publishRelease(repository, release, token, preview, environment, manualAction = "create") {
470
+ async function publishRelease(repository, release, token, preview, environment, assets, manualAction = "create") {
431
471
  const stdout = environment.stdout ?? ((value) => process.stdout.write(value));
432
472
  const colors = environment.colors ?? ansis;
433
473
  printChangesPreview(preview, stdout, colors);
@@ -437,9 +477,52 @@ async function publishRelease(repository, release, token, preview, environment,
437
477
  printManualReleaseUrl(repository, release, stdout, colors, manualAction);
438
478
  return;
439
479
  }
440
- const result = await repository.provider.publishRelease(repository, release, token, environment.fetch ?? globalThis.fetch);
480
+ const result = await repository.provider.publishRelease(repository, {
481
+ ...release,
482
+ assets
483
+ }, token, environment.fetch ?? globalThis.fetch);
441
484
  printPublishedRelease(repository.provider.name, result, stdout, colors);
442
485
  }
486
+ async function readReleaseAssets(cwd, paths) {
487
+ const files = /* @__PURE__ */ new Set();
488
+ async function collect(path) {
489
+ const metadata = await lstat(path).catch(() => void 0);
490
+ if (!metadata) throw new Error(`Release asset does not exist: ${path}`);
491
+ if (metadata.isFile()) {
492
+ files.add(path);
493
+ return;
494
+ }
495
+ if (!metadata.isDirectory()) throw new Error(`Release asset must be a file or directory: ${path}`);
496
+ const entries = await readdir(path, { withFileTypes: true });
497
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) await collect(resolve(path, entry.name));
498
+ }
499
+ for (const path of paths) {
500
+ if (!isDynamicPattern(path)) {
501
+ await collect(resolve(cwd, path));
502
+ continue;
503
+ }
504
+ const matches = await glob(path, {
505
+ absolute: true,
506
+ cwd,
507
+ dot: true,
508
+ expandDirectories: false,
509
+ followSymbolicLinks: false,
510
+ onlyFiles: false
511
+ });
512
+ if (!matches.length) throw new Error(`Release asset pattern did not match: ${path}`);
513
+ for (const match of matches.sort((left, right) => left.localeCompare(right))) await collect(match);
514
+ }
515
+ const names = /* @__PURE__ */ new Set();
516
+ return Promise.all([...files].map(async (path) => {
517
+ const name = basename(path);
518
+ if (names.has(name)) throw new Error(`Release assets must have unique file names: ${name}`);
519
+ names.add(name);
520
+ return {
521
+ name,
522
+ data: await readFile(path)
523
+ };
524
+ }));
525
+ }
443
526
  function resolveGitIdentity(author) {
444
527
  const match = /^([^<>\r\n]+)<([^<>\r\n]+)>$/.exec(author);
445
528
  if (!match) throw new Error("Author must use the \"Name <email>\" format");
@@ -462,8 +545,9 @@ function resolveOptions(versionArgument, options) {
462
545
  if (typeof options.tagPrefix === "string") tagPrefix = options.tagPrefix;
463
546
  else if (options.tagPrefix === false) tagPrefix = "";
464
547
  if (options.to !== void 0 && release) throw new Error("--to cannot be used with --release");
465
- if (options.author !== void 0 && !commit && !release) throw new Error("--author requires --commit or --release");
466
- if (!release && (options.name !== void 0 || options.draft !== void 0 || options.prerelease !== void 0)) throw new Error("--name, --draft, and --prerelease require --release");
548
+ if (options.to !== void 0 && options.tag) throw new Error("--to cannot be used with --tag");
549
+ if (options.author !== void 0 && !commit && !options.tag && !release) throw new Error("--author requires --commit, --tag, or --release");
550
+ if (!release && (options.name !== void 0 || options.draft !== void 0 || options.prerelease !== void 0 || (options.asset?.length ?? 0) > 0)) throw new Error("--name, --draft, --prerelease, and --asset require --release");
467
551
  return {
468
552
  version,
469
553
  from: options.from,
@@ -472,7 +556,11 @@ function resolveOptions(versionArgument, options) {
472
556
  output: options.output ?? defaultConfig.cli.output,
473
557
  dry: options.dry ?? defaultConfig.cli.dry,
474
558
  commit,
559
+ tag: options.tag ?? defaultConfig.cli.tag,
475
560
  release,
561
+ assets: options.asset ?? [],
562
+ bump: options.bump ?? defaultConfig.cli.bump,
563
+ changelog: options.changelog ?? defaultConfig.cli.changelog,
476
564
  author: options.author ?? defaultConfig.cli.author,
477
565
  token: options.token,
478
566
  tagPrefix,
@@ -486,7 +574,10 @@ function resolveOptions(versionArgument, options) {
486
574
  }
487
575
  //#endregion
488
576
  //#region src/cli.ts
489
- const cli = cac("keepchanges").option("--from <ref>", "Start Git reference").option("--to <ref>", "End Git reference").option("--repository <source>", "Repository slug or URL").option("--output <path>", "Changelog file path").option("--dry", "Preview without modifying files or remotes").option("--commit", "Commit the changelog and version update").option("--release", "Publish a repository release").option("--author <author>", "Commit author in \"Name <email>\" format").option("-t, --token <token>", "Repository token").option("--no-tag-prefix", "Create version tags without a prefix").option("--tag-prefix <prefix>", "Prefix used for version tags", { default: defaultConfig.cli.tagPrefix }).option("--name <name>", "Repository release name").option("-d, --draft", "Create a draft repository release").option("--prerelease", "Mark the repository release as prerelease").option("--emoji", "Use emojis in changelog section titles").option("--capitalize", "Capitalize changelog entries").option("--group", "Group repeated commit scopes");
577
+ const cli = cac("keepchanges").option("--from <ref>", "Start Git reference").option("--to <ref>", "End Git reference").option("--repository <source>", "Repository slug or URL").option("--output <path>", "Changelog file path").option("--dry", "Preview without modifying files or remotes").option("--commit", "Commit the changelog and version update").option("--tag", "Commit, tag, and push without publishing a release").option("--release", "Publish a repository release").option("--asset <path>", "Release asset file, directory, or glob", {
578
+ type: [],
579
+ default: []
580
+ }).option("--bump", "Update the project version; --no-bump disables it", { default: defaultConfig.cli.bump }).option("--changelog", "Write the changelog; --no-changelog disables it", { default: defaultConfig.cli.changelog }).option("--author <author>", "Commit author in \"Name <email>\" format").option("-t, --token <token>", "Repository token").option("--no-tag-prefix", "Create version tags without a prefix").option("--tag-prefix <prefix>", "Prefix used for version tags", { default: defaultConfig.cli.tagPrefix }).option("--name <name>", "Repository release name").option("-d, --draft", "Create a draft repository release").option("--prerelease", "Mark the repository release as prerelease").option("--emoji", "Use emojis in changelog section titles").option("--capitalize", "Capitalize changelog entries").option("--group", "Group repeated commit scopes");
490
581
  cli.command("<version>").usage("<version> [options]").action(async (versionArgument, options) => {
491
582
  await createChanges(resolveOptions(versionArgument, options), { cwd: process.cwd() });
492
583
  });
@@ -7,7 +7,10 @@ const defaultConfig = {
7
7
  to: "HEAD",
8
8
  dry: false,
9
9
  commit: false,
10
+ tag: false,
10
11
  release: false,
12
+ bump: true,
13
+ changelog: true,
11
14
  author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>",
12
15
  tagPrefix: "v",
13
16
  draft: false
package/dist/index.d.mts CHANGED
@@ -19,6 +19,13 @@ interface RepositoryRelease {
19
19
  body: string;
20
20
  prerelease: boolean;
21
21
  draft: boolean;
22
+ assets?: RepositoryReleaseAsset[];
23
+ }
24
+ interface RepositoryReleaseAsset {
25
+ /** File name shown on the Release; names must be unique within a Release. */
26
+ name: string;
27
+ /** Exact bytes sent to the repository provider. */
28
+ data: Uint8Array;
22
29
  }
23
30
  interface RepositoryReleaseResult {
24
31
  url: string;
@@ -28,6 +35,7 @@ type ManualReleaseAction = 'create' | 'edit';
28
35
  interface RepositoryProvider {
29
36
  name: string;
30
37
  tokenEnv: string;
38
+ supportsReleaseAssets?: boolean;
31
39
  parse: (source: string) => Repository | undefined;
32
40
  token: (explicit: string | undefined, env: NodeJS.ProcessEnv) => string | undefined;
33
41
  commitUrl: (repository: Repository, hash: string) => string;
@@ -88,7 +96,10 @@ interface KeepChangesConfig {
88
96
  to: string;
89
97
  dry: boolean;
90
98
  commit: boolean;
99
+ tag: boolean;
91
100
  release: boolean;
101
+ bump: boolean;
102
+ changelog: boolean;
92
103
  author: string;
93
104
  tagPrefix: string;
94
105
  draft: boolean;
@@ -101,7 +112,10 @@ declare const defaultConfig: {
101
112
  to: string;
102
113
  dry: false;
103
114
  commit: false;
115
+ tag: false;
104
116
  release: false;
117
+ bump: true;
118
+ changelog: true;
105
119
  author: string;
106
120
  tagPrefix: string;
107
121
  draft: false;
@@ -154,4 +168,4 @@ declare function writeChangelog(path: string, changelog: string): Promise<void>;
154
168
  declare function hasRelease(changelog: string, version: string): boolean;
155
169
  declare function insertRelease(changelog: string, release: string): string;
156
170
  //#endregion
157
- export { type ChangelogConfig, type ChangelogConfigOverrides, type ChangelogMessages, type ChangelogSectionConfig, type Commit, type GenerateChangelogOptions, type GeneratedChangelog, type KeepChangesConfig, type ManualReleaseAction, type RawCommit, type Repository, type RepositoryAuthor, type RepositoryCommit, type RepositoryProvider, type RepositoryRelease, type RepositoryReleaseResult, defaultConfig, generateChangelog, hasRelease, insertRelease, parseCommit, parseCommits, readChangelog, resolveChangelogConfig, writeChangelog };
171
+ export { type ChangelogConfig, type ChangelogConfigOverrides, type ChangelogMessages, type ChangelogSectionConfig, type Commit, type GenerateChangelogOptions, type GeneratedChangelog, type KeepChangesConfig, type ManualReleaseAction, type RawCommit, type Repository, type RepositoryAuthor, type RepositoryCommit, type RepositoryProvider, type RepositoryRelease, type RepositoryReleaseAsset, type RepositoryReleaseResult, defaultConfig, generateChangelog, hasRelease, insertRelease, parseCommit, parseCommits, readChangelog, resolveChangelogConfig, writeChangelog };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as insertRelease, c as defaultConfig, i as hasRelease, l as resolveChangelogConfig, n as parseCommits, o as readChangelog, r as generateChangelog, s as writeChangelog, t as parseCommit } from "./commit-DBopyhs1.mjs";
1
+ import { a as insertRelease, c as defaultConfig, i as hasRelease, l as resolveChangelogConfig, n as parseCommits, o as readChangelog, r as generateChangelog, s as writeChangelog, t as parseCommit } from "./commit-DswG6QDH.mjs";
2
2
  export { defaultConfig, generateChangelog, hasRelease, insertRelease, parseCommit, parseCommits, readChangelog, resolveChangelogConfig, writeChangelog };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "keepchanges",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "description": "Generate and maintain CHANGELOG.md from Conventional Commits.",
6
6
  "author": "edram",
7
7
  "license": "MIT",
@@ -36,6 +36,7 @@
36
36
  "ansis": "^4.3.1",
37
37
  "cac": "^7.0.0",
38
38
  "tinyexec": "^1.2.4",
39
+ "tinyglobby": "^0.2.17",
39
40
  "verkit": "^0.3.0"
40
41
  },
41
42
  "devDependencies": {