diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dba8966..ec96ee80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add a new `release-notes` output to the action containing the release notes for the newly released versions. + ## [3.0.0] - 2024-04-08 ### Fixed diff --git a/__tests__/fixtures/empty_release/release-notes.expected.md b/__tests__/fixtures/empty_release/release-notes.expected.md new file mode 100644 index 00000000..e69de29b diff --git a/__tests__/fixtures/first_release/release-notes.expected.md b/__tests__/fixtures/first_release/release-notes.expected.md new file mode 100644 index 00000000..346fdaac --- /dev/null +++ b/__tests__/fixtures/first_release/release-notes.expected.md @@ -0,0 +1,3 @@ +### Added + +- Everything since the beginning! \ No newline at end of file diff --git a/__tests__/fixtures/lowercase_link_reference/release-notes.expected.md b/__tests__/fixtures/lowercase_link_reference/release-notes.expected.md new file mode 100644 index 00000000..e69de29b diff --git a/__tests__/fixtures/standard/release-notes.expected.md b/__tests__/fixtures/standard/release-notes.expected.md new file mode 100644 index 00000000..aa2e99a7 --- /dev/null +++ b/__tests__/fixtures/standard/release-notes.expected.md @@ -0,0 +1,3 @@ +### Changed + +- Our main theme is now blue instead of red. \ No newline at end of file diff --git a/__tests__/fixtures/tag_on_tag/release-notes.expected.md b/__tests__/fixtures/tag_on_tag/release-notes.expected.md new file mode 100644 index 00000000..aa2e99a7 --- /dev/null +++ b/__tests__/fixtures/tag_on_tag/release-notes.expected.md @@ -0,0 +1,3 @@ +### Changed + +- Our main theme is now blue instead of red. \ No newline at end of file diff --git a/__tests__/fixtures/tag_release/release-notes.expected.md b/__tests__/fixtures/tag_release/release-notes.expected.md new file mode 100644 index 00000000..aa2e99a7 --- /dev/null +++ b/__tests__/fixtures/tag_release/release-notes.expected.md @@ -0,0 +1,3 @@ +### Changed + +- Our main theme is now blue instead of red. \ No newline at end of file diff --git a/__tests__/getReleaseNotes.test.ts b/__tests__/getReleaseNotes.test.ts new file mode 100644 index 00000000..297b38a9 --- /dev/null +++ b/__tests__/getReleaseNotes.test.ts @@ -0,0 +1,35 @@ +import getReleaseNotes from "../src/getReleaseNotes"; +import { read } from "to-vfile"; + +interface Fixture { + tag: string; + version: string; + date: string; + genesisHash: string; + owner: string; + repo: string; +} + +it.each(["empty_release", "standard", "first_release", "lowercase_link_reference", "tag_release", "tag_on_tag"])( + `should extract %s release-notes output`, + async function(testcase) { + const expectedChangelog = await read( + `./__tests__/fixtures/${testcase}/CHANGELOG.expected.md`, + { + encoding: "utf-8" + } + ); + const release: Fixture = await import( + `./fixtures/${testcase}/fixture` + ).then(module => module.default); + + const expectedReleaseNotes = await read( + `./__tests__/fixtures/${testcase}/release-notes.expected.md`, + { + encoding: "utf-8" + } + ).then(expected => expected.toString("utf-8")); + const actualReleaseNotes = getReleaseNotes(expectedChangelog, release.version); + expect(actualReleaseNotes).toEqual(expectedReleaseNotes); + } +); diff --git a/action.yml b/action.yml index e0a9f0f6..b6bab9ef 100644 --- a/action.yml +++ b/action.yml @@ -17,6 +17,9 @@ inputs: changelogPath: description: 'The path to the changelog file. Defaults to `./CHANGELOG.md`' required: false +outputs: + release-notes: + description: 'The release notes of the newly released version' runs: using: 'node20' main: 'dist/index.js' diff --git a/src/getReleaseNotes.ts b/src/getReleaseNotes.ts new file mode 100644 index 00000000..8e6f149d --- /dev/null +++ b/src/getReleaseNotes.ts @@ -0,0 +1,55 @@ +import unified, { Transformer } from "unified"; +import markdown from "remark-parse"; +import stringify from "remark-stringify"; +import { VFile } from "vfile"; +import { Node } from "unist"; +import { MarkdownRootNode } from "markdown-nodes"; + +function releaseNotesExtraction(version: string) { + return transformer as unknown as Transformer; + + function transformer(tree: MarkdownRootNode, _file: VFile) { + const children = tree.children; + + const firstNodeIndex = children.findIndex( + node => node.type === "heading" && node.depth === 2 && + node.children.length > 1 && node.children[0].type === "linkReference" && + node.children[0].identifier === version + ) + 1; + const firstNode = children.slice(firstNodeIndex); + + let lastNodeIndex = firstNode.findIndex( + node => node.type === "heading" && node.depth === 2 + ); + // special case: release notes for first release will not end with another + // section, instead they end with the compare URLs + if (lastNodeIndex === -1) { + lastNodeIndex = firstNode.findIndex( + node => node.type === "definition" && node.identifier === "unreleased" + ); + } + + const releaseNotesNodes = firstNode.slice(0, lastNodeIndex); + tree.children = releaseNotesNodes; + return tree as Node; + } +} + +export default function getReleaseNotes( + file: VFile, + version: string +): string { + // @ts-ignore + return unified() + .use(markdown) + .use(releaseNotesExtraction, version) + .data("settings", { + listItemIndent: "1", + tightDefinitions: true, + bullet: "-" + }) + .use(stringify) + .processSync(file) + .toString("utf-8") + .trim(); +} diff --git a/src/index.ts b/src/index.ts index 21fe63dd..4f169135 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ import { setFailed } from "@actions/core"; +import { setOutput } from "@actions/core/lib/core"; import { read, write } from "to-vfile"; import updateChangelog from "./updateChangelog"; import getInputs from "./getInputs"; import getGenesisHash from "./getGenesisHash"; +import getReleaseNotes from "./getReleaseNotes"; async function run(): Promise { try { @@ -20,8 +22,10 @@ async function run(): Promise { owner, repo ); - await write(newChangelog, { encoding: "utf-8" }); + + const releaseNotes = getReleaseNotes(newChangelog, version); + setOutput("release-notes", releaseNotes); } catch (error) { setFailed(error.message); }