import type { Arrayable, Nullable } from "@libs/typing"
import { assert } from "@std/assert"
import * as semver from "@std/semver"
import * as git from "./mod.ts"
export type { Arrayable, Nullable }
export type * from "@std/semver"
export function changelog(path: string, { scopes = [], minor = ["feat"], patch = ["fix", "docs", "perf", "refactor", "chore"], ...options } = {} as ChangelogOptions): Changelog {
const base = git.blame(path).find(({ content }) => /"version": "\d\.\d\.\d.*"/.test(content))
assert(base, `Could not find "version" key in ${path}`)
const version = semver.parse(base.content.match(/"version": "(?<version>\d\.\d\.\d.*)"/)?.groups?.version ?? "")
const commits = git.log(base.sha, { filter: { conventional: true, scopes } })
const result = { version: { bump: null, current: version, next: version }, changelog: "" } as Changelog
if (commits.some(({ breaking }) => breaking)) {
result.version.bump = "major"
result.version.next = semver.increment(version, result.version.bump)
} else if (commits.some(({ type }) => minor.includes(type as string))) {
result.version.bump = "minor"
result.version.next = semver.increment(version, result.version.bump)
} else if (commits.some(({ type }) => patch.includes(type as string))) {
result.version.bump = "patch"
result.version.next = semver.increment(version, result.version.bump)
}
if (options.write) {
const content = Deno.readTextFileSync(path)
Deno.writeTextFileSync(path, content.replace(/"version": "\d\.\d\.\d.*"/, `"version": "${semver.format(result.version.next)}"`))
}
result.changelog = commits.map(({ summary }) => summary).join("\n")
return result
}
export type ChangelogOptions = {
scopes?: Arrayable<string>
patch?: Arrayable<string>
minor?: Arrayable<string>
write?: boolean
}
export type Changelog = {
version: {
bump: Nullable<"major" | "minor" | "patch">
current: semver.SemVer
next: semver.SemVer
}
changelog: string
}
|