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 +151 -95
- package/README.zh-CN.md +143 -86
- package/dist/cli.mjs +115 -24
- package/dist/{commit-DBopyhs1.mjs → commit-DswG6QDH.mjs} +3 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# keepchanges
|
|
2
2
|
|
|
3
|
-
Generate and maintain
|
|
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
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
47
|
+
## Release workflows
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
68
|
+
The second command reuses the existing tag. It does not move the tag or commit the artifacts.
|
|
49
69
|
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
release API:
|
|
162
|
+
## Command reference
|
|
124
163
|
|
|
125
|
-
```
|
|
126
|
-
npx keepchanges
|
|
164
|
+
```text
|
|
165
|
+
npx keepchanges <version> [options]
|
|
127
166
|
```
|
|
128
167
|
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
and
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
-
|
|
34
|
+
版本号也可以包含 `v` 前缀:
|
|
33
35
|
|
|
34
36
|
```bash
|
|
35
37
|
npx keepchanges v1.1.0
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
`package.json#version` 更新为 `1.1.0
|
|
40
|
+
默认会写入 `CHANGELOG.md`。如果当前项目是 npm 包,还会将
|
|
41
|
+
`package.json#version` 更新为 `1.1.0`,但不会创建 Git commit。
|
|
40
42
|
|
|
41
|
-
##
|
|
43
|
+
## 发布工作流
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
重新生成历史版本:
|
|
116
152
|
|
|
117
153
|
```bash
|
|
118
154
|
npx keepchanges 1.0.0 --to 1.0.0 --dry
|
|
119
155
|
```
|
|
120
156
|
|
|
121
|
-
|
|
157
|
+
## 命令参考
|
|
122
158
|
|
|
123
|
-
```
|
|
124
|
-
npx keepchanges
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
解析 commit 主作者并发布 Release。
|
|
213
|
+
GitHub 和 Gitea 均支持作者解析与 Release 发布。Gitea 使用 `GITEA_TOKEN`,
|
|
214
|
+
但目前不支持附件上传。
|
|
145
215
|
|
|
146
|
-
|
|
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
|
-
|
|
155
|
-
|
|
218
|
+
版本 tag 默认使用 `v` 前缀。配置的前缀会应用于 tag 创建和查找、版本形式的
|
|
219
|
+
`--from` 和 `--to`、比较链接及 Release 名称。分支名、commit hash 和 `HEAD`
|
|
220
|
+
等非版本 ref 保持不变。不同前缀的 tag 序列彼此独立。
|
|
156
221
|
|
|
157
|
-
`--
|
|
158
|
-
|
|
222
|
+
显式传入 `--to` 而不传 `--from` 时,会相对于该目标查找前一个匹配的 tag,
|
|
223
|
+
而不是使用当前 `HEAD` 的最近 tag。与 `--commit` 一起使用时,`--to` 必须指向
|
|
224
|
+
当前 `HEAD`。
|
|
159
225
|
|
|
160
|
-
|
|
226
|
+
### Commit 和发布安全
|
|
161
227
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
231
|
+
`--release` 会复用已有 tag,重新生成该版本的 Release notes,并创建或更新
|
|
232
|
+
Release。远程独有的 tag 会被拉取,本地独有的 tag 会被推送;本地和远程 tag
|
|
233
|
+
指向不同 commit 时会停止,不会强制覆盖或移动 tag。
|
|
176
234
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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`
|
|
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-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
432
|
+
if (createsTag && taggedCommit) {
|
|
400
433
|
if (!remoteTaggedCommit) await git(environment.cwd, "push", "origin", `refs/tags/${tag}`);
|
|
401
|
-
|
|
434
|
+
if (!options.release) return;
|
|
435
|
+
await publishRelease(repository, repositoryRelease, token, preview, environment, releaseAssets, "edit");
|
|
402
436
|
return;
|
|
403
437
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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,
|
|
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.
|
|
466
|
-
if (
|
|
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("--
|
|
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-
|
|
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.
|
|
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": {
|