diff --git a/deno.lock b/deno.lock index 1eaa719..4692111 100644 --- a/deno.lock +++ b/deno.lock @@ -410,6 +410,9 @@ "https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1", "https://deno.land/x/jszip@0.11.0/types.ts": "1528d1279fbb64dd118c371331c641a3a5eff2b594336fb38a7659cf4c53b2d1", "https://deno.land/x/promise_object@v0.10.0/index.ts": "095e9fed1c0948110c8ded200814b051ac105ec2315b7ffc2c8ab51250d5a82b", + "https://deno.land/x/typed_regex@0.2.0/api_types.ts": "27a3f41894244da95b60646962231a3b02c4ed55bf7042747c737869fe3d4574", + "https://deno.land/x/typed_regex@0.2.0/mod.ts": "2a51f6fca3f6cabe28d420680c4bc6609704e3cc833ba987efe2c44c911330bb", + "https://deno.land/x/typed_regex@0.2.0/type_parser.ts": "a265790f94234a5338c7854af3794bcb012015f7c3e1c091ebf7b160b1627d31", "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6", "https://deno.land/x/zod@v3.20.5/ZodError.ts": "10bb0d014b0ece532c3bc395c50ae25996315a5897c0216517d9174c2fb570b5", "https://deno.land/x/zod@v3.20.5/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", diff --git a/scripts/changelog/changelog_test.ts b/scripts/changelog/changelog_test.ts new file mode 100644 index 0000000..17b287d --- /dev/null +++ b/scripts/changelog/changelog_test.ts @@ -0,0 +1,61 @@ +import { assertEquals } from "$std/assert/assert_equals.ts" +import { outdent } from "$dax/src/deps.ts" +import { getSections, parseCommits } from "./mod.ts" +import { renderBBCode } from "./render_bbcode.ts" + +export const exampleCommits = parseCommits([ + "feat: asdf (#1234)", + "feat!: test (#123)", + "fix(L10n): language stuff (#415)", +]) +const exampleSections = getSections(exampleCommits) + +Deno.test("parseCommits() correctly parse commits", () => { + assertEquals(exampleCommits, [ + { + type: "feat", + scopes: undefined, + breaking: undefined, + subject: "asdf", + pr: "1234", + }, + { + type: "feat", + scopes: undefined, + breaking: "!", + subject: "test", + pr: "123", + }, + { + type: "fix", + scopes: "L10n", + breaking: undefined, + subject: "language stuff", + pr: "415", + }, + ]) +}) + +Deno.test("renderBBCode() outputs identical text", () => + assertEquals( + renderBBCode({ version: "v1.0.0", date: "2024-03-12", sections: exampleSections }), + outdent` + [h1]v1.0.0 (2024-03-12)[/h1] + + [h2]Breaking Changes[/h2] + [list] + [*] test ([url=https://github.com/scarf005/marisa/pull/123]#123[/url]) + [/list] + + [h2]New Features[/h2] + [list] + [*] asdf ([url=https://github.com/scarf005/marisa/pull/1234]#1234[/url]) + [*] test ([url=https://github.com/scarf005/marisa/pull/123]#123[/url]) + [/list] + + [h2]Fixes[/h2] + [list] + [*] language stuff ([url=https://github.com/scarf005/marisa/pull/415]#415[/url]) + [/list] + `, + )) diff --git a/scripts/changelog/mod.ts b/scripts/changelog/mod.ts new file mode 100644 index 0000000..eb2eceb --- /dev/null +++ b/scripts/changelog/mod.ts @@ -0,0 +1,49 @@ +import $ from "$dax/mod.ts" +import { typedRegEx } from "https://deno.land/x/typed_regex@0.2.0/mod.ts" +import type { RegExCaptureResult } from "https://deno.land/x/typed_regex@0.2.0/type_parser.ts" + +import { renderBBCode } from "./render_bbcode.ts" + +export type Commit = RegExCaptureResult +export type Sections = Record + +export const getTags = () => $`git tag --sort=-v:refname`.lines() + +type CommitRange = { from: string; to: string } +export const getCommits = ({ from, to }: CommitRange) => + $`git log ${from}..${to} --pretty=format:"%s"`.lines() + +const re = + "^(?\\w+)(\\((?.*)?\\))?(?!)?:\\s*(?.*?)\\s*\\(#(?\\d+)\\)$" + +const commitParser = typedRegEx(re) + +export const parseCommits = (xs: string[]) => + xs + .map(commitParser.captures) + .filter((x): x is Commit => x !== undefined) + .filter((x) => x.breaking || ["fix", "feat"].includes(x.type)) + +export const getSections = (commits: Commit[]): Sections => ({ + "Breaking Changes": commits.filter((x) => x.breaking), + "New Features": commits.filter((x) => x.type === "feat"), + Fixes: commits.filter((x) => x.type === "fix"), +}) + +if (import.meta.main) { + const [latestTag] = await getTags() + const commits = await getCommits({ from: latestTag, to: "main" }).then(parseCommits) + + const sections: Sections = getSections(commits) + + console.log(latestTag) + console.log(sections) + console.log(commits) + console.log( + renderBBCode({ + version: "v1.0.0", + date: new Date().toISOString().split("T")[0], + sections, + }), + ) +} diff --git a/scripts/changelog/render.ts b/scripts/changelog/render.ts new file mode 100644 index 0000000..6f2f6c0 --- /dev/null +++ b/scripts/changelog/render.ts @@ -0,0 +1,18 @@ +import { Commit, Sections } from "./mod.ts" + +export type ChangelogRenderer = (option: { + version: string + date: string + sections: Sections +}) => string + +export type SectionFormatter = (tup: [section: string, commits: Commit[]]) => string +export type SectionsFormatter = { fmtSection: SectionFormatter; sections: Sections } + +export const renderSections = ( + { fmtSection, sections }: SectionsFormatter, +) => + Object.entries(sections) + .filter(([, commits]) => commits.length > 0) + .map(fmtSection) + .join("\n\n") diff --git a/scripts/changelog/render_bbcode.ts b/scripts/changelog/render_bbcode.ts new file mode 100644 index 0000000..f3842ee --- /dev/null +++ b/scripts/changelog/render_bbcode.ts @@ -0,0 +1,16 @@ +import { outdent } from "$dax/src/deps.ts" +import { Commit } from "./mod.ts" +import { ChangelogRenderer, renderSections, SectionFormatter } from "./render.ts" + +const fmtCommit = (x: Commit) => + ` [*] ${x.subject} ([url=https://github.com/scarf005/marisa/pull/${x.pr}]#${x.pr}[/url])` + +const fmtSection: SectionFormatter = ([section, commits]) => + `[h2]${section}[/h2]\n` + "[list]\n" + commits.map(fmtCommit).join("\n") + "\n[/list]" + +export const renderBBCode: ChangelogRenderer = ({ version, date, sections }) => + outdent` + [h1]${version} (${date})[/h1] + + ${renderSections({ fmtSection, sections })} + `