@eighty4/changelog
Version:
Changelog utilities for CICD
88 lines (83 loc) • 2.92 kB
text/typescript
import { BadChangelogError, CliError } from './errors.ts'
import { inspectChangelog } from './inspect.ts'
import { readChangelogFile } from './readChangelog.ts'
import { isSemverVersion } from './semver.ts'
export type GetContentOpts = {
changelogFile: string
version: string
}
export function parseArgs(args: Array<string>): GetContentOpts {
const version = args.shift()
if (typeof version === 'undefined') {
throw new CliError('missing version')
}
if (version !== 'Unreleased' && !isSemverVersion(version)) {
throw new CliError(version + ' is not a `vX.X.X` format semver')
}
const opts: GetContentOpts = {
changelogFile: 'CHANGELOG.md',
version,
}
let shifted
while ((shifted = args.shift())) {
switch (shifted) {
case '--changelog-file':
if (typeof (shifted = args.shift()) === 'undefined') {
throw new CliError('--changelog-file value is missing')
}
opts.changelogFile = shifted
break
default:
throw new CliError(`bad arg \`${shifted}\``)
}
}
return opts
}
export async function getVersionContentFromCliArgs(
args: Array<string>,
): Promise<string> {
return await getVersionContent(parseArgs(args))
}
export async function getVersionContent(opts: GetContentOpts): Promise<string> {
const changelogContent = await readChangelogFile(opts.changelogFile)
const inspectResult = inspectChangelog(changelogContent)
if (inspectResult.errors.length) {
throw new BadChangelogError(opts.changelogFile, inspectResult.errors)
}
const versionStartStr = `## [${opts.version}]`
const versionStart = changelogContent.indexOf(versionStartStr)
if (versionStart === -1) {
throw new Error(opts.version + ' not found')
}
const notesStart = changelogContent.indexOf('\n', versionStart)
let notesEnd = changelogContent.indexOf('\n## ', notesStart)
if (notesEnd === -1) {
notesEnd = changelogContent.indexOf('[', notesStart)
}
if (notesEnd === -1) {
notesEnd = changelogContent.length
}
const notes = changelogContent.substring(notesStart, notesEnd).trim()
if (!notes.length) {
return ''
}
let result = ''
let categoryLines: Array<string> = []
const mergeCategoryIntoResult = () => {
if (categoryLines.some(l => /^(\*|\-|\+)/.test(l))) {
result += categoryLines.join('\n')
}
}
for (const l of notes.split(/\r?\n/).map(l => l.trim())) {
if (opts.version === 'Unreleased' && /^(\*|\-|\+) \?\?\?$/.test(l)) {
continue
}
if (l.startsWith('### ')) {
mergeCategoryIntoResult()
categoryLines = []
}
categoryLines.push(l)
}
mergeCategoryIntoResult()
return result
}