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 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 and tag format:
5
+ Default version format:
6
6
 
7
7
  ```text
8
8
  YY.MMDD
9
9
  ```
10
10
 
11
- Example:
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. Prints the git commands needed to commit, tag, and push the reviewed release.
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 only. Review `CHANGELOG.md`, then run the printed git commands when the release notes are correct.
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 a `v`-prefixed git tag while keeping `package.json` unprefixed:
71
+ Create an unprefixed git tag while keeping the default package version unchanged:
64
72
 
65
73
  ```bash
66
- npx calver-bump --tag-prefix v
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` does not commit or tag by default; it prints the follow-up `git add`, `git commit`, `git tag`, and `git push --follow-tags` commands.
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
- - The working tree must be clean before creating a real release.
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.
@@ -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 tag = push || (flag(args, '--tag') ?? config.tag ?? false);
79
- const commit = tag || (flag(args, '--commit') ?? config.commit ?? false);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calver-bump",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Release CLI for internal applications using readable CalVer versions.",
5
5
  "repository": {
6
6
  "type": "git",
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.map((commit) => requestForCommit(commit, requestUrlBuilder)).filter(Boolean));
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 suffix = commit.request
137
- ? ` (${formatRequestLink(commit.request)})`
138
- : commit.url ? ` ([${shortHash}](${commit.url}))` : ` (${shortHash})`;
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.url ? `[${request.label}](${request.url})` : request.label;
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 = /(?:^|\s)(?:See merge request\s+\S+!|!)(?<number>\d+)(?=\D|$)/i.exec(message);
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:\/\/(?<host>[^/]+)\/(?<repo>.+?)(?:\.git)?$/.exec(remote);
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 { assertCleanWorktree, assertTagAvailable, currentBranch, git, gitLines } from './git.js';
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 ? `${options.tagPrefix}${version}` : version,
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 ? `${options.tagPrefix}${version}` : version)
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