UNPKG

package-versioner

Version:

A lightweight yet powerful CLI tool for automated semantic versioning based on Git history and conventional commits.

1,429 lines (1,375 loc) 95.6 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { run: () => run }); module.exports = __toCommonJS(index_exports); var fs10 = __toESM(require("fs"), 1); var import_node_path8 = __toESM(require("path"), 1); var import_commander = require("commander"); // src/changelog/changelogRegenerator.ts var import_node_child_process2 = require("child_process"); var import_node_fs = __toESM(require("fs"), 1); var import_node_path = __toESM(require("path"), 1); // src/utils/logging.ts var import_chalk = __toESM(require("chalk"), 1); var import_figlet = __toESM(require("figlet"), 1); // src/utils/jsonOutput.ts var _jsonOutputMode = false; var _jsonData = { dryRun: false, updates: [], tags: [] }; function enableJsonOutput(dryRun = false) { _jsonOutputMode = true; _jsonData.dryRun = dryRun; _jsonData.updates = []; _jsonData.tags = []; _jsonData.commitMessage = void 0; } function isJsonOutputMode() { return _jsonOutputMode; } function addPackageUpdate(packageName, newVersion, filePath) { if (!_jsonOutputMode) return; _jsonData.updates.push({ packageName, newVersion, filePath }); } function addTag(tag) { if (!_jsonOutputMode) return; _jsonData.tags.push(tag); } function setCommitMessage(message) { if (!_jsonOutputMode) return; _jsonData.commitMessage = message; } function printJsonOutput() { if (_jsonOutputMode) { console.log(JSON.stringify(_jsonData, null, 2)); } } // src/utils/logging.ts function log(message, level = "info") { const showDebug = process.env.DEBUG === "true" || process.env.DEBUG === "1"; if (level === "debug" && !showDebug) { return; } let chalkFn; switch (level) { case "success": chalkFn = import_chalk.default.green; break; case "warning": chalkFn = import_chalk.default.yellow; break; case "error": chalkFn = import_chalk.default.red; break; case "debug": chalkFn = import_chalk.default.gray; break; default: chalkFn = import_chalk.default.blue; } if (isJsonOutputMode()) { if (level === "error") { chalkFn(message); console.error(message); } return; } const formattedMessage = level === "debug" ? `[DEBUG] ${message}` : message; if (level === "error") { console.error(chalkFn(formattedMessage)); } else { console.log(chalkFn(formattedMessage)); } } // src/changelog/commitParser.ts var import_node_child_process = require("child_process"); var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/; var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/; function extractChangelogEntriesFromCommits(projectDir, revisionRange) { try { const command = `git log ${revisionRange} --pretty=format:"%B---COMMIT_DELIMITER---" --no-merges`; const output = (0, import_node_child_process.execSync)(command, { cwd: projectDir, encoding: "utf8" }); const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== ""); return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes("ambiguous argument") && errorMessage.includes("unknown revision")) { const tagName = revisionRange.split("..")[0] || revisionRange; if (tagName.startsWith("v") && !tagName.includes("@")) { log( `Error: Tag "${tagName}" not found. If you're using package-specific tags (like "package-name@v1.0.0"), you may need to configure "tagTemplate" in your version.config.json to use: \${packageName}@\${prefix}\${version}`, "error" ); } else { log( `Error: Tag or revision "${tagName}" not found in the repository. Please check if this tag exists or if you need to fetch it from the remote.`, "error" ); } } else { log(`Error extracting commits: ${errorMessage}`, "error"); } return []; } } function parseCommitMessage(message) { const trimmedMessage = message.trim(); const match = trimmedMessage.match(CONVENTIONAL_COMMIT_REGEX); if (match) { const [, type, scope, breakingMark, subject, body = ""] = match; const breakingFromMark = breakingMark === "!"; const breakingChangeMatch = body.match(BREAKING_CHANGE_REGEX); const hasBreakingChange = breakingFromMark || breakingChangeMatch !== null; const changelogType = mapCommitTypeToChangelogType(type); if (!changelogType) { return null; } const issueIds = extractIssueIds(body); let description = subject; if (hasBreakingChange) { description = `**BREAKING** ${description}`; } return { type: changelogType, description, scope: scope || void 0, issueIds: issueIds.length > 0 ? issueIds : void 0, originalType: type // Store original type for custom formatting }; } if (!trimmedMessage.startsWith("Merge") && !trimmedMessage.match(/^v?\d+\.\d+\.\d+/)) { const firstLine = trimmedMessage.split("\n")[0].trim(); return { type: "changed", description: firstLine }; } return null; } function mapCommitTypeToChangelogType(type) { switch (type) { case "feat": return "added"; case "fix": return "fixed"; case "docs": case "style": case "refactor": case "perf": case "build": case "ci": return "changed"; case "revert": return "removed"; case "chore": return "changed"; case "test": return null; default: return "changed"; } } function extractIssueIds(body) { const issueRegex = /(?:fix|fixes|close|closes|resolve|resolves)\s+#(\d+)/gi; const issueIds = []; let match = issueRegex.exec(body); while (match !== null) { issueIds.push(`#${match[1]}`); match = issueRegex.exec(body); } return issueIds; } // src/changelog/formatters.ts function formatChangelogEntries(format, version, date, entries, packageName, repoUrl) { const formattingEntries = entries.map((entry) => { const hasBreaking = entry.description.includes("**BREAKING**"); return { ...entry, breaking: hasBreaking, // Clean up the description to remove the **BREAKING** prefix since we'll handle it in formatting description: hasBreaking ? entry.description.replace("**BREAKING** ", "") : entry.description }; }); return format === "keep-a-changelog" ? formatKeepAChangelogEntries(version, date, formattingEntries, repoUrl) : formatAngularEntries(version, date, formattingEntries, packageName); } function formatKeepAChangelogEntries(version, date, entries, repoUrl) { const added = []; const changed = []; const deprecated = []; const removed = []; const fixed = []; const security = []; for (const entry of entries) { let entryText; if (entry.breaking) { entryText = entry.scope ? `- **BREAKING** **${entry.scope}**: ${entry.description}` : `- **BREAKING** ${entry.description}`; } else { entryText = entry.scope ? `- **${entry.scope}**: ${entry.description}` : `- ${entry.description}`; } const entryType = entry.originalType || entry.type; switch (entryType) { case "feat": added.push(entryText); break; case "fix": fixed.push(entryText); break; case "docs": case "style": case "refactor": case "perf": case "build": case "ci": changed.push(entryText); break; case "test": break; case "chore": if (entry.description.toLowerCase().includes("deprecat")) { deprecated.push(entryText); } else { changed.push(entryText); } break; // Keep-a-changelog standard types case "added": added.push(entryText); break; case "changed": changed.push(entryText); break; case "deprecated": deprecated.push(entryText); break; case "removed": removed.push(entryText); break; case "fixed": fixed.push(entryText); break; case "security": security.push(entryText); break; default: changed.push(entryText); } } let content = `## [${version}] - ${date} `; if (added.length > 0) { content += `### Added ${added.join("\n")} `; } if (changed.length > 0) { content += `### Changed ${changed.join("\n")} `; } if (deprecated.length > 0) { content += `### Deprecated ${deprecated.join("\n")} `; } if (removed.length > 0) { content += `### Removed ${removed.join("\n")} `; } if (fixed.length > 0) { content += `### Fixed ${fixed.join("\n")} `; } if (security.length > 0) { content += `### Security ${security.join("\n")} `; } if (repoUrl) { content += `[${version}]: ${repoUrl}/compare/v${version}...HEAD `; } return content.trim(); } function formatAngularEntries(version, date, entries, packageName) { const features = []; const bugfixes = []; const performance = []; const breaking = []; for (const entry of entries) { if (entry.breaking) { breaking.push(entry); } const entryType = entry.originalType || entry.type; switch (entryType) { case "feat": case "added": features.push(entry); break; case "fix": case "fixed": bugfixes.push(entry); break; case "perf": performance.push(entry); break; } } let content = `## [${version}]${packageName ? ` (${packageName})` : ""} (${date}) `; if (features.length > 0) { content += "### Features\n\n"; content += formatAngularTypeEntries(features); content += "\n"; } if (bugfixes.length > 0) { content += "### Bug Fixes\n\n"; content += formatAngularTypeEntries(bugfixes); content += "\n"; } if (performance.length > 0) { content += "### Performance Improvements\n\n"; content += formatAngularTypeEntries(performance); content += "\n"; } if (breaking.length > 0) { content += "### BREAKING CHANGES\n\n"; content += formatAngularTypeEntries(breaking); content += "\n"; } return content.trim(); } function formatAngularTypeEntries(entries) { var _a; const entriesByScope = /* @__PURE__ */ new Map(); for (const entry of entries) { const scope = entry.scope || ""; if (!entriesByScope.has(scope)) { entriesByScope.set(scope, []); } (_a = entriesByScope.get(scope)) == null ? void 0 : _a.push(entry); } const result = []; for (const [scope, scopeEntries] of Object.entries(groupEntriesByScope(entries))) { if (scope !== "undefined" && scope !== "") { result.push(`* **${scope}:**`); for (const entry of scopeEntries) { const description = entry.description.replace("**BREAKING** ", ""); result.push(` * ${description}`); } } else { for (const entry of scopeEntries) { const description = entry.description.replace("**BREAKING** ", ""); result.push(`* ${description}`); } } } return result.join("\n"); } function groupEntriesByScope(entries) { const result = {}; for (const entry of entries) { const scope = entry.scope || ""; if (!result[scope]) { result[scope] = []; } result[scope].push(entry); } return result; } // src/changelog/templates.ts function getDefaultTemplate(format) { return format === "keep-a-changelog" ? getKeepAChangelogTemplate() : getAngularTemplate(); } function getKeepAChangelogTemplate() { return `# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). `; } function getAngularTemplate() { return `# Changelog `; } // src/changelog/changelogRegenerator.ts function getAllVersionTags(since, versionPrefix = "v") { try { const command = `git tag --list "${versionPrefix}*" --sort=creatordate`; const tagOutput = (0, import_node_child_process2.execSync)(command, { encoding: "utf8" }).trim(); if (!tagOutput) { return []; } const allTags = tagOutput.split("\n").filter((tag) => !!tag); let filteredTags = allTags; if (since) { const sinceIndex = allTags.findIndex((tag) => tag === since); if (sinceIndex >= 0) { filteredTags = allTags.slice(sinceIndex); } else { log( `Warning: --since tag "${since}" not found in git history, including all tags`, "warning" ); } } return filteredTags.map((tag) => { try { const date = (0, import_node_child_process2.execSync)(`git log -1 --format=%ad --date=short ${tag}`, { encoding: "utf8" }).trim(); const version = tag.replace(new RegExp(`^${versionPrefix}`), ""); return { tag, version, date }; } catch (error) { log(`Failed to get date for tag ${tag}: ${error}`, "warning"); return { tag, version: tag.replace(new RegExp(`^${versionPrefix}`), ""), date: "Unknown" }; } }); } catch (error) { log(`Failed to get version tags: ${error}`, "error"); return []; } } async function regenerateChangelog(options) { const { format, since, projectDir } = options; const packageJsonPath = import_node_path.default.join(projectDir, "package.json"); let packageName = ""; let repoUrl = options.repoUrl; if (import_node_fs.default.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(import_node_fs.default.readFileSync(packageJsonPath, "utf8")); packageName = packageJson.name || ""; if (!repoUrl && packageJson.repository) { if (typeof packageJson.repository === "string") { repoUrl = packageJson.repository; } else if (packageJson.repository.url) { repoUrl = packageJson.repository.url; } if ((repoUrl == null ? void 0 : repoUrl.startsWith("git+")) && (repoUrl == null ? void 0 : repoUrl.endsWith(".git"))) { repoUrl = repoUrl.substring(4, repoUrl.length - 4); } } } catch (error) { log(`Failed to read package.json: ${error}`, "warning"); } } let versionPrefix = "v"; try { const allTags = (0, import_node_child_process2.execSync)("git tag --list", { encoding: "utf8" }).trim().split("\n"); const versionTag = allTags.find((tag) => /^[vV][0-9]/.test(tag)); if (versionTag) { versionPrefix = versionTag.charAt(0); } } catch { } let tags = getAllVersionTags(since, versionPrefix); if (!tags.length && since) { tags = getAllVersionTags(void 0, versionPrefix); } if (!tags.length) { throw new Error( 'No version tags found in git history. Make sure you have tags that start with the version prefix (usually "v").' ); } let changelogContent = getDefaultTemplate(format); log(`Found ${tags.length} version tags, generating changelog...`, "info"); const versions = []; for (let i = tags.length - 1; i >= 0; i--) { const currentTag = tags[i]; const previousTag = i > 0 ? tags[i - 1].tag : null; log(`Processing changes for ${currentTag.tag}...`, "info"); try { let tagRange; if (previousTag) { tagRange = `${previousTag}..${currentTag.tag}`; } else if (since && currentTag.tag === since) { try { const allTagsCmd = `git tag --list "${versionPrefix}*" --sort=creatordate`; const allTagsOutput = (0, import_node_child_process2.execSync)(allTagsCmd, { encoding: "utf8" }).trim(); const allTags = allTagsOutput.split("\n").filter((tag) => !!tag); const sinceIndex = allTags.findIndex((tag) => tag === since); const actualPreviousTag = sinceIndex > 0 ? allTags[sinceIndex - 1] : null; if (actualPreviousTag) { tagRange = `${actualPreviousTag}..${currentTag.tag}`; } else { tagRange = currentTag.tag; } } catch (error) { log(`Failed to find previous tag for ${currentTag.tag}: ${error}`, "warning"); tagRange = currentTag.tag; } } else { tagRange = currentTag.tag; } const entries = extractChangelogEntriesFromCommits(projectDir, tagRange); if (!entries.length) { log(`No changelog entries found for ${currentTag.tag}, adding placeholder entry`, "info"); entries.push({ type: "changed", description: `Release version ${currentTag.version}` }); } versions.unshift( formatChangelogEntries( format, currentTag.version, currentTag.date, entries, packageName, repoUrl ) ); } catch (error) { log(`Failed to process version ${currentTag.tag}: ${error}`, "error"); } } changelogContent += versions.join("\n\n"); return changelogContent; } async function writeChangelog(content, outputPath, dryRun) { if (dryRun) { log("--- Changelog Preview ---", "info"); console.log(content); log("--- End Preview ---", "info"); return; } try { import_node_fs.default.writeFileSync(outputPath, content, "utf8"); log(`Changelog successfully written to ${outputPath}`, "success"); } catch (error) { throw new Error( `Failed to write changelog: ${error instanceof Error ? error.message : String(error)}` ); } } // src/config.ts var fs2 = __toESM(require("fs"), 1); var import_node_process = require("process"); function loadConfig(configPath) { const localProcess = (0, import_node_process.cwd)(); const filePath = configPath || `${localProcess}/version.config.json`; return new Promise((resolve, reject) => { fs2.readFile(filePath, "utf-8", (err, data) => { if (err) { reject(new Error(`Could not locate the config file at ${filePath}: ${err.message}`)); return; } try { const config = JSON.parse(data); resolve(config); } catch (err2) { const errorMessage = err2 instanceof Error ? err2.message : String(err2); reject(new Error(`Failed to parse config file ${filePath}: ${errorMessage}`)); } }); }); } // src/core/versionEngine.ts var import_node_process5 = require("process"); var import_get_packages = require("@manypkg/get-packages"); // src/errors/gitError.ts var GitError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "GitError"; } }; function createGitError(code, details) { const messages = { ["NOT_GIT_REPO" /* NOT_GIT_REPO */]: "Not a git repository", ["GIT_PROCESS_ERROR" /* GIT_PROCESS_ERROR */]: "Failed to create new version", ["NO_FILES" /* NO_FILES */]: "No files specified for commit", ["NO_COMMIT_MESSAGE" /* NO_COMMIT_MESSAGE */]: "Commit message is required", ["GIT_ERROR" /* GIT_ERROR */]: "Git operation failed" }; const baseMessage = messages[code]; const fullMessage = details ? `${baseMessage}: ${details}` : baseMessage; return new GitError(fullMessage, code); } // src/errors/versionError.ts var VersionError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "VersionError"; } }; function createVersionError(code, details) { const messages = { ["CONFIG_REQUIRED" /* CONFIG_REQUIRED */]: "Configuration is required", ["PACKAGES_NOT_FOUND" /* PACKAGES_NOT_FOUND */]: "Failed to get packages information", ["WORKSPACE_ERROR" /* WORKSPACE_ERROR */]: "Failed to get workspace packages", ["INVALID_CONFIG" /* INVALID_CONFIG */]: "Invalid configuration", ["PACKAGE_NOT_FOUND" /* PACKAGE_NOT_FOUND */]: "Package not found", ["VERSION_CALCULATION_ERROR" /* VERSION_CALCULATION_ERROR */]: "Failed to calculate version" }; const baseMessage = messages[code]; const fullMessage = details ? `${baseMessage}: ${details}` : baseMessage; return new VersionError(fullMessage, code); } // src/utils/packageFiltering.ts var import_node_path2 = __toESM(require("path"), 1); var import_micromatch = __toESM(require("micromatch"), 1); function filterPackagesByConfig(packages, configTargets, workspaceRoot) { if (configTargets.length === 0) { log("No config targets specified, returning all packages", "debug"); return packages; } const matchedPackages = /* @__PURE__ */ new Set(); for (const target of configTargets) { const dirMatches = filterByDirectoryPattern(packages, target, workspaceRoot); const nameMatches = filterByPackageNamePattern(packages, target); dirMatches.forEach((pkg) => matchedPackages.add(pkg)); nameMatches.forEach((pkg) => matchedPackages.add(pkg)); } return Array.from(matchedPackages); } function filterByDirectoryPattern(packages, pattern, workspaceRoot) { if (pattern === "./" || pattern === ".") { return packages.filter((pkg) => pkg.dir === workspaceRoot); } const normalizedPattern = pattern.replace(/\\/g, "/"); return packages.filter((pkg) => { const relativePath = import_node_path2.default.relative(workspaceRoot, pkg.dir); const normalizedRelativePath = relativePath.replace(/\\/g, "/"); if (normalizedPattern === normalizedRelativePath) { return true; } try { return import_micromatch.default.isMatch(normalizedRelativePath, normalizedPattern, { dot: true, noglobstar: false, bash: true }); } catch (error) { log( `Invalid directory pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`, "warning" ); return false; } }); } function filterByPackageNamePattern(packages, pattern) { return packages.filter((pkg) => { var _a; if (!((_a = pkg.packageJson) == null ? void 0 : _a.name) || typeof pkg.packageJson.name !== "string") { return false; } return matchesPackageNamePattern(pkg.packageJson.name, pattern); }); } function matchesPackageNamePattern(packageName, pattern) { if (packageName === pattern) { return true; } if (pattern.startsWith("@") && pattern.endsWith("/*") && !pattern.includes("**")) { const scope = pattern.slice(0, -2); return packageName.startsWith(`${scope}/`); } try { return import_micromatch.default.isMatch(packageName, pattern, { dot: true, contains: false, noglobstar: false, bash: true }); } catch (error) { log( `Invalid package name pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`, "warning" ); return false; } } // src/core/versionStrategies.ts var import_node_child_process4 = require("child_process"); var import_node_fs7 = __toESM(require("fs"), 1); var path8 = __toESM(require("path"), 1); // src/changelog/changelogManager.ts var fs3 = __toESM(require("fs"), 1); var path3 = __toESM(require("path"), 1); function updateChangelog(packagePath, packageName, version, entries, repoUrl, format = "keep-a-changelog") { try { const changelogPath = path3.join(packagePath, "CHANGELOG.md"); let existingContent = ""; if (fs3.existsSync(changelogPath)) { existingContent = fs3.readFileSync(changelogPath, "utf8"); } const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]; const newVersionContent = formatChangelogEntries( format, version, today, entries, packageName, repoUrl ); let finalContent; if (existingContent) { if (format === "keep-a-changelog") { const headerEndIndex = existingContent.indexOf("\n## "); if (headerEndIndex > 0) { const beforeVersions = existingContent.substring(0, headerEndIndex); const afterVersions = existingContent.substring(headerEndIndex); finalContent = `${beforeVersions} ${newVersionContent} ${afterVersions}`; } else { finalContent = `${existingContent} ${newVersionContent} `; } } else { const headerEndIndex = existingContent.indexOf("\n## "); if (headerEndIndex > 0) { const beforeVersions = existingContent.substring(0, headerEndIndex); const afterVersions = existingContent.substring(headerEndIndex); finalContent = `${beforeVersions} ${newVersionContent} ${afterVersions}`; } else { finalContent = `${existingContent} ${newVersionContent} `; } } } else { if (format === "keep-a-changelog") { finalContent = `# Changelog All notable changes to ${packageName} will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ${newVersionContent} `; } else { finalContent = `# Changelog ${newVersionContent} `; } } log(`Writing changelog to: ${changelogPath}`, "info"); fs3.writeFileSync(changelogPath, finalContent); log(`Updated changelog at ${changelogPath}`, "success"); } catch (error) { log( `Error updating changelog: ${error instanceof Error ? error.message : String(error)}`, "error" ); } } // src/git/commands.ts var import_node_process2 = require("process"); // src/git/commandExecutor.ts var import_node_child_process3 = require("child_process"); var execAsync = (command, options) => { const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options }; return new Promise((resolve, reject) => { (0, import_node_child_process3.exec)( command, defaultOptions, (error, stdout, stderr) => { if (error) { reject(error); } else { resolve({ stdout: stdout.toString(), stderr: stderr.toString() }); } } ); }); }; var execSync3 = (command, args) => (0, import_node_child_process3.execSync)(command, { maxBuffer: 1024 * 1024 * 10, ...args }); // src/git/repository.ts var import_node_fs2 = require("fs"); var import_node_path3 = require("path"); function isGitRepository(directory) { const gitDir = (0, import_node_path3.join)(directory, ".git"); if (!(0, import_node_fs2.existsSync)(gitDir)) { return false; } const stats = (0, import_node_fs2.statSync)(gitDir); if (!stats.isDirectory()) { return false; } try { execSync3("git rev-parse --is-inside-work-tree", { cwd: directory }); return true; } catch (_error) { return false; } } function getCurrentBranch() { const result = execSync3("git rev-parse --abbrev-ref HEAD"); return result.toString().trim(); } // src/git/commands.ts async function gitAdd(files) { const command = `git add ${files.join(" ")}`; return execAsync(command); } async function gitCommit(options) { const command = ["commit"]; if (options.amend) { command.push("--amend"); } if (options.author) { command.push(`--author="${options.author}"`); } if (options.date) { command.push(`--date="${options.date}"`); } if (options.skipHooks) { command.push("--no-verify"); } command.push(`-m "${options.message}"`); return execAsync(`git ${command.join(" ")}`); } async function createGitTag(options) { const { tag, message = "", args = "" } = options; const command = `git tag -a -m "${message}" ${tag} ${args}`; return execAsync(command); } async function gitProcess(options) { const { files, nextTag, commitMessage, skipHooks, dryRun } = options; if (!isGitRepository((0, import_node_process2.cwd)())) { throw createGitError("NOT_GIT_REPO" /* NOT_GIT_REPO */); } try { if (!dryRun) { await gitAdd(files); await gitCommit({ message: commitMessage, skipHooks }); if (nextTag) { const tagMessage = `New Version ${nextTag} generated at ${(/* @__PURE__ */ new Date()).toISOString()}`; await createGitTag({ tag: nextTag, message: tagMessage }); } } else { log("[DRY RUN] Would add files:", "info"); for (const file of files) { log(` - ${file}`, "info"); } log(`[DRY RUN] Would commit with message: "${commitMessage}"`, "info"); if (nextTag) { log(`[DRY RUN] Would create tag: ${nextTag}`, "info"); } } } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); log(`Git process error: ${errorMessage}`, "error"); if (err instanceof Error && err.stack) { console.error("Git process stack trace:"); console.error(err.stack); } throw createGitError("GIT_PROCESS_ERROR" /* GIT_PROCESS_ERROR */, errorMessage); } } async function createGitCommitAndTag(files, nextTag, commitMessage, skipHooks, dryRun) { try { if (!files || files.length === 0) { throw createGitError("NO_FILES" /* NO_FILES */); } if (!commitMessage) { throw createGitError("NO_COMMIT_MESSAGE" /* NO_COMMIT_MESSAGE */); } setCommitMessage(commitMessage); if (nextTag) { addTag(nextTag); } await gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun }); if (!dryRun) { log(`Created tag: ${nextTag}`, "success"); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log(`Failed to create git commit and tag: ${errorMessage}`, "error"); if (error instanceof Error) { console.error("Git operation error details:"); console.error(error.stack || error.message); if (errorMessage.includes("Command failed:")) { const cmdOutput = errorMessage.split("Command failed:")[1]; if (cmdOutput) { console.error("Git command output:", cmdOutput.trim()); } } } else { console.error("Unknown git error:", error); } throw new GitError(`Git operation failed: ${errorMessage}`, "GIT_ERROR" /* GIT_ERROR */); } } // src/git/tagsAndBranches.ts var import_git_semver_tags = require("git-semver-tags"); var import_semver = __toESM(require("semver"), 1); // src/utils/formatting.ts function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function formatVersionPrefix(prefix) { return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix; } function formatTag(version, prefix, packageName, template, packageSpecificTags) { if ((template == null ? void 0 : template.includes("${packageName}")) && !packageName) { log( 'Warning: Your tagTemplate contains ${packageName} but no package name is available.\nThis will result in an empty package name in the tag (e.g., "@v1.0.0" instead of "my-package@v1.0.0").\n\nTo fix this:\n\u2022 If using synced mode: Set "packageSpecificTags": true in your config to enable package names in tags\n\u2022 If you want global tags: Remove ${packageName} from your tagTemplate (e.g., use "${prefix}${version}")\n\u2022 If using single/async mode: Ensure your package.json has a valid "name" field', "warning" ); } if (template) { return template.replace(/\$\{version\}/g, version).replace(/\$\{prefix\}/g, prefix).replace(/\$\{packageName\}/g, packageName || ""); } if (packageSpecificTags && packageName) { return `${packageName}@${prefix}${version}`; } return `${prefix}${version}`; } function formatCommitMessage(template, version, packageName, additionalContext) { if (template.includes("${packageName}") && !packageName) { log( 'Warning: Your commitMessage template contains ${packageName} but no package name is available.\nThis will result in an empty package name in the commit message (e.g., "Release @v1.0.0").\n\nTo fix this:\n\u2022 If using synced mode: Set "packageSpecificTags": true to enable package names in commits\n\u2022 If you want generic commit messages: Remove ${packageName} from your commitMessage template\n\u2022 If using single/async mode: Ensure your package.json has a valid "name" field', "warning" ); } let result = template.replace(/\$\{version\}/g, version).replace(/\$\{packageName\}/g, packageName || ""); if (additionalContext) { for (const [key, value] of Object.entries(additionalContext)) { const placeholder = `\${${key}}`; result = result.replace( new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), value ); } } return result; } // src/git/tagsAndBranches.ts function getCommitsLength(pkgRoot, sinceTag) { try { let gitCommand; if (sinceTag && sinceTag.trim() !== "") { gitCommand = `git rev-list --count ${sinceTag}..HEAD ${pkgRoot}`; } else { gitCommand = `git rev-list --count HEAD ^$(git describe --tags --abbrev=0) ${pkgRoot}`; } const amount = execSync3(gitCommand).toString().trim(); return Number(amount); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log(`Failed to get number of commits since last tag: ${errorMessage}`, "error"); return 0; } } async function getLatestTag(versionPrefix) { try { const tags = await (0, import_git_semver_tags.getSemverTags)({ tagPrefix: versionPrefix }); if (tags.length === 0) { return ""; } const chronologicalLatest = tags[0]; const sortedTags = [...tags].sort((a, b) => { const versionA = import_semver.default.clean(a) || "0.0.0"; const versionB = import_semver.default.clean(b) || "0.0.0"; return import_semver.default.rcompare(versionA, versionB); }); const semanticLatest = sortedTags[0]; if (semanticLatest !== chronologicalLatest) { log( `Tag ordering differs: chronological latest is ${chronologicalLatest}, semantic latest is ${semanticLatest}`, "debug" ); log(`Using semantic latest (${semanticLatest}) to handle out-of-order tag creation`, "info"); } return semanticLatest; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log(`Failed to get latest tag: ${errorMessage}`, "error"); if (error instanceof Error && error.message.includes("No names found")) { log("No tags found in the repository.", "info"); } return ""; } } async function lastMergeBranchName(branches, baseBranch) { try { const escapedBranches = branches.map((branch) => escapeRegExp(branch)); const branchesRegex = `${escapedBranches.join("/(.*)|")}/(.*)`; const command = `git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads --merged ${baseBranch} | grep -o -i -E "${branchesRegex}" | awk -F'[ ]' '{print $1}' | head -n 1`; const { stdout } = await execAsync(command); return stdout.trim(); } catch (error) { console.error( "Error while getting the last branch name:", error instanceof Error ? error.message : String(error) ); return null; } } async function getLatestTagForPackage(packageName, versionPrefix, options) { try { const tagTemplate = (options == null ? void 0 : options.tagTemplate) || "${prefix}${version}"; const packageSpecificTags = (options == null ? void 0 : options.packageSpecificTags) ?? false; const escapedPackageName = escapeRegExp(packageName); const escapedPrefix = versionPrefix ? escapeRegExp(versionPrefix) : ""; log( `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`, "debug" ); const allTags = await (0, import_git_semver_tags.getSemverTags)({ tagPrefix: versionPrefix }); log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug"); if (packageSpecificTags) { const packageTagPattern = escapeRegExp(tagTemplate).replace(/\\\$\\\{packageName\\\}/g, `(?:${escapedPackageName})`).replace(/\\\$\\\{prefix\\\}/g, `(?:${escapedPrefix})`).replace(/\\\$\\\{version\\\}/g, "(?:[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)"); log(`Using package tag pattern: ${packageTagPattern}`, "debug"); const packageTagRegex = new RegExp(`^${packageTagPattern}$`); let packageTags = allTags.filter((tag) => packageTagRegex.test(tag)); if (packageTags.length > 0) { const chronologicalFirst = packageTags[0]; const sortedPackageTags2 = [...packageTags].sort((a, b) => { let versionA = ""; let versionB = ""; if (a.includes("@")) { const afterAt = a.split("@")[1] || ""; versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), ""); } else { versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), ""); } if (b.includes("@")) { const afterAtB = b.split("@")[1] || ""; versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), ""); } else { versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), ""); } const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0"; const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0"; return import_semver.default.rcompare(cleanVersionA, cleanVersionB); }); log(`Found ${packageTags.length} package tags using configured pattern`, "debug"); log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug"); if (sortedPackageTags2[0] !== chronologicalFirst) { log( `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`, "debug" ); } return sortedPackageTags2[0]; } if (versionPrefix) { const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`); packageTags = allTags.filter((tag) => pattern1.test(tag)); if (packageTags.length > 0) { const sortedPackageTags2 = [...packageTags].sort((a, b) => { const afterAt = a.split("@")[1] || ""; const versionA = afterAt.replace( new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "" ); const afterAtB = b.split("@")[1] || ""; const versionB = afterAtB.replace( new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "" ); const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0"; const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0"; return import_semver.default.rcompare(cleanVersionA, cleanVersionB); }); log( `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`, "debug" ); log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug"); return sortedPackageTags2[0]; } } if (versionPrefix) { const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`); packageTags = allTags.filter((tag) => pattern2.test(tag)); if (packageTags.length > 0) { const sortedPackageTags2 = [...packageTags].sort((a, b) => { const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0"; const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0"; return import_semver.default.rcompare(versionA, versionB); }); log( `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug" ); log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug"); return sortedPackageTags2[0]; } } const pattern3 = new RegExp(`^${escapedPackageName}@`); packageTags = allTags.filter((tag) => pattern3.test(tag)); if (packageTags.length === 0) { log("No matching tags found for pattern: packageName@version", "debug"); if (allTags.length > 0) { log(`Available tags: ${allTags.join(", ")}`, "debug"); } else { log("No tags available in the repository", "debug"); } return ""; } const sortedPackageTags = [...packageTags].sort((a, b) => { const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0"; const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0"; return import_semver.default.rcompare(versionA, versionB); }); log(`Found ${packageTags.length} package tags for ${packageName}`, "debug"); log(`Using semantically latest tag: ${sortedPackageTags[0]}`, "debug"); return sortedPackageTags[0]; } log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug"); return ""; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log(`Failed to get latest tag for package ${packageName}: ${errorMessage}`, "error"); if (error instanceof Error && error.message.includes("No names found")) { log(`No tags found for package ${packageName}.`, "info"); } return ""; } } // src/package/packageManagement.ts var import_node_fs4 = __toESM(require("fs"), 1); var import_node_path5 = __toESM(require("path"), 1); // src/cargo/cargoHandler.ts var import_node_fs3 = __toESM(require("fs"), 1); var import_node_path4 = __toESM(require("path"), 1); var TOML = __toESM(require("smol-toml"), 1); function getCargoInfo(cargoPath) { var _a; if (!import_node_fs3.default.existsSync(cargoPath)) { log(`Cargo.toml file not found at: ${cargoPath}`, "error"); throw new Error(`Cargo.toml file not found at: ${cargoPath}`); } try { const fileContent = import_node_fs3.default.readFileSync(cargoPath, "utf8"); const cargo = TOML.parse(fileContent); if (!((_a = cargo.package) == null ? void 0 : _a.name)) { log(`Package name not found in: ${cargoPath}`, "error"); throw new Error(`Package name not found in: ${cargoPath}`); } return { name: cargo.package.name, version: cargo.package.version || "0.0.0", path: cargoPath, dir: import_node_path4.default.dirname(cargoPath), content: cargo }; } catch (error) { log(`Error reading Cargo.toml: ${cargoPath}`, "error"); if (error instanceof Error) { log(error.message, "error"); throw error; } throw new Error(`Failed to process Cargo.toml at ${cargoPath}`); } } function isCargoToml(filePath) { return import_node_path4.default.basename(filePath) === "Cargo.toml"; } function updateCargoVersion(cargoPath, version) { var _a; try { const originalContent = import_node_fs3.default.readFileSync(cargoPath, "utf8"); const cargo = TOML.parse(originalContent); const packageName = (_a = cargo.package) == null ? void 0 : _a.name; if (!packageName) { throw new Error(`No package name found in ${cargoPath}`); } if (!cargo.package) { cargo.package = { name: packageName, version }; } else { cargo.package.version = version; } const updatedContent = TOML.stringify(cargo); import_node_fs3.default.writeFileSync(cargoPath, updatedContent); addPackageUpdate(packageName, version, cargoPath); log(`Updated Cargo.toml at ${cargoPath} to version ${version}`, "success"); } catch (error) { log(`Failed to update Cargo.toml at ${cargoPath}`, "error"); if (error instanceof Error) { log(error.message, "error"); } throw error; } } // src/package/packageManagement.ts function updatePackageVersion(packagePath, version) { if (isCargoToml(packagePath)) { updateCargoVersion(packagePath, version); return; } try { const packageContent = import_node_fs4.default.readFileSync(packagePath, "utf8"); const packageJson = JSON.parse(packageContent); const packageName = packageJson.name; packageJson.version = version; import_node_fs4.default.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)} `); addPackageUpdate(packageName, version, packagePath); log(`Updated package.json at ${packagePath} to version ${version}`, "success"); } catch (error) { log(`Failed to update package.json at ${packagePath}`, "error"); if (error instanceof Error) { log(error.message, "error"); } throw error; } } // src/package/packageProcessor.ts var fs8 = __toESM(require("fs"), 1); var import_node_path7 = __toESM(require("path"), 1); var import_node_process4 = require("process"); // src/core/versionCalculator.ts var import_node_process3 = require("process"); var import_conventional_recommended_bump = require("conventional-recommended-bump"); var import_semver3 = __toESM(require("semver"), 1); // src/utils/manifestHelpers.ts var import_node_fs5 = __toESM(require("fs"), 1); var import_node_path6 = __toESM(require("path"), 1); function getVersionFromManifests(packageDir) { const packageJsonPath = import_node_path6.default.join(packageDir, "package.json"); const cargoTomlPath = import_node_path6.default.join(packageDir, "Cargo.toml"); if (import_node_fs5.default.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(import_node_fs5.default.readFileSync(packageJsonPath, "utf-8")); if (packageJson.version) { log(`Found version ${packageJson.version} in package.json`, "debug"); return { version: packageJson.version, manifestFound: true, manifestPath: packageJsonPath, manifestType: "package.json" }; } log("No version field found in package.json", "debug"); } catch (packageJsonError) { const errMsg = packageJsonError instanceof Error ? packageJsonError.message : String(packageJsonError); log(`Error reading package.json: ${errMsg}`, "warning"); } } if (import_node_fs5.default.existsSync(cargoTomlPath)) { try { const cargoInfo = getCargoInfo(cargoTomlPath); if (cargoInfo.version) { log(`Found version ${cargoInfo.version} in Cargo.toml`, "debug"); return { version: cargoInfo.version, manifestFound: true, manifestPath: cargoTomlPath, manifestType: "Cargo.toml" }; } log("No version field found in Cargo.toml", "debug"); } catch (cargoTomlError) { const errMsg = cargoTomlError instanceof Error ? cargoTomlError.message : String(cargoTomlError); log(`Error reading Cargo.toml: ${errMsg}`, "warning"); } } return { version: null, manifestFound: false, manifestPath: "", manifestType: null }; } // src/utils/versionUtils.ts var import_node_fs6 = __toESM(require("fs"), 1); var import_semver2 = __toESM(require("semver"), 1); var TOML2 = __toESM(require("smol-toml"), 1); // src/git/tagVerification.ts function verifyTag(tagName, cwd5) { if (!tagName || tagName.trim() === "") { return { exists: false, reachable: false, error: "Empty tag name" }; } try { execSync3(`git rev-parse --verify "${tagName}"`, { cwd: cwd5, stdio: "ignore" }); return { exists: true, reachable: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes("unknown revision") || errorMessage.includes("bad revision") || errorMessage.includes("No such ref")) { return { exists: false, reachable: false, error: `Tag '${tagName}' not found in repository` }; } return { exists: false, reachable: false, error: `Git error: ${errorMessage}` }; } } // src/utils/versionUtils.ts var STANDARD_BUMP_TYPES = ["major", "minor", "patch"]; function normalizePrereleaseIdentifier(prereleaseIdentifier, config) { if (prereleaseIdentifier === true) { return (config == null ? void 0 : config.prereleaseIdentifier) || "next"; } if (typeof prereleaseIdentifier === "string") { return prereleaseIdentifier; } return void 0; } function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) { if (prereleaseIden