Skip to content

Commit 393ce7e

Browse files
committed
feat(release): add CalVer plugin for YYYY.M.D[-N] versioning
Adds a local semantic-release plugin (release/calver.cjs) that overrides nextRelease.version in the prepare lifecycle with a calendar-based version. Checks existing git tags for today to append an iteration suffix when multiple releases happen on the same day (e.g. 2026.3.17-1, 2026.3.17-2). Uses UTC date for timezone-consistent CI behavior. Plugin runs before @semantic-release/npm and @semantic-release/git so all downstream plugins use the CalVer version string. Commit-analyzer still gates whether a release fires (fix:/feat:/perf:/BREAKING CHANGE only).
1 parent fc22d5d commit 393ce7e

File tree

2 files changed

+39
-0
lines changed

2 files changed

+39
-0
lines changed

.releaserc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"plugins": [
55
"@semantic-release/commit-analyzer",
66
"@semantic-release/release-notes-generator",
7+
"./release/calver.cjs",
78
[
89
"@semantic-release/changelog",
910
{

release/calver.cjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use strict";
2+
// CalVer semantic-release plugin — YYYY.M.D[-iteration]
3+
// Overrides nextRelease.version in the prepare lifecycle before
4+
// @semantic-release/npm and @semantic-release/git consume it.
5+
const { execSync } = require("child_process");
6+
7+
module.exports = {
8+
prepare(pluginConfig, context) {
9+
const { nextRelease, logger } = context;
10+
11+
const now = new Date();
12+
const base = `${now.getUTCFullYear()}.${now.getUTCMonth() + 1}.${now.getUTCDate()}`;
13+
14+
// Find any tags already published today
15+
let existing = [];
16+
try {
17+
const out = execSync(`git tag -l "${base}" "${base}-*"`, { encoding: "utf8" });
18+
existing = out.trim().split("\n").filter(Boolean);
19+
} catch {
20+
existing = [];
21+
}
22+
23+
let version;
24+
if (existing.length === 0) {
25+
version = base;
26+
} else {
27+
const iters = existing.map((t) => {
28+
const m = t.match(/-(\d+)$/);
29+
return m ? parseInt(m[1], 10) : 0;
30+
});
31+
version = `${base}-${Math.max(...iters) + 1}`;
32+
}
33+
34+
logger.log("CalVer: %s → %s", nextRelease.version, version);
35+
nextRelease.version = version;
36+
nextRelease.gitTag = `v${version}`;
37+
},
38+
};

0 commit comments

Comments
 (0)