Repository: semantic-release/release-notes-generator Branch: master Commit: 0b4e4a7e080e Files: 14 Total size: 73.2 KB Directory structure: gitextract_za807k0y/ ├── .git-blame-ignore-revs ├── .github/ │ └── workflows/ │ ├── release.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── index.js ├── lib/ │ ├── hosts-config.js │ └── load-changelog-config.js ├── package.json ├── test/ │ ├── integration.test.js │ └── load-changelog-config.test.js └── wrappers/ └── conventional-changelog-writer.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .git-blame-ignore-revs ================================================ # style: prettier 833e2127a843ed33f9da1b945e28933afe25a5aa ================================================ FILE: .github/workflows/release.yml ================================================ name: Release "on": push: branches: - master - next - beta - "*.x" permissions: contents: read # for checkout jobs: release: permissions: contents: write # to be able to publish a GitHub release issues: write # to be able to comment on released issues pull-requests: write # to be able to comment on released pull requests id-token: write # to enable use of OIDC for npm provenance name: release runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version: lts/* - run: npm clean-install - run: corepack npm audit signatures - run: npx semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_BOT_NPM_TOKEN }} ================================================ FILE: .github/workflows/test.yml ================================================ name: Test "on": push: branches: - master - renovate/** pull_request: types: - opened - synchronize permissions: contents: read env: FORCE_COLOR: 1 NPM_CONFIG_COLOR: always jobs: test_matrix: strategy: matrix: node-version: - 20.8.1 - 22.0.0 os: - ubuntu-latest runs-on: "${{ matrix.os }}" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "Use Node.js ${{ matrix.node-version }}" uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "${{ matrix.node-version }}" cache: npm - run: npm clean-install - run: corepack npm audit signatures - run: npm test test_dev: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "Use development Node.js version" uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version-file: .nvmrc cache: npm - run: npm clean-install - run: corepack npm audit signatures - run: npm test test: runs-on: ubuntu-latest needs: test_matrix if: ${{ !cancelled() }} steps: - name: All matrix versions passed if: ${{ !(contains(needs.*.result, 'failure')) }} run: exit 0 - name: Some matrix version failed if: ${{ contains(needs.*.result, 'failure') }} run: exit 1 ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/macos,windows,linux,node ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/macos,windows,linux,node ================================================ FILE: .nvmrc ================================================ 24 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Pierre-Denis Vanduynslager Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # **release-notes-generator** [**semantic-release**](https://github.com/semantic-release/semantic-release) plugin to generate changelog content with [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) [![Build Status](https://github.com/semantic-release/release-notes-generator/workflows/Test/badge.svg)](https://github.com/semantic-release/release-notes-generator/actions?query=workflow%3ATest+branch%3Amaster) [![npm latest version](https://img.shields.io/npm/v/@semantic-release/release-notes-generator/latest.svg)](https://www.npmjs.com/package/@semantic-release/release-notes-generator) [![npm next version](https://img.shields.io/npm/v/@semantic-release/release-notes-generator/next.svg)](https://www.npmjs.com/package/@semantic-release/release-notes-generator) | Step | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `generateNotes` | Generate release notes for the commits added since the last release with [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog). | ## Install > [!TIP] > You do not need to directly depend on this package if you are using `semantic-release`. > `semantic-release` already depends on this package, and defining your own direct dependency can result in conflicts when you update `semantic-release`. ```bash $ npm install @semantic-release/release-notes-generator -D ``` ## Usage The plugin can be configured in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration): ```json { "plugins": [ [ "@semantic-release/commit-analyzer", { "preset": "angular", "parserOpts": { "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] } } ], [ "@semantic-release/release-notes-generator", { "preset": "angular", "parserOpts": { "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] }, "writerOpts": { "commitsSort": ["subject", "scope"] } } ] ] } ``` With this example: - the commits that contains `BREAKING CHANGE`, `BREAKING CHANGES` or `BREAKING` in their body will be considered breaking changes (by default the [angular preset](https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/index.js#L14) checks only for `BREAKING CHANGE` and `BREAKING CHANGES`) - the commits will be sorted in the changelog by `subject` then `scope` (by default the [angular preset](https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/index.js#L90) sort the commits in the changelog by `scope` then `subject`) ## Configuration ### Options | Option | Description | Default | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `preset` | [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) preset (possible values: [`angular`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular), [`atom`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-atom), [`codemirror`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-codemirror), [`ember`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-ember), [`eslint`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-eslint), [`express`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-express), [`jquery`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-jquery), [`jshint`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-jshint), [`conventionalcommits`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits)). | [`angular`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular) | | `config` | NPM package name of a custom [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) preset. | - | | `parserOpts` | Additional [conventional-commits-parser](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-commits-parser#parseroptions) options that will extends the ones loaded by `preset` or `config`. This is convenient to use a [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) preset with some customizations without having to create a new module. | - | | `writerOpts` | Additional [conventional-commits-writer](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#options) options that will extends the ones loaded by `preset` or `config`. This is convenient to use a [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) preset with some customizations without having to create a new module. | - | | `host` | The host used to generate links to issues and commits. See [conventional-changelog-writer#host](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#host). | The host from the [`repositoryurl` option](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#repositoryurl). | | `linkCompare` | Whether to include a link to compare changes since previous release in the release note. | `true` | | `linkReferences` | Whether to include a link to issues and commits in the release note. See [conventional-changelog-writer#linkreferences](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#linkreferences). | `true` | | `commit` | Keyword used to generate commit links (formatted as `////`). See [conventional-changelog-writer#commit](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#commit). | `commits` for Bitbucket repositories, `commit` otherwise | | `issue` | Keyword used to generate issue links (formatted as `////`). See [conventional-changelog-writer#issue](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#issue). | `issue` for Bitbucket repositories, `issues` otherwise | | `presetConfig` | Additional configuration passed to the [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) preset. Used for example with [conventional-changelog-conventionalcommits](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits#specific-options). | - | **Notes**: in order to use a `preset` it must be installed (for example to use the [eslint preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-eslint) you must install it with `npm install conventional-changelog-eslint -D`) **Note**: `config` will be overwritten by the values of `preset`. You should use either `preset` or `config`, but not both. **Note**: Individual properties of `parserOpts` and `writerOpts` will override ones loaded with an explicitly set `preset` or `config`. If `preset` or `config` are not set, only the properties set in `parserOpts` and `writerOpts` will be used. **Note**: For presets that expects a configuration object, such as [`conventionalcommits`](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits), the `presetConfig` option **must** be set. ================================================ FILE: index.js ================================================ import { format } from "url"; import { find, merge } from "lodash-es"; import getStream from "get-stream"; import intoStream from "into-stream"; 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"; const debug = debugFactory("semantic-release:release-notes-generator"); /** * Generate the changelog for all the commits in `options.commits`. * * @param {Object} pluginConfig The plugin configuration. * @param {String} pluginConfig.preset conventional-changelog preset ('angular', 'atom', 'codemirror', 'ember', 'eslint', 'express', 'jquery', 'jscs', 'jshint'). * @param {String} pluginConfig.config Requireable npm package with a custom conventional-changelog preset * @param {Object} pluginConfig.parserOpts Additional `conventional-changelog-parser` options that will overwrite ones loaded by `preset` or `config`. * @param {Object} pluginConfig.writerOpts Additional `conventional-changelog-writer` options that will overwrite ones loaded by `preset` or `config`. * @param {Object} context The semantic-release context. * @param {Array} context.commits The commits to analyze. * @param {Object} context.lastRelease The last release with `gitHead` corresponding to the commit hash used to make the last release and `gitTag` corresponding to the git tag associated with `gitHead`. * @param {Object} context.nextRelease The next release with `gitHead` corresponding to the commit hash used to make the release, the release `version` and `gitTag` corresponding to the git tag associated with `gitHead`. * @param {Object} context.options.repositoryUrl The git repository URL. * * @returns {String} The changelog for all the commits in `context.commits`. */ export async function generateNotes(pluginConfig, context) { const { commits, lastRelease, nextRelease, options, cwd } = context; const repositoryUrl = options.repositoryUrl.replace(/\.git$/i, ""); const { commitOpts, parserOpts, writerOpts } = await loadChangelogConfig(pluginConfig, context); const [match, auth, host, path] = /^(?!.+:\/\/)(?:(?.*)@)?(?.*?):(?.*)$/.exec(repositoryUrl) || []; let { hostname, port, pathname, protocol } = new URL( match ? `ssh://${auth ? `${auth}@` : ""}${host}/${path}` : repositoryUrl ); port = protocol.includes("ssh") ? "" : port; protocol = protocol && /http[^s]/.test(protocol) ? "http" : "https"; const [, owner, repository] = /^\/(?[^/]+)?\/?(?.+)?$/.exec(pathname) || []; const { issue, commit, referenceActions, issuePrefixes } = find(HOSTS_CONFIG, (conf) => conf.hostname === hostname) || HOSTS_CONFIG.default; const parser = new CommitParser({ referenceActions, issuePrefixes, ...parserOpts }); const parsedCommits = filterRevertedCommitsSync( commits .filter(({ message, hash }) => { if (!message.trim()) { debug("Skip commit %s with empty message", hash); return false; } if (commitOpts && commitOpts.ignore && new RegExp(commitOpts.ignore).test(message) && !commitOpts.merges) { debug("Skip commit %s by ignore option", hash); return false; } return true; }) .map((rawCommit) => ({ ...rawCommit, ...parser.parse(rawCommit.message), })) ); const previousTag = lastRelease.gitTag || lastRelease.gitHead; const currentTag = nextRelease.gitTag || nextRelease.gitHead; const { host: hostConfig, linkCompare, linkReferences, commit: commitConfig, issue: issueConfig } = pluginConfig; const changelogContext = merge( { version: nextRelease.version, host: format({ protocol, hostname, port }), owner, repository, previousTag, currentTag, linkCompare: currentTag && previousTag, issue, commit, packageData: ((await readPackageUp({ normalize: false, cwd })) || {}).packageJson, }, { host: hostConfig, linkCompare, linkReferences, commit: commitConfig, issue: issueConfig } ); debug("version: %o", changelogContext.version); debug("host: %o", changelogContext.hostname); debug("owner: %o", changelogContext.owner); debug("repository: %o", changelogContext.repository); debug("previousTag: %o", changelogContext.previousTag); debug("currentTag: %o", changelogContext.currentTag); debug("host: %o", changelogContext.host); debug("linkReferences: %o", changelogContext.linkReferences); debug("issue: %o", changelogContext.issue); debug("commit: %o", changelogContext.commit); return getStream(intoStream.object(parsedCommits).pipe(writer(changelogContext, writerOpts))); } ================================================ FILE: lib/hosts-config.js ================================================ export default { github: { hostname: "github.com", issue: "issues", commit: "commit", referenceActions: ["close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"], issuePrefixes: ["#", "gh-"], }, bitbucket: { hostname: "bitbucket.org", issue: "issue", commit: "commits", referenceActions: [ "close", "closes", "closed", "closing", "fix", "fixes", "fixed", "fixing", "resolve", "resolves", "resolved", "resolving", ], issuePrefixes: ["#"], }, gitlab: { hostname: "gitlab.com", issue: "issues", commit: "commit", referenceActions: ["close", "closes", "closed", "closing", "fix", "fixes", "fixed", "fixing"], issuePrefixes: ["#"], }, default: { issue: "issues", commit: "commit", referenceActions: [ "close", "closes", "closed", "closing", "fix", "fixes", "fixed", "fixing", "resolve", "resolves", "resolved", "resolving", ], issuePrefixes: ["#", "gh-"], }, }; ================================================ FILE: lib/load-changelog-config.js ================================================ import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import importFrom from "import-from-esm"; import conventionalChangelogAngular from "conventional-changelog-angular"; /** * Load `conventional-changelog-parser` options. Handle presets that return either a `Promise` or a `Promise`. * * @param {Object} pluginConfig The plugin configuration. * @param {Object} pluginConfig.preset conventional-changelog preset ('angular', 'atom', 'codemirror', 'ember', 'eslint', 'express', 'jquery', 'jscs', 'jshint') * @param {string} pluginConfig.config Requireable npm package with a custom conventional-changelog preset * @param {Object} pluginConfig.parserOpts Additional `conventional-changelog-parser` options that will overwrite ones loaded by `preset` or `config`. * @param {Object} pluginConfig.writerOpts Additional `conventional-changelog-writer` options that will overwrite ones loaded by `preset` or `config`. * @param {Object} context The semantic-release context. * @param {Array} context.commits The commits to analyze. * @param {String} context.cwd The current working directory. * * @return {Promise} a `Promise` that resolve to the `conventional-changelog-core` config. */ export default async ({ preset, config, parserOpts, writerOpts, presetConfig }, { cwd }) => { let loadedConfig; const __dirname = dirname(fileURLToPath(import.meta.url)); if (preset) { const presetPackage = `conventional-changelog-${preset.toLowerCase()}`; loadedConfig = await ( (await importFrom.silent(__dirname, presetPackage)) || (await importFrom(cwd, presetPackage)) )(presetConfig); } else if (config) { loadedConfig = await ((await importFrom.silent(__dirname, config)) || (await importFrom(cwd, config)))(); } else { loadedConfig = await conventionalChangelogAngular(); } return { commitOpts: loadedConfig.commits, parserOpts: { ...loadedConfig.parser, ...parserOpts }, writerOpts: { ...loadedConfig.writer, ...writerOpts }, }; }; ================================================ FILE: package.json ================================================ { "name": "@semantic-release/release-notes-generator", "description": "semantic-release plugin to generate changelog content with conventional-changelog", "version": "0.0.0-development", "author": "Pierre Vanduynslager (https://twitter.com/@pvdlg_)", "bugs": { "url": "https://github.com/semantic-release/release-notes-generator/issues" }, "contributors": [ "Stephan Bönnemann (http://boennemann.me)", "Gregor Martynus (https://twitter.com/gr2m)" ], "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", "get-stream": "^7.0.0", "import-from-esm": "^2.0.0", "into-stream": "^7.0.0", "lodash-es": "^4.17.21", "read-package-up": "^11.0.0" }, "devDependencies": { "ava": "6.4.1", "c8": "11.0.0", "conventional-changelog-atom": "5.1.0", "conventional-changelog-conventionalcommits": "9.3.1", "conventional-changelog-ember": "5.1.0", "conventional-changelog-eslint": "6.1.0", "conventional-changelog-express": "5.1.0", "conventional-changelog-jshint": "5.2.0", "escape-string-regexp": "5.0.0", "fs-extra": "11.3.4", "lockfile-lint": "5.0.0", "ls-engines": "0.9.4", "npm-run-all2": "8.0.4", "prettier": "3.8.3", "publint": "0.3.18", "semantic-release": "25.0.3", "sinon": "21.1.2", "stream-buffers": "3.0.3", "tempy": "3.2.0", "testdouble": "3.20.2" }, "engines": { "node": ">=20.8.1" }, "files": [ "lib", "wrappers", "index.js" ], "homepage": "https://github.com/semantic-release/release-notes-generator#readme", "keywords": [ "changelog", "conventional-changelog", "conventional-commits", "github", "publish", "release", "release-note-generator", "semantic-release" ], "license": "MIT", "main": "./index.js", "exports": "./index.js", "c8": { "include": [ "lib/**/*.js", "index.js" ], "reporter": [ "json", "text", "html" ], "all": true }, "peerDependencies": { "semantic-release": ">=20.1.0" }, "lockfile-lint": { "path": "package-lock.json", "type": "npm", "validate-https": true, "allowed-hosts": [ "npm" ] }, "prettier": { "printWidth": 120, "trailingComma": "es5" }, "publishConfig": { "access": "public", "provenance": true }, "repository": { "type": "git", "url": "https://github.com/semantic-release/release-notes-generator.git" }, "scripts": { "lint:prettier": "prettier --check \"{lib,test}/**/*.{js,json,ts}\" \"*.{md,json,js}\" \".github/**/*.yml\"", "lint:prettier:fix": "prettier --write \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"{bin,lib,test}/**/*.js\"", "lint:lockfile": "lockfile-lint", "lint:engines": "ls-engines", "lint:publish": "publint --strict", "test": "npm-run-all --print-label --parallel lint:* --parallel test:*", "test:unit": "c8 ava --verbose", "test:integration": "ava --verbose test/integration.test.js" }, "type": "module", "ava": { "files": [ "test/**/*.test.js", "!test/integration.test.js" ], "nodeArguments": [ "--loader=testdouble", "--no-warnings" ] }, "renovate": { "extends": [ "github>semantic-release/.github:renovate-config" ] }, "packageManager": "npm@11.12.1" } ================================================ FILE: test/integration.test.js ================================================ import path from "node:path"; import test from "ava"; import fs from "fs-extra"; import escape from "escape-string-regexp"; import { temporaryDirectory } from "tempy"; import * as td from "testdouble"; import streamBuffers from "stream-buffers"; import conventionalChangelogEslint from "conventional-changelog-eslint"; const cwd = process.cwd(); const host = "https://github.com"; const owner = "owner"; const repository = "repo"; const repositoryUrl = `${host}/${owner}/${repository}`; const lastRelease = { gitTag: "v1.0.0" }; const nextRelease = { gitTag: "v2.0.0", version: "2.0.0" }; test.afterEach.always(() => { td.reset(); }); test.serial('Use "conventional-changelog-angular" by default', 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({}, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](https://github.com/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://github.com/owner/repo/commit/222))")) ); }); test.serial("Set conventional-changelog-writer context", async (t) => { t.plan(0); const cwd = temporaryDirectory(); const writerDouble = td.func(); td.when( writerDouble({ version: nextRelease.version, host, owner, repository, previousTag: lastRelease.gitTag, currentTag: nextRelease.gitTag, linkCompare: lastRelease.gitTag, issue: "issues", commit: "commit", packageData: undefined, linkReferences: undefined, }), { ignoreExtraArgs: true } ).thenReturn(new streamBuffers.WritableStreamBuffer()); await td.replaceEsm("../wrappers/conventional-changelog-writer.js", {}, writerDouble); const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; await generateNotes({}, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }); }); test.serial("Set conventional-changelog-writer context with package.json", async (t) => { t.plan(0); const cwd = temporaryDirectory(); const packageData = { name: "package", version: "0.0.0" }; const writerDouble = td.func(); td.when( writerDouble({ version: nextRelease.version, host, owner, repository, previousTag: lastRelease.gitTag, currentTag: nextRelease.gitTag, linkCompare: lastRelease.gitTag, issue: "issues", commit: "commit", packageData, linkReferences: undefined, }), { ignoreExtraArgs: true } ).thenReturn(new streamBuffers.WritableStreamBuffer()); await td.replaceEsm("../wrappers/conventional-changelog-writer.js", {}, writerDouble); const { generateNotes } = await import("../index.js"); await fs.outputJson(path.resolve(cwd, "package.json"), packageData); const commits = [ { hash: "111", message: "fix(scope1): First fix" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; await generateNotes({}, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }); }); test.serial('Accept a "preset" option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "Fix: First fix (fixes #123)" }, { hash: "222", message: "Update: Second feature (fixes #456)" }, ]; const changelog = await generateNotes( { preset: "eslint" }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Fix/); t.regex( changelog, new RegExp( escape( "* First fix (fixes #123) ([111](https://github.com/owner/repo/commit/111)), closes [#123](https://github.com/owner/repo/issues/123)" ) ) ); t.regex(changelog, /### Update/); t.regex( changelog, new RegExp( escape( "* Second feature (fixes #456) ([222](https://github.com/owner/repo/commit/222)), closes [#456](https://github.com/owner/repo/issues/456)" ) ) ); }); test.serial('Accept a "config" option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "Fix: First fix (fixes #123)" }, { hash: "222", message: "Update: Second feature (fixes #456)" }, ]; const changelog = await generateNotes( { config: "conventional-changelog-eslint" }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Fix/); t.regex( changelog, new RegExp( escape( "* First fix (fixes #123) ([111](https://github.com/owner/repo/commit/111)), closes [#123](https://github.com/owner/repo/issues/123)" ) ) ); t.regex(changelog, /### Update/); t.regex( changelog, new RegExp( escape( "* Second feature (fixes #456) ([222](https://github.com/owner/repo/commit/222)), closes [#456](https://github.com/owner/repo/issues/456)" ) ) ); }); test.serial('Accept a "parseOpts" and "writerOpts" objects as option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "%%Fix%% First fix (keyword #123)" }, { hash: "222", message: "%%Update%% Second feature (keyword JIRA-456)" }, ]; const changelog = await generateNotes( { parserOpts: { headerPattern: /^%%(?.*?)%% (?.*)$/, headerCorrespondence: ["tag", "message"], referenceActions: ["keyword"], issuePrefixes: ["#", "JIRA-"], }, writerOpts: (await conventionalChangelogEslint()).writer, }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Fix/); t.regex( changelog, new RegExp( escape( "* First fix (keyword #123) ([111](https://github.com/owner/repo/commit/111)), closes [#123](https://github.com/owner/repo/issues/123)" ) ) ); t.regex(changelog, /### Update/); t.regex( changelog, new RegExp( escape( "* Second feature (keyword JIRA-456) ([222](https://github.com/owner/repo/commit/222)), closes [#456](https://github.com/owner/repo/issues/456)" ) ) ); }); test.serial('Accept a partial "parseOpts" and "writerOpts" objects as option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): 2 First fix (fixes #123)" }, { hash: "222", message: "fix(scope2): 1 Second fix (fixes #456)" }, ]; const changelog = await generateNotes( { preset: "angular", parserOpts: { headerPattern: /^(?\w*)\((?.*)\): (?.*)$/ }, writerOpts: { commitsSort: ["subject", "scope"] }, }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, /\* \*\*scope2:\*\* 1 Second fix[\S\s]*\* \*\*scope1:\*\* 2 First fix/); }); test.serial('Accept a partial "presetConfig" object as option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix: First fix" }, { hash: "222", message: "test: Change test" }, ]; const changelog = await generateNotes( { preset: "conventionalcommits", presetConfig: { types: [ { type: "fix", section: "Bug Fixes", hidden: true }, { type: "test", section: "Test !!", hidden: false }, ], }, }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ); t.notRegex(changelog, /### Bug Fixes/); t.notRegex(changelog, new RegExp(escape("First fix"))); t.regex(changelog, /### Test !!/); t.regex(changelog, new RegExp(escape("* Change test ([222](https://github.com/owner/repo/commit/222))"))); }); test.serial('Accept ignoreCommits in "presetConfig" object as option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix: First fix" }, { hash: "222", message: "test: Change test [python]" }, ]; const changelog = await generateNotes( { preset: "conventionalcommits", presetConfig: { ignoreCommits: "\\[python\\]", types: [ { type: "fix", section: "Bug Fixes", hidden: false }, { type: "test", section: "Test !!", hidden: false }, ], }, }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("First fix"))); t.notRegex(changelog, /### Test !!/); t.notRegex(changelog, new RegExp(escape("* Change test ([222](https://github.com/owner/repo/commit/222))"))); }); test.serial('Use "gitHead" from "lastRelease" and "nextRelease" if "gitTag" is not defined', 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( {}, { cwd, options: { repositoryUrl }, lastRelease: { gitHead: "abc" }, nextRelease: { gitHead: "def", version: "2.0.0" }, commits, } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/abc...def)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](https://github.com/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://github.com/owner/repo/commit/222))")) ); }); test.serial("Accept a custom repository URL", 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( {}, { cwd, options: { repositoryUrl: "http://domain.com:90/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(http://domain.com:90/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](http://domain.com:90/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](http://domain.com:90/owner/repo/commit/222))")) ); }); test.serial("Accept a custom repository URL with git format", 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( {}, { cwd, options: { repositoryUrl: "git@domain.com:owner/repo.git" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://domain.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](https://domain.com/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://domain.com/owner/repo/commit/222))")) ); }); test.serial("Accept a custom repository URL with git format without user", 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( {}, { cwd, options: { repositoryUrl: "domain.com:owner/repo.git" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://domain.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](https://domain.com/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://domain.com/owner/repo/commit/222))")) ); }); test.serial("Accept a custom repository URL with git+http format", 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( {}, { cwd, options: { repositoryUrl: "git+http://domain.com:90/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(http://domain.com:90/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](http://domain.com:90/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](http://domain.com:90/owner/repo/commit/222))")) ); }); test.serial('Accept a custom repository URL with ".git" extension', 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( {}, { cwd, options: { repositoryUrl: "https://domain.com:90/owner/repo.git" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://domain.com:90/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp(escape("* **scope1:** First fix ([111](https://domain.com:90/owner/repo/commit/111))")) ); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://domain.com:90/owner/repo/commit/222))")) ); }); test.serial("Accept a custom repository URL with git+https format", async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix\n\nresolve #10" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; const changelog = await generateNotes( {}, { cwd, options: { repositoryUrl: "git+https://domain.com:90/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://domain.com:90/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp( escape( "* **scope1:** First fix ([111](https://domain.com:90/owner/repo/commit/111)), closes [#10](https://domain.com:90/owner/repo/issues/10)" ) ) ); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://domain.com:90/owner/repo/commit/222))")) ); }); test.serial("Accept a custom repository URL with git+ssh format and custom port", 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( {}, { cwd, options: { repositoryUrl: "git+ssh://git@domain.com:2222/owner/repo.git" }, lastRelease, nextRelease, commits, } ); t.regex(changelog, new RegExp(escape("(https://domain.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](https://domain.com/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://domain.com/owner/repo/commit/222))")) ); }); test.serial("Accept a Bitbucket repository URL", async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix\n\nResolves #10" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; const changelog = await generateNotes( {}, { cwd, options: { repositoryUrl: "git+https://bitbucket.org/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://bitbucket.org/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp( escape( "* **scope1:** First fix ([111](https://bitbucket.org/owner/repo/commits/111)), closes [#10](https://bitbucket.org/owner/repo/issue/10)" ) ) ); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://bitbucket.org/owner/repo/commits/222))")) ); }); test.serial("Accept a Gitlab repository URL", async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix\n\nclosed #10" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; const changelog = await generateNotes( {}, { cwd, options: { repositoryUrl: "git+https://gitlab.com/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://gitlab.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp( escape( "* **scope1:** First fix ([111](https://gitlab.com/owner/repo/commit/111)), closes [#10](https://gitlab.com/owner/repo/issues/10)" ) ) ); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://gitlab.com/owner/repo/commit/222))")) ); }); test.serial('Accept a "linkCompare" option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix\n\nResolves #10" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; const changelog = await generateNotes( { linkCompare: false }, { cwd, options: { repositoryUrl: "git+https://bitbucket.org/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("# 2.0.0"))); t.notRegex(changelog, new RegExp(escape("(https://bitbucket.org/owner/repo/compare/v1.0.0...v2.0.0)"))); }); test.serial('Accept a "linkReferences" option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix\n\nResolves #10" }, { hash: "222", message: "feat(scope2): Second feature" }, ]; const changelog = await generateNotes( { linkReferences: false }, { cwd, options: { repositoryUrl: "git+https://bitbucket.org/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix 111, closes #10"))); t.regex(changelog, /### Features/); t.regex(changelog, new RegExp(escape("* **scope2:** Second feature 222"))); }); test.serial('Accept a "host" option', 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( { host: "http://my-host:90" }, { cwd, options: { repositoryUrl: "https://github.com/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(http://my-host:90/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](http://my-host:90/owner/repo/commit/111))"))); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](http://my-host:90/owner/repo/commit/222))")) ); }); test.serial('Accept a "commit" option', 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( { commit: "test-commits" }, { cwd, options: { repositoryUrl: "https://github.com/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp(escape("* **scope1:** First fix ([111](https://github.com/owner/repo/test-commits/111))")) ); t.regex(changelog, /### Features/); t.regex( changelog, new RegExp(escape("* **scope2:** Second feature ([222](https://github.com/owner/repo/test-commits/222))")) ); }); test.serial('Accept an "issue" option', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [{ hash: "111", message: "fix(scope1): First fix\n\nresolve #10" }]; const changelog = await generateNotes( { issue: "test-issues" }, { cwd, options: { repositoryUrl: "https://github.com/owner/repo" }, lastRelease, nextRelease, commits } ); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp( escape( "* **scope1:** First fix ([111](https://github.com/owner/repo/commit/111)), closes [#10](https://github.com/owner/repo/test-issues/10)" ) ) ); }); test.serial("Ignore malformatted commits and include valid ones", async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix" }, { hash: "222", message: "Feature => Invalid message" }, ]; const changelog = await generateNotes({}, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, /\* \*\*scope1:\*\* First fix/); t.notRegex(changelog, /### Features/); t.notRegex(changelog, /Feature => Invalid message/); }); test.serial("Exclude commits if they have a matching revert commits", async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "df012f1", message: "revert: feat(scope2): First feature\n\nThis reverts commit df012f2.\n" }, { hash: "df012f2", message: "feat(scope2): First feature" }, { hash: "df012f3", message: "fix(scope1): First fix" }, ]; const changelog = await generateNotes({}, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex( changelog, new RegExp(escape("* **scope1:** First fix ([df012f3](https://github.com/owner/repo/commit/df012f3))")) ); t.notRegex(changelog, /### Features/); t.notRegex(changelog, /Second feature/); }); test.serial("Exclude commits with empty message", async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "fix(scope1): First fix" }, { hash: "222", message: "" }, { hash: "333", message: " " }, ]; const changelog = await generateNotes({}, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }); t.regex(changelog, new RegExp(escape("(https://github.com/owner/repo/compare/v1.0.0...v2.0.0)"))); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix ([111](https://github.com/owner/repo/commit/111))"))); t.notRegex(changelog, /222/); t.notRegex(changelog, /333/); }); test.serial('Throw error if "preset" doesn`t exist', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "Fix: First fix (fixes #123)" }, { hash: "222", message: "Update: Second feature (fixes #456)" }, ]; await t.throwsAsync( generateNotes({ preset: "unknown-preset" }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }), { code: "MODULE_NOT_FOUND" } ); }); test.serial('Throw error if "config" doesn`t exist', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "Fix: First fix (fixes #123)" }, { hash: "222", message: "Update: Second feature (fixes #456)" }, ]; await t.throwsAsync( generateNotes({ config: "unknown-config" }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits }), { code: "MODULE_NOT_FOUND" } ); }); test.serial('ReThrow error from "conventional-changelog"', async (t) => { const { generateNotes } = await import("../index.js"); const commits = [ { hash: "111", message: "Fix: First fix (fixes #123)" }, { hash: "222", message: "Update: Second feature (fixes #456)" }, ]; await t.throwsAsync( generateNotes( { writerOpts: { transform() { throw new Error("Test error"); }, }, }, { cwd, options: { repositoryUrl }, lastRelease, nextRelease, commits } ), { message: "Test error" } ); }); test("Accept a custom AWS CodeCommit repository URL", 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( {}, { cwd, options: { repositoryUrl: "codecommit::eu-central-1://profile@repository-name", }, lastRelease, nextRelease, commits, } ); t.regex(changelog, /### Bug Fixes/); t.regex(changelog, new RegExp(escape("* **scope1:** First fix 111"))); t.regex(changelog, /### Features/); t.regex(changelog, new RegExp(escape("* **scope2:** Second feature 222"))); }); ================================================ FILE: test/load-changelog-config.test.js ================================================ import test from "ava"; import importFrom from "import-from-esm"; import sinon from "sinon"; import conventionalChangelogAngular from "conventional-changelog-angular"; import loadChangelogConfig from "../lib/load-changelog-config.js"; const cwd = process.cwd(); /** * assertion to compare loaded writerOpts with the expected writerOpts from the angular preset * * @param {Object} t AVA assertion library. * @param {Object} loadedWriterOpts * @param {Object} angularPresetWriterOpts */ function assertWriterOptsAreFromAngularPreset(t, loadedWriterOpts, angularPresetWriterOpts) { const { transform: loadedTransform, ...loadedWriterOptsWithoutTransform } = loadedWriterOpts; const { transform: angularPresetTransform, ...angularPresetWriterOptsWithoutTransform } = angularPresetWriterOpts; t.deepEqual(loadedWriterOptsWithoutTransform, angularPresetWriterOptsWithoutTransform); t.is(loadedTransform.toString(), angularPresetTransform.toString()); } /** * AVA macro to verify that `loadChangelogConfig` return a config object with parserOpts and writerOpts. * * @method loadPreset * @param {Object} t AVA assertion library. * @param {String} preset the `conventional-changelog` preset to test. * @param {Object} pluginOptions The plugin configuration. */ async function loadPreset(t, preset, pluginOptions) { const changelogConfig = await loadChangelogConfig({ ...pluginOptions, preset }, { cwd }); t.truthy(changelogConfig.parserOpts.headerPattern); t.truthy(changelogConfig.writerOpts.groupBy); } loadPreset.title = (providedTitle, preset) => `${providedTitle} Load "${preset}" preset`.trim(); /** * AVA macro to verify that `loadChangelogConfig` return a config object with parserOpts and writerOpts. * * @method loadPreset * @param {Object} t AVA assertion library. * @param {String} config the `conventional-changelog` config to test. * @param {Object} pluginOptions The plugin configuration. */ async function loadConfig(t, config, pluginOptions) { const changelogConfig = await loadChangelogConfig( { ...pluginOptions, config: `conventional-changelog-${config}` }, { cwd } ); t.truthy(changelogConfig.parserOpts.headerPattern); t.truthy(changelogConfig.writerOpts.groupBy); } loadConfig.title = (providedTitle, config) => `${providedTitle} Load "${config}" config`.trim(); test('Load "conventional-changelog-angular" by default', async (t) => { const changelogConfig = await loadChangelogConfig({}, { cwd }); const angularChangelogConfig = await conventionalChangelogAngular(); t.deepEqual(changelogConfig.parserOpts, angularChangelogConfig.parser); assertWriterOptsAreFromAngularPreset(t, changelogConfig.writerOpts, angularChangelogConfig.writer); }); test('Accept a "parserOpts" object as option', async (t) => { const customParserOptions = { headerPattern: /^##(?.*?)## (?.*)$/, headerCorrespondence: ["tag", "shortDesc"], }; const changelogConfig = await loadChangelogConfig({ parserOpts: customParserOptions }, { cwd }); const angularChangelogConfig = await conventionalChangelogAngular(); t.is(customParserOptions.headerPattern, changelogConfig.parserOpts.headerPattern); t.deepEqual(customParserOptions.headerCorrespondence, changelogConfig.parserOpts.headerCorrespondence); t.deepEqual(changelogConfig.parserOpts.noteKeywords, angularChangelogConfig.parser.noteKeywords); assertWriterOptsAreFromAngularPreset(t, changelogConfig.writerOpts, angularChangelogConfig.writer); }); test('Accept a "writerOpts" object as option', async (t) => { const customWriterOptions = { commitGroupsSort: "title", commitsSort: ["scope", "subject"] }; const changelogConfig = await loadChangelogConfig({ writerOpts: customWriterOptions }, { cwd }); const angularChangelogConfig = await conventionalChangelogAngular(); t.is(customWriterOptions.commitGroupsSort, changelogConfig.writerOpts.commitGroupsSort); t.deepEqual(customWriterOptions.commitsSort, changelogConfig.writerOpts.commitsSort); t.deepEqual(changelogConfig.writerOpts.noteGroupsSort, angularChangelogConfig.writer.noteGroupsSort); t.deepEqual(changelogConfig.parserOpts, angularChangelogConfig.parser); }); test('Accept a partial "parserOpts" object as option that overwrite a preset', async (t) => { const customParserOptions = { headerPattern: /^##(?.*?)## (?.*)$/, headerCorrespondence: ["tag", "shortDesc"], }; const changelogConfig = await loadChangelogConfig({ parserOpts: customParserOptions, preset: "angular" }, { cwd }); const angularChangelogConfig = await conventionalChangelogAngular(); t.is(customParserOptions.headerPattern, changelogConfig.parserOpts.headerPattern); t.deepEqual(customParserOptions.headerCorrespondence, changelogConfig.parserOpts.headerCorrespondence); t.truthy(changelogConfig.parserOpts.noteKeywords); assertWriterOptsAreFromAngularPreset(t, changelogConfig.writerOpts, angularChangelogConfig.writer); }); test('Accept a "writerOpts" object as option that overwrite a preset', async (t) => { const customWriterOptions = { commitGroupsSort: "title", commitsSort: ["scope", "subject"] }; const changelogConfig = await loadChangelogConfig({ writerOpts: customWriterOptions, preset: "angular" }, { cwd }); const angularChangelogConfig = await conventionalChangelogAngular(); t.is(customWriterOptions.commitGroupsSort, changelogConfig.writerOpts.commitGroupsSort); t.deepEqual(customWriterOptions.commitsSort, changelogConfig.writerOpts.commitsSort); t.truthy(changelogConfig.writerOpts.noteGroupsSort); t.deepEqual(changelogConfig.parserOpts, angularChangelogConfig.parser); }); test('Accept a partial "parserOpts" object as option that overwrite a config', async (t) => { const customParserOptions = { headerPattern: /^##(?.*?)## (?.*)$/, headerCorrespondence: ["tag", "shortDesc"], }; const changelogConfig = await loadChangelogConfig( { parserOpts: customParserOptions, config: "conventional-changelog-angular", }, { cwd } ); const angularChangelogConfig = await conventionalChangelogAngular(); t.is(customParserOptions.headerPattern, changelogConfig.parserOpts.headerPattern); t.deepEqual(customParserOptions.headerCorrespondence, changelogConfig.parserOpts.headerCorrespondence); t.truthy(changelogConfig.parserOpts.noteKeywords); assertWriterOptsAreFromAngularPreset(t, changelogConfig.writerOpts, angularChangelogConfig.writer); }); test('Accept a "writerOpts" object as option that overwrite a config', async (t) => { const customWriterOptions = { commitGroupsSort: "title", commitsSort: ["scope", "subject"] }; const changelogConfig = await loadChangelogConfig( { writerOpts: customWriterOptions, config: "conventional-changelog-angular", }, { cwd } ); const angularChangelogConfig = await conventionalChangelogAngular(); t.is(customWriterOptions.commitGroupsSort, changelogConfig.writerOpts.commitGroupsSort); t.deepEqual(customWriterOptions.commitsSort, changelogConfig.writerOpts.commitsSort); t.truthy(changelogConfig.writerOpts.noteGroupsSort); t.deepEqual(changelogConfig.parserOpts, angularChangelogConfig.parser); }); test(loadPreset, "angular"); test(loadConfig, "angular"); test(loadPreset, "atom"); test(loadConfig, "atom"); test(loadPreset, "ember"); test(loadConfig, "ember"); test(loadPreset, "eslint"); test(loadConfig, "eslint"); test(loadPreset, "express"); test(loadConfig, "express"); test(loadPreset, "jshint"); test(loadConfig, "jshint"); test(loadPreset, "conventionalcommits", { presetConfig: {} }); test(loadConfig, "conventionalcommits", { presetConfig: {} }); test('Throw error if "config" doesn`t exist', async (t) => { await t.throwsAsync(loadChangelogConfig({ config: "unknown-config" }, { cwd }), { code: "MODULE_NOT_FOUND" }); }); test('Throw error if "preset" doesn`t exist', async (t) => { await t.throwsAsync(loadChangelogConfig({ preset: "unknown-preset" }, { cwd }), { code: "MODULE_NOT_FOUND" }); }); test.serial("Load preset and config correctly when importFrom.silent fails", async (t) => { sinon.stub(importFrom, "silent").returns(undefined); await loadPreset(t, "angular"); await loadConfig(t, "angular"); sinon.restore(); }); ================================================ FILE: wrappers/conventional-changelog-writer.js ================================================ import { writeChangelogStream as writer } from 'conventional-changelog-writer'; export default writer;