UNPKG

xrelease

Version:

xrelease (pronounced cross-release) helps you setup automated releases for your project for any language

75 lines (66 loc) 2.55 kB
import { execa } from 'execa'; import fs from 'fs/promises'; export type ChangelogTemplate = 'conventional' | 'simple'; export async function generateChangelog(version: string, template: ChangelogTemplate = 'conventional'): Promise<void> { try { // Try to get the last tag let commits: string; try { const { stdout: lastTag } = await execa('git', ['describe', '--tags', '--abbrev=0']); // Get commits since last tag const { stdout } = await execa('git', ['log', '--oneline', '--no-merges', `${lastTag}..HEAD`]); commits = stdout; } catch (error) { // If no tags exist, get all commits const { stdout } = await execa('git', ['log', '--oneline', '--no-merges']); commits = stdout; } // Generate changelog content const date = new Date().toISOString().split('T')[0]; const header = `## [${version}] - ${date}\n\n`; let content = ''; if (!commits.trim()) { content = '* No changes\n\n'; } else if (template === 'conventional') { content = formatConventionalCommits(commits); } else { content = formatSimpleCommits(commits); } const changelogContent = header + content; try { // Try to read existing changelog const existingChangelog = await fs.readFile('CHANGELOG.md', 'utf-8'); const updatedChangelog = existingChangelog.replace(/^(# Changelog\n\n)?/, ''); // Remove existing header if present await fs.writeFile('CHANGELOG.md', '# Changelog\n\n' + changelogContent + updatedChangelog); } catch (error) { // If file doesn't exist, create new one await fs.writeFile('CHANGELOG.md', '# Changelog\n\n' + changelogContent); } } catch (error) { if (error instanceof Error && error.message.includes('ENOENT')) { throw new Error('Failed to update changelog file'); } throw error; } } function formatConventionalCommits(commits: string): string { const lines = commits.split('\n').filter(Boolean); const formatted = lines.map((line) => { const match = line.match(/^[a-f0-9]+ (feat|fix|docs|style|refactor|test|chore)(\([^)]+\))?: (.+)$/); if (match) { const [, type, , message] = match; return `* ${type}: ${message}`; } return `* ${line.replace(/^[a-f0-9]+ /, '')}`; }); return formatted.join('\n') + '\n\n'; } function formatSimpleCommits(commits: string): string { return ( commits .split('\n') .filter(Boolean) .map((line) => `* ${line.replace(/^[a-f0-9]+ /, '')}`) .join('\n') + '\n\n' ); }