calver-bump 1.0.0 → 1.0.1
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 +17 -8
- package/bin/calver-bump.js +8 -5
- package/package.json +1 -1
- package/src/changelog.js +17 -7
- package/src/index.js +5 -6
package/README.md
CHANGED
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Release CLI for applications and internal tools that use readable CalVer versions.
|
|
4
4
|
|
|
5
|
-
Default version
|
|
5
|
+
Default version format:
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
8
|
YY.MMDD
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Default git tag format:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
vYY.MMDD
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Example package version and git tag:
|
|
12
18
|
|
|
13
19
|
```text
|
|
14
20
|
26.0529
|
|
21
|
+
v26.0529
|
|
15
22
|
```
|
|
16
23
|
|
|
17
24
|
## What it does
|
|
@@ -20,7 +27,8 @@ Example:
|
|
|
20
27
|
2. Updates `package-lock.json` or `npm-shrinkwrap.json` when present.
|
|
21
28
|
3. Warns when `pnpm-lock.yaml` or `yarn.lock` is present, because those lockfiles do not store the root package version consistently.
|
|
22
29
|
4. Creates or prepends a `CHANGELOG.md` entry from conventional commits since the nearest reachable tag.
|
|
23
|
-
5.
|
|
30
|
+
5. Creates a release commit.
|
|
31
|
+
6. Creates an annotated git tag.
|
|
24
32
|
|
|
25
33
|
## Usage
|
|
26
34
|
|
|
@@ -28,7 +36,7 @@ Example:
|
|
|
28
36
|
npx calver-bump
|
|
29
37
|
```
|
|
30
38
|
|
|
31
|
-
By default, `calver-bump` updates files
|
|
39
|
+
By default, `calver-bump` updates release files, creates a release commit, and creates an annotated `v`-prefixed git tag.
|
|
32
40
|
|
|
33
41
|
Preview the planned release without writing files:
|
|
34
42
|
|
|
@@ -60,10 +68,10 @@ Use long CalVer instead:
|
|
|
60
68
|
npx calver-bump --format long
|
|
61
69
|
```
|
|
62
70
|
|
|
63
|
-
Create
|
|
71
|
+
Create an unprefixed git tag while keeping the default package version unchanged:
|
|
64
72
|
|
|
65
73
|
```bash
|
|
66
|
-
npx calver-bump --tag-prefix
|
|
74
|
+
npx calver-bump --tag-prefix ""
|
|
67
75
|
```
|
|
68
76
|
|
|
69
77
|
Push the release commit and annotated tag:
|
|
@@ -165,11 +173,12 @@ The package includes a manual GitHub Actions publish workflow. Run the `Publish`
|
|
|
165
173
|
- Changelog entries fall back to commit hash links for GitHub and GitLab-style remotes when no pull/merge request reference is found.
|
|
166
174
|
- Release entries include a `Full Changelog` section with a deduped list of pull/merge requests found in the release range, including the local commit title when available.
|
|
167
175
|
- Later releases prepend only commits since the previous nearest reachable tag.
|
|
168
|
-
- Plain `calver-bump`
|
|
176
|
+
- Plain `calver-bump` creates the release commit and annotated tag by default.
|
|
169
177
|
- Use `--commit` to create only the release commit.
|
|
170
178
|
- Use `--tag` to create the release commit and annotated tag.
|
|
179
|
+
- Use `--skip-commit` to update files only without creating a release commit or tag.
|
|
171
180
|
- Use `--push` to create the release commit, create the annotated tag, and push both.
|
|
172
181
|
- Release tags are annotated so `git push --follow-tags <remote> <branch>` pushes them.
|
|
173
|
-
-
|
|
182
|
+
- Unrelated dirty working-tree files do not block a release; only release files are staged.
|
|
174
183
|
- Existing release tags are rejected before files are written.
|
|
175
184
|
- If tag creation fails after the release commit, the CLI undoes its own commit and leaves the file changes in the working tree for inspection or recovery.
|
package/bin/calver-bump.js
CHANGED
|
@@ -75,12 +75,15 @@ async function releaseOptions(args) {
|
|
|
75
75
|
const changelogOnly = flag(args, '--changelog-only') ?? config.changelogOnly ?? false;
|
|
76
76
|
const skipCommit = flag(args, '--skip-commit') ?? config.skipCommit ?? false;
|
|
77
77
|
const push = flag(args, '--push') ?? config.push ?? false;
|
|
78
|
-
const
|
|
79
|
-
const
|
|
78
|
+
const commitFlag = flag(args, '--commit');
|
|
79
|
+
const tagFlag = flag(args, '--tag');
|
|
80
|
+
const writesOnly = skipCommit || versionOnly || changelogOnly;
|
|
81
|
+
const tag = !writesOnly && (push || (tagFlag ?? config.tag ?? (commitFlag || config.commit ? false : true)));
|
|
82
|
+
const commit = tag || (!writesOnly && (commitFlag ?? config.commit ?? false));
|
|
80
83
|
if (versionOnly && changelogOnly) {
|
|
81
84
|
throw new Error('--version-only cannot be combined with --changelog-only.');
|
|
82
85
|
}
|
|
83
|
-
if (skipCommit && (commit || tag || push)) {
|
|
86
|
+
if (skipCommit && (commitFlag || tagFlag || config.commit || config.tag || push)) {
|
|
84
87
|
throw new Error('--skip-commit cannot be combined with --commit, --tag, or --push.');
|
|
85
88
|
}
|
|
86
89
|
return {
|
|
@@ -96,7 +99,7 @@ async function releaseOptions(args) {
|
|
|
96
99
|
from: value(args, '--from') ?? config.from,
|
|
97
100
|
format: value(args, '--format') ?? config.format ?? 'short',
|
|
98
101
|
remote: value(args, '--remote') ?? config.remote ?? 'origin',
|
|
99
|
-
tagPrefix: value(args, '--tag-prefix') ?? config.tagPrefix ?? '',
|
|
102
|
+
tagPrefix: value(args, '--tag-prefix') ?? config.tagPrefix ?? 'v',
|
|
100
103
|
types: parseTypes(value(args, '--types')) ?? config.types,
|
|
101
104
|
changelogSections: config.changelogSections,
|
|
102
105
|
};
|
|
@@ -204,7 +207,7 @@ function helpText() {
|
|
|
204
207
|
Options:
|
|
205
208
|
--dry-run Preview the release without writing files.
|
|
206
209
|
--format <name> Version format: short, compact, or long.
|
|
207
|
-
--tag-prefix <prefix> Prefix the git tag without changing package.json.
|
|
210
|
+
--tag-prefix <prefix> Prefix the git tag without changing package.json (default: v).
|
|
208
211
|
--types <list> Comma-separated conventional commit types to include.
|
|
209
212
|
--from <tag> Use an explicit changelog base tag.
|
|
210
213
|
--no-fetch Use local tags only; do not fetch remote tags.
|
package/package.json
CHANGED
package/src/changelog.js
CHANGED
|
@@ -21,6 +21,7 @@ export async function releaseNotes(cwd, options = {}) {
|
|
|
21
21
|
const conventionalCommits = dedupeConventionalChanges(commits
|
|
22
22
|
.map((commit) => ({ ...commit, subject: conventionalSubjectForCommit(commit) ?? commit.subject }))
|
|
23
23
|
.filter((commit) => isConventionalCommit(commit.subject))
|
|
24
|
+
.filter((commit) => !isReleaseCommit(commit.subject))
|
|
24
25
|
.filter((commit) => allowedTypes.includes(conventionalType(commit.subject)))
|
|
25
26
|
.filter((commit) => !isCommitInChangelog(commit, options.existingChangelog ?? ''))
|
|
26
27
|
.map((commit) => ({
|
|
@@ -28,7 +29,10 @@ export async function releaseNotes(cwd, options = {}) {
|
|
|
28
29
|
request: requestForCommit(commit, requestUrlBuilder),
|
|
29
30
|
url: commitUrlBuilder ? commitUrlBuilder(commit.hash) : null,
|
|
30
31
|
})));
|
|
31
|
-
const requests = uniqueRequests(commits
|
|
32
|
+
const requests = uniqueRequests(commits
|
|
33
|
+
.filter((commit) => !isReleaseCommit(conventionalSubjectForCommit(commit) ?? commit.subject))
|
|
34
|
+
.map((commit) => requestForCommit(commit, requestUrlBuilder))
|
|
35
|
+
.filter(Boolean));
|
|
32
36
|
return {
|
|
33
37
|
previousTag: latestTag,
|
|
34
38
|
range: latestTag ? `${latestTag}..HEAD` : 'HEAD',
|
|
@@ -102,6 +106,10 @@ function isConventionalCommit(subject) {
|
|
|
102
106
|
return /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?: .+/.test(subject);
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
function isReleaseCommit(subject) {
|
|
110
|
+
return /^chore\(release\):\s.+/.test(subject);
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
function conventionalSubjectForCommit(commit) {
|
|
106
114
|
return commitLines(commit).find((line) => isConventionalCommit(line)) ?? null;
|
|
107
115
|
}
|
|
@@ -133,14 +141,16 @@ function conventionalType(subject) {
|
|
|
133
141
|
|
|
134
142
|
function formatCommitEntry(commit) {
|
|
135
143
|
const shortHash = commit.hash.slice(0, 7);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
const links = [
|
|
145
|
+
shortHash,
|
|
146
|
+
commit.request ? formatRequestLink(commit.request) : null,
|
|
147
|
+
].filter(Boolean);
|
|
148
|
+
const suffix = ` (${links.join(', ')})`;
|
|
139
149
|
return `${commit.subject}${suffix}`;
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
function formatRequestLink(request) {
|
|
143
|
-
return request.
|
|
153
|
+
return request.label;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
function formatRequestEntry(request) {
|
|
@@ -221,7 +231,7 @@ function requestTitleForCommit(commit) {
|
|
|
221
231
|
}
|
|
222
232
|
|
|
223
233
|
function parseRequestReference(message) {
|
|
224
|
-
const gitlabMerge = /(
|
|
234
|
+
const gitlabMerge = /(?:^|[\s(])(?:See merge request\s+\S+!|!)(?<number>\d+)(?=\D|$)/i.exec(message);
|
|
225
235
|
if (gitlabMerge) {
|
|
226
236
|
return { provider: 'gitlab', number: gitlabMerge.groups.number, label: `!${gitlabMerge.groups.number}` };
|
|
227
237
|
}
|
|
@@ -258,7 +268,7 @@ function parseGitRemote(remote) {
|
|
|
258
268
|
return sshMatch.groups;
|
|
259
269
|
}
|
|
260
270
|
|
|
261
|
-
const httpsMatch = /^https
|
|
271
|
+
const httpsMatch = /^https?:\/\/(?<host>[^/]+)\/(?<repo>.+?)(?:\.git)?$/.exec(remote);
|
|
262
272
|
if (httpsMatch) {
|
|
263
273
|
return httpsMatch.groups;
|
|
264
274
|
}
|
package/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
updatePackageVersion,
|
|
9
9
|
writeChangelog,
|
|
10
10
|
} from './files.js';
|
|
11
|
-
import {
|
|
11
|
+
import { assertTagAvailable, currentBranch, git, gitLines } from './git.js';
|
|
12
12
|
|
|
13
13
|
export async function planRelease(options = {}) {
|
|
14
14
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -18,7 +18,7 @@ export async function planRelease(options = {}) {
|
|
|
18
18
|
existingTags,
|
|
19
19
|
format: options.format ?? 'short',
|
|
20
20
|
});
|
|
21
|
-
const tag = `${options.tagPrefix ?? ''}${version}`;
|
|
21
|
+
const tag = `${options.tagPrefix ?? 'v'}${version}`;
|
|
22
22
|
const notes = options.versionOnly
|
|
23
23
|
? null
|
|
24
24
|
: await releaseNotes(cwd, { ...options, tag, existingChangelog: await readChangelog(cwd) });
|
|
@@ -59,7 +59,6 @@ export async function runRelease(options = {}) {
|
|
|
59
59
|
return plan;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
await assertCleanWorktree(cwd);
|
|
63
62
|
if (createsTag(options)) {
|
|
64
63
|
await assertTagAvailable(cwd, plan.tag);
|
|
65
64
|
}
|
|
@@ -124,16 +123,16 @@ async function prependChangelog(cwd, version, options = {}) {
|
|
|
124
123
|
const notes = await releaseNotes(cwd, {
|
|
125
124
|
...options,
|
|
126
125
|
existingChangelog: existing,
|
|
127
|
-
tag: `${options.tagPrefix ?? ''}${version}`,
|
|
126
|
+
tag: `${options.tagPrefix ?? 'v'}${version}`,
|
|
128
127
|
});
|
|
129
128
|
const heading = formatReleaseHeading({
|
|
130
129
|
version,
|
|
131
130
|
previousTag: notes.previousTag,
|
|
132
|
-
tag: options.tagPrefix
|
|
131
|
+
tag: `${options.tagPrefix ?? 'v'}${version}`,
|
|
133
132
|
compareUrlBuilder: notes.compareUrlBuilder,
|
|
134
133
|
});
|
|
135
134
|
const compareUrl = notes.previousTag && notes.compareUrlBuilder
|
|
136
|
-
? notes.compareUrlBuilder(notes.previousTag, options.tagPrefix
|
|
135
|
+
? notes.compareUrlBuilder(notes.previousTag, `${options.tagPrefix ?? 'v'}${version}`)
|
|
137
136
|
: null;
|
|
138
137
|
const entry = `${heading}\n\n${formatReleaseNotes(notes.changes, options.changelogSections)}${formatFullChangelog(notes.requests, compareUrl)}\n`;
|
|
139
138
|
|