Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | import { readFileSync, writeFileSync, existsSync } from 'fs';
import path from 'path';
import { getConventionalCommitsSince } from './git.js';
import { getConfig } from './config.js';
import { dryRunAction, info } from './log.js';
const CONVENTIONAL_TYPES = {
feat: 'Features',
fix: 'Bug Fixes',
perf: 'Performance Improvements',
revert: 'Reverts',
refactor: 'Code Refactoring',
docs: 'Documentation',
style: 'Styles',
chore: 'Chores',
test: 'Tests',
build: 'Build System',
ci: 'Continuous Integration',
};
const VISIBLE_TYPES = ['feat', 'fix', 'perf', 'revert'];
function parseConventionalCommit(subject) {
const match = subject.match(/^(\w+)(?:\(([^)]+)\))?(!?):\s*(.+)$/);
if (!match) return null;
return {
type: match[1],
scope: match[2] || null,
breaking: match[3] === '!',
description: match[4],
};
}
export function generateChangelogNotes(packageName, version, sinceRef, pkgPaths = []) {
const commits = getConventionalCommitsSince(sinceRef, pkgPaths);
const grouped = {};
const breaking = [];
for (const commit of commits) {
const parsed = parseConventionalCommit(commit.subject);
if (!parsed) continue;
if (parsed.breaking) {
breaking.push({ ...parsed, hash: commit.hash });
}
if (!VISIBLE_TYPES.includes(parsed.type)) continue;
const heading = CONVENTIONAL_TYPES[parsed.type] || parsed.type;
if (!grouped[heading]) grouped[heading] = [];
const shortHash = commit.hash.substring(0, 7);
const scopePrefix = parsed.scope ? `**${parsed.scope}:** ` : '';
grouped[heading].push(`* ${scopePrefix}${parsed.description} (${shortHash})`);
}
const lines = [];
const shortName = packageName.split('/').pop();
const dateStr = new Date().toISOString().split('T')[0];
lines.push(`## ${version} (${dateStr})`);
lines.push('');
if (breaking.length > 0) {
lines.push('### BREAKING CHANGES');
lines.push('');
for (const b of breaking) {
const scopePrefix = b.scope ? `**${b.scope}:** ` : '';
lines.push(`* ${scopePrefix}${b.description}`);
}
lines.push('');
}
const headings = Object.keys(grouped).sort();
if (headings.length === 0 && breaking.length === 0) {
lines.push(`**Note:** Version bump only for package ${packageName}`);
lines.push('');
} else {
for (const heading of headings) {
lines.push(`### ${heading}`);
lines.push('');
for (const entry of grouped[heading]) {
lines.push(entry);
}
lines.push('');
}
}
return lines.join('\n');
}
export function prependChangelog(pkgPath, notes) {
const changelogPath = path.join(pkgPath, 'CHANGELOG.md');
if (dryRunAction(`prepend to ${changelogPath}`)) {
info(`Changelog notes:\n${notes}`);
return changelogPath;
}
let existing = '';
if (existsSync(changelogPath)) {
existing = readFileSync(changelogPath, 'utf8');
}
const header = '# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n';
let content;
if (existing.startsWith('# Change Log')) {
const afterHeader = existing.indexOf('\n\n', existing.indexOf('\n') + 1);
const body = afterHeader >= 0 ? existing.substring(afterHeader + 2) : '';
content = `${header}\n${notes}\n\n${body}`;
} else if (existing) {
content = `${header}\n${notes}\n\n${existing}`;
} else {
content = `${header}\n${notes}\n`;
}
writeFileSync(changelogPath, content.replace(/\n{3,}/g, '\n\n'));
return changelogPath;
}
|