#!/usr/bin/env node import { readFileSync, writeFileSync } from 'node:fs' import { resolve } from 'node:path' const SECTION_BY_TYPE = { added: 'Added', changed: 'Changed', deprecated: 'Deprecated', removed: 'Removed', fixed: 'Fixed', security: 'Security', } function usage() { console.error( [ 'Usage:', ' pnpm changelog:add -- --type fixed --message "Fix thing"', '', `Types: ${Object.keys(SECTION_BY_TYPE).join(', ')}`, ].join('\n') ) process.exit(1) } function argValue(name) { const index = process.argv.indexOf(`--${name}`) if (index === -1) return null return process.argv[index + 1] ?? null } const type = argValue('type')?.toLowerCase() const message = argValue('message')?.trim() if (!type || !message || !SECTION_BY_TYPE[type]) { usage() } const section = SECTION_BY_TYPE[type] const changelogPath = resolve('CHANGELOG.md') let changelog = readFileSync(changelogPath, 'utf8') if (!/^## \[Unreleased\]/m.test(changelog)) { changelog = changelog.replace(/(\r?\n)(## \[[^\r\n]+\])/, `$1## [Unreleased]$1$1$2`) } // JS regex has no \z anchor, so match to the next version heading or true // end-of-input ($ with nothing following — \n-tolerant under the m flag). const unreleasedMatch = changelog.match(/^## \[Unreleased\][\s\S]*?(?=^## \[|$(?![\s\S]))/m) if (!unreleasedMatch) { throw new Error('Could not find or create an Unreleased section.') } const unreleased = unreleasedMatch[0] const lineEnding = changelog.includes('\r\n') ? '\r\n' : '\n' const bullet = `- ${message}` let nextUnreleased const sectionRegex = new RegExp(`(^### ${section}\\s*)([\\s\\S]*?)(?=^### |$(?![\\s\\S]))`, 'm') const sectionMatch = unreleased.match(sectionRegex) if (sectionMatch) { const body = sectionMatch[2].trimEnd() const replacement = `${sectionMatch[1]}${body ? `${body}${lineEnding}` : ''}${bullet}${lineEnding}${lineEnding}` nextUnreleased = unreleased.replace(sectionRegex, replacement) } else { const insert = `${lineEnding}### ${section}${lineEnding}${lineEnding}${bullet}${lineEnding}` nextUnreleased = unreleased.trimEnd() + insert + lineEnding } changelog = changelog.replace(unreleased, nextUnreleased) writeFileSync(changelogPath, changelog)