diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82c847d0..aaedb69c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,6 @@ jobs: strategy: matrix: node-version: - - 20.8.1 - 22.0.0 os: - ubuntu-latest diff --git a/index.js b/index.js index 61c3296a..d99aa339 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,12 @@ import { format } from "url"; import { find, merge } from "lodash-es"; import { CommitParser } from "conventional-commits-parser"; -import writer from "./wrappers/conventional-changelog-writer.js"; import { filterRevertedCommitsSync } from "conventional-commits-filter"; import { readPackageUp } from "read-package-up"; import debugFactory from "debug"; import loadChangelogConfig from "./lib/load-changelog-config.js"; import HOSTS_CONFIG from "./lib/hosts-config.js"; +import selectWriter from "./lib/select-writer.js"; const debug = debugFactory("semantic-release:release-notes-generator"); @@ -92,5 +92,5 @@ export async function generateNotes(pluginConfig, context) { debug("issue: %o", changelogContext.issue); debug("commit: %o", changelogContext.commit); - return writer(parsedCommits, changelogContext, writerOpts); + return selectWriter(writerOpts)(parsedCommits, changelogContext, writerOpts); } diff --git a/lib/select-writer.js b/lib/select-writer.js new file mode 100644 index 00000000..acb2a90e --- /dev/null +++ b/lib/select-writer.js @@ -0,0 +1,14 @@ +import writerV8 from "../wrappers/conventional-changelog-writer.js"; +import writerV9 from "../wrappers/conventional-changelog-writer-v9.js"; + +/** + * Select the `conventional-changelog-writer` version compatible with the loaded preset's `writerOpts`. + * + * `conventional-changelog-writer@9` replaced its Handlebars-string templates (`mainTemplate`, `headerPartial`, etc.) with plain render functions, and renamed the root template option from `mainTemplate` to `template`. Presets built against the old writer (e.g. `conventional-changelog-angular@8`) never set `writerOpts.template`; presets built against the new writer (e.g. `conventional-changelog-conventionalcommits@10`) always do, as a function. The two are mutually exclusive, so this is a reliable dispatch key without needing the preset to declare its own writer version explicitly. + * + * @param {Object} writerOpts The resolved writer options from `loadChangelogConfig`. + * @return {Function} The `writeChangelogString` implementation matching `writerOpts`' own template architecture. + */ +export default function selectWriter(writerOpts) { + return typeof writerOpts.template === "function" ? writerV9 : writerV8; +} diff --git a/package-lock.json b/package-lock.json index 6d205095..fc6d0392 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", + "conventional-changelog-writer-v9": "npm:conventional-changelog-writer@^9.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", @@ -23,6 +24,7 @@ "c8": "11.0.0", "conventional-changelog-atom": "5.1.0", "conventional-changelog-conventionalcommits": "9.3.1", + "conventional-changelog-conventionalcommits-v10": "npm:conventional-changelog-conventionalcommits@10.2.1", "conventional-changelog-ember": "5.1.0", "conventional-changelog-eslint": "6.1.0", "conventional-changelog-express": "5.1.0", @@ -40,7 +42,7 @@ "testdouble": "3.20.2" }, "engines": { - "node": ">=20.8.1" + "node": ">=22.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" @@ -139,6 +141,15 @@ "node": ">=0.1.90" } }, + "node_modules/@conventional-changelog/template": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.2.1.tgz", + "integrity": "sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -1649,6 +1660,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/argue-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.1.0.tgz", + "integrity": "sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, "node_modules/argv-formatter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", @@ -2897,6 +2920,20 @@ "node": ">=18" } }, + "node_modules/conventional-changelog-conventionalcommits-v10": { + "name": "conventional-changelog-conventionalcommits", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.2.1.tgz", + "integrity": "sha512-n4Kr1HFMTf3iMbES0TMxKIcYtUUv4rKqyQQp2JwfOEfFCOfGT3Tq4mCyJ8S9/YPyWhydjfKrrvnyl+gCjA+mJQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@conventional-changelog/template": "^1.2.1" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/conventional-changelog-ember": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-5.1.0.tgz", @@ -2963,6 +3000,47 @@ "node": ">=18" } }, + "node_modules/conventional-changelog-writer-v9": { + "name": "conventional-changelog-writer", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-9.2.0.tgz", + "integrity": "sha512-eACD5BaAQCYjeI3RExkCZopNaaIuXzCP4kaAb2lEFXcGa/KOuxJfj8v/SnjhvF6377pYn0A2Gji+6GhwUK8rEA==", + "license": "MIT", + "dependencies": { + "@conventional-changelog/template": "^1.2.1", + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0", + "conventional-commits-filter": "^6.0.1", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog-writer-v9/node_modules/@simple-libs/stream-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz", + "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/conventional-changelog-writer-v9/node_modules/conventional-commits-filter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-6.0.1.tgz", + "integrity": "sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, "node_modules/conventional-commits-filter": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", diff --git a/package.json b/package.json index fb3c8318..6abd01c9 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", + "conventional-changelog-writer-v9": "npm:conventional-changelog-writer@^9.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", @@ -25,6 +26,7 @@ "c8": "11.0.0", "conventional-changelog-atom": "5.1.0", "conventional-changelog-conventionalcommits": "9.3.1", + "conventional-changelog-conventionalcommits-v10": "npm:conventional-changelog-conventionalcommits@10.2.1", "conventional-changelog-ember": "5.1.0", "conventional-changelog-eslint": "6.1.0", "conventional-changelog-express": "5.1.0", @@ -42,7 +44,7 @@ "testdouble": "3.20.2" }, "engines": { - "node": ">=20.8.1" + "node": ">=22.0.0" }, "files": [ "lib", diff --git a/test/integration.test.js b/test/integration.test.js index 3fb2796a..a89b9fa1 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -36,6 +36,43 @@ test.serial('Use "conventional-changelog-angular" by default', async (t) => { ); }); +test.serial( + 'Use "conventional-changelog-conventionalcommits-v10" (render-function writer templates) end to end', + async (t) => { + const { generateNotes } = await import("../index.js"); + const commits = [ + { hash: "111", message: "fix(scope1): First fix" }, + { hash: "222", message: "feat(scope2): Second feature" }, + ]; + const changelog = await generateNotes( + { config: "conventional-changelog-conventionalcommits-v10" }, + { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } + ); + + t.regex(changelog, /### Bug Fixes/); + t.regex(changelog, new RegExp(escape("First fix ([111](https://github.com/owner/repo/commit/111))"))); + t.regex(changelog, /### Features/); + t.regex(changelog, new RegExp(escape("Second feature ([222](https://github.com/owner/repo/commit/222))"))); + } +); + +test.serial("Dispatch to the v9 writer when writerOpts.template is a render function", async (t) => { + const writerV8Double = td.func(); + const writerV9Double = td.func(); + await td.replaceEsm("../wrappers/conventional-changelog-writer.js", {}, writerV8Double); + await td.replaceEsm("../wrappers/conventional-changelog-writer-v9.js", {}, writerV9Double); + const { generateNotes } = await import("../index.js"); + + const commits = [{ hash: "111", message: "fix(scope1): First fix" }]; + await generateNotes( + { writerOpts: { template: () => "" } }, + { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } + ); + + td.verify(writerV9Double(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())); + t.throws(() => td.verify(writerV8Double(td.matchers.anything(), td.matchers.anything(), td.matchers.anything()))); +}); + test.serial("Set conventional-changelog-writer context", async (t) => { const cwd = temporaryDirectory(); const writerDouble = td.func(); diff --git a/test/load-changelog-config.test.js b/test/load-changelog-config.test.js index 895d0071..ee819d45 100644 --- a/test/load-changelog-config.test.js +++ b/test/load-changelog-config.test.js @@ -177,6 +177,15 @@ test('Throw error if "preset" doesn`t exist', async (t) => { await t.throwsAsync(loadChangelogConfig({ preset: "unknown-preset" }, { cwd }), { code: "MODULE_NOT_FOUND" }); }); +test('Load "conventional-changelog-conventionalcommits-v10" config with the new render-function writer template', async (t) => { + const changelogConfig = await loadChangelogConfig( + { config: "conventional-changelog-conventionalcommits-v10", presetConfig: {} }, + { cwd } + ); + + t.is(typeof changelogConfig.writerOpts.template, "function"); +}); + test.serial("Load preset and config correctly when importFrom.silent fails", async (t) => { sinon.stub(importFrom, "silent").returns(undefined); diff --git a/wrappers/conventional-changelog-writer-v9.js b/wrappers/conventional-changelog-writer-v9.js new file mode 100644 index 00000000..2f270526 --- /dev/null +++ b/wrappers/conventional-changelog-writer-v9.js @@ -0,0 +1,3 @@ +import { writeChangelogString as writer } from "conventional-changelog-writer-v9"; + +export default writer; diff --git a/wrappers/conventional-changelog-writer.js b/wrappers/conventional-changelog-writer.js index ff814cb3..0c54ea26 100644 --- a/wrappers/conventional-changelog-writer.js +++ b/wrappers/conventional-changelog-writer.js @@ -1,4 +1,3 @@ -import { writeChangelogString as writer } from 'conventional-changelog-writer'; +import { writeChangelogString as writer } from "conventional-changelog-writer"; export default writer; -