UNPKG

auto-version-tool

Version:

根据git commit历史自动修改版本号并生成changelog的CLI工具 (Automatically bump version & generate changelog based on git commits)

265 lines (252 loc) 9.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChangelogService = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const mustache_1 = __importDefault(require("mustache")); class ChangelogService { constructor(config) { this.config = config; } async generateChangelog(versionInfo, commits) { const changelogEntry = this.createChangelogEntry(versionInfo, commits); return this.renderChangelogEntry(changelogEntry); } async updateChangelog(versionInfo, commits) { const changelogPath = path.resolve(this.config.changelog.outputFile); const newEntry = await this.generateChangelog(versionInfo, commits); // 读取现有的changelog let existingContent = ''; if (fs.existsSync(changelogPath)) { existingContent = fs.readFileSync(changelogPath, 'utf-8'); } // 生成新的changelog内容 const updatedContent = this.mergeChangelog(newEntry, existingContent); // 写入文件 fs.writeFileSync(changelogPath, updatedContent, 'utf-8'); console.log(`✅ 已更新 changelog: ${changelogPath}`); } createChangelogEntry(versionInfo, commits) { const filteredCommits = this.filterCommits(commits); return { version: versionInfo.next, date: new Date().toISOString().split('T')[0], commits: filteredCommits, breaking: filteredCommits.filter(c => c.breaking), features: filteredCommits.filter(c => c.type === 'feat'), fixes: filteredCommits.filter(c => c.type === 'fix'), others: filteredCommits.filter(c => c.type && !['feat', 'fix'].includes(c.type) && !c.breaking) }; } filterCommits(commits) { return commits.filter(commit => { // 过滤掉不需要的提交类型 if (!commit.type) return false; return this.config.changelog.includeTypes.includes(commit.type); }); } renderChangelogEntry(entry) { const template = this.getTemplate(); const data = { version: entry.version, date: entry.date, hasBreaking: entry.breaking.length > 0, breaking: entry.breaking.map(this.formatCommit.bind(this)), hasFeatures: entry.features.length > 0, features: entry.features.map(this.formatCommit.bind(this)), hasFixes: entry.fixes.length > 0, fixes: entry.fixes.map(this.formatCommit.bind(this)), hasOthers: entry.others.length > 0, others: this.groupOtherCommits(entry.others) }; return mustache_1.default.render(template, data); } formatCommit(commit) { const shortHash = commit.hash.substring(0, 7); const scope = commit.scope ? `**${commit.scope}**: ` : ''; const subject = commit.subject || commit.message.split('\n')[0]; return { hash: commit.hash, shortHash, scope, subject, scopeDisplay: commit.scope, message: `${scope}${subject}`, author: commit.author, type: commit.type, typeTitle: this.getTypeTitle(commit.type), emoji: this.getTypeEmoji(commit.type) }; } groupOtherCommits(commits) { if (this.config.changelog.groupBy === 'none') { return commits.map(this.formatCommit.bind(this)); } const grouped = {}; commits.forEach(commit => { const key = this.config.changelog.groupBy === 'type' ? commit.type || 'other' : commit.scope || 'other'; if (!grouped[key]) { grouped[key] = []; } grouped[key].push(commit); }); return Object.entries(grouped).map(([key, commits]) => ({ title: this.getGroupTitle(key), commits: commits.map(this.formatCommit.bind(this)) })); } getTypeTitle(type) { if (!type) return 'Other'; const typeConfig = this.config.commitTypes[type]; return typeConfig?.title || type.charAt(0).toUpperCase() + type.slice(1); } getTypeEmoji(type) { if (!type) return ''; const typeConfig = this.config.commitTypes[type]; return typeConfig?.emoji || ''; } getGroupTitle(group) { if (this.config.changelog.groupBy === 'type') { return this.getTypeTitle(group); } return group.charAt(0).toUpperCase() + group.slice(1); } getTemplate() { // 如果配置了自定义模板文件,读取它 if (this.config.changelog.template && fs.existsSync(this.config.changelog.template)) { return fs.readFileSync(this.config.changelog.template, 'utf-8'); } // 使用默认模板 return this.getDefaultTemplate(); } getDefaultTemplate() { return `## [{{version}}]({{compareUrl}}) ({{date}}) {{#hasBreaking}} ### ⚠ BREAKING CHANGES {{#breaking}} * {{emoji}} {{message}} ([{{shortHash}}]({{commitUrl}})) {{/breaking}} {{/hasBreaking}} {{#hasFeatures}} ### ✨ Features {{#features}} * {{emoji}} {{message}} ([{{shortHash}}]({{commitUrl}})) {{/features}} {{/hasFeatures}} {{#hasFixes}} ### 🐛 Bug Fixes {{#fixes}} * {{emoji}} {{message}} ([{{shortHash}}]({{commitUrl}})) {{/fixes}} {{/hasFixes}} {{#hasOthers}} ### 📦 Other Changes {{#others}} {{#title}} #### {{title}} {{/title}} {{#commits}} * {{emoji}} {{message}} ([{{shortHash}}]({{commitUrl}})) {{/commits}} {{/others}} {{/hasOthers}} `; } mergeChangelog(newEntry, existingContent) { if (!existingContent.trim()) { return this.createInitialChangelog(newEntry); } // 找到第一个版本标题的位置 const versionHeaderRegex = /^## \[.*?\]/m; const match = existingContent.match(versionHeaderRegex); if (match && match.index !== undefined) { // 在第一个版本之前插入新条目 const beforeFirstVersion = existingContent.substring(0, match.index); const afterFirstVersion = existingContent.substring(match.index); return beforeFirstVersion + newEntry + '\n' + afterFirstVersion; } else { // 如果没有找到版本标题,说明是空的changelog return this.createInitialChangelog(newEntry); } } createInitialChangelog(firstEntry) { 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). ${firstEntry}`; } async getChangelogHistory() { const changelogPath = path.resolve(this.config.changelog.outputFile); if (!fs.existsSync(changelogPath)) { return []; } const content = fs.readFileSync(changelogPath, 'utf-8'); return this.parseChangelog(content); } parseChangelog(content) { // 这里可以实现changelog解析逻辑 // 暂时返回空数组 return []; } async validateChangelog() { try { const changelogPath = path.resolve(this.config.changelog.outputFile); if (!fs.existsSync(changelogPath)) { return true; // 文件不存在时视为有效 } const content = fs.readFileSync(changelogPath, 'utf-8'); // 基本验证:检查是否有有效的markdown格式 const hasHeader = content.includes('# Changelog') || content.includes('# CHANGELOG'); const hasVersions = /^## \[.*?\]/.test(content); return hasHeader || hasVersions; } catch { return false; } } } exports.ChangelogService = ChangelogService; //# sourceMappingURL=ChangelogService.js.map