UNPKG

gitversionjs

Version:

Auto-generates SemVer versions from Git tags and branches, GitFlow-style.

136 lines (135 loc) 4.93 kB
import { execSync } from "child_process"; function escapeRegExp(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function parseVersionFromTag(tag, prefix) { const cleaned = prefix ? tag.replace(new RegExp(`^${escapeRegExp(prefix)}`), "") : tag; const [maj, min, pat] = cleaned.split(".").map((n) => Number(n) || 0); return [maj, min, pat]; } // Supports: // release/2.2.0 → 2.2.0 // release/2.2 → 2.2.0 // release/2 → 2.0.0 // Same for hotfix/* (hotfix/1.2.3 etc.) function parseVersionFromBranch(branch) { const m = branch.match(/^(?:release|hotfix)\/(v?\d+)(?:\.(\d+))?(?:\.(\d+))?$/); if (!m) return null; const major = Number(m[1].replace(/^v/, "")) || 0; const minor = m[2] ? Number(m[2]) || 0 : 0; const patch = m[3] ? Number(m[3]) || 0 : 0; return [major, minor, patch]; } function sortTagsDesc(tags, prefix) { return [...tags].sort((a, b) => { const [Amaj, Amin, Apat] = parseVersionFromTag(a, prefix); const [Bmaj, Bmin, Bpat] = parseVersionFromTag(b, prefix); if (Amaj !== Bmaj) return Bmaj - Amaj; if (Amin !== Bmin) return Bmin - Amin; return Bpat - Apat; }); } function fmt(maj, min, pat, build) { return build !== undefined ? `${maj}.${min}.${pat}.${build}` : `${maj}.${min}.${pat}`; } function getCommitsSinceLastVersionChange(tags, currentBranch, tagPrefix, cwd = ".") { try { // Sort tags in descending order const sortedTags = sortTagsDesc(tags, tagPrefix); // Find the latest tag that changed the version let lastVersionTag = null; for (const tag of sortedTags) { const [tagMajor, tagMinor, tagPatch] = parseVersionFromTag(tag, tagPrefix); const [branchMajor, branchMinor, branchPatch] = parseVersionFromBranch(currentBranch) ?? [0, 1, 0]; // Check if the tag version differs from the branch version if (tagMajor !== branchMajor || tagMinor !== branchMinor || tagPatch !== branchPatch) { lastVersionTag = tag; break; } } // If no tag is found, use the first tag const tagToCompare = lastVersionTag || sortedTags[0]; // Fetch commits since the determined tag const result = execSync(`git log ${tagToCompare}..HEAD --pretty=format:"%h %s"`, { cwd, encoding: "utf-8" }); return result.split("\n").filter(Boolean); // Split into lines and remove empty entries } catch (error) { console.error("Error fetching commits:", error); return []; } } export function calculateVersion(gitInfo, config) { const { tags, branchType, currentBranch } = gitInfo; const { tagPrefix = "v" } = config; // Base: branch-encoded version > latest tag > default const branchVer = parseVersionFromBranch(currentBranch); const latestTag = sortTagsDesc(tags, tagPrefix)[0] ?? null; const tagged = latestTag ? parseVersionFromTag(latestTag, tagPrefix) : null; const [baseMajor, baseMinor, basePatch] = branchVer ?? tagged ?? [0, 1, 0]; // Get commits since the last tag const commits = latestTag ? getCommitsSinceLastVersionChange(tags, currentBranch, tagPrefix) : []; const commitCount = commits.length; // Count the number of commits // Output (these are returned) let outMajor = baseMajor; let outMinor = baseMinor; let outPatch = basePatch; let version = ""; switch (branchType) { case "main": { version = fmt(outMajor, outMinor, outPatch, commitCount); break; } case "develop": { outMinor = outMinor + 1; outPatch = 0; version = `${outMajor}.${outMinor}.${outPatch}.${commitCount}`; break; } case "feature": { outMinor = outMinor + 1; outPatch = 0; version = `${outMajor}.${outMinor}.${outPatch}.${commitCount}`; break; } case "release": { if (!branchVer) { outMinor; outPatch = 0; } version = fmt(outMajor, outMinor, outPatch, commitCount); break; } case "hotfix": { if (!branchVer) { outPatch = outPatch + 1; } version = fmt(outMajor, outMinor, outPatch, commitCount); break; } default: { version = fmt(outMajor, outMinor, outPatch, commitCount); } } return { version, major: outMajor, minor: outMinor, patch: outPatch, branch: currentBranch, tag: latestTag || null, branchType, timestamp: new Date().toISOString(), commits, // Include commits in the returned object }; }