feat(release): add conventional commits configurability for version and changelog (#22004)

Co-authored-by: Isaac Mann <isaacplmann@users.noreply.github.com>
This commit is contained in:
Austin Fahsl 2024-03-13 18:19:24 -06:00 committed by GitHub
parent 73a8091b93
commit cbb88f0d29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 5492 additions and 76 deletions

View File

@ -2331,6 +2331,14 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Customize Conventional Commit Types",
"path": "/recipes/nx-release/customize-conventional-commit-types",
"id": "customize-conventional-commit-types",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@ -4163,6 +4171,14 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Customize Conventional Commit Types",
"path": "/recipes/nx-release/customize-conventional-commit-types",
"id": "customize-conventional-commit-types",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@ -4223,6 +4239,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Customize Conventional Commit Types",
"path": "/recipes/nx-release/customize-conventional-commit-types",
"id": "customize-conventional-commit-types",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Other",
"path": "/recipes/other",

View File

@ -3188,6 +3188,17 @@
"isExternal": false,
"path": "/recipes/nx-release/update-local-registry-setup",
"tags": ["nx-release"]
},
{
"id": "customize-conventional-commit-types",
"name": "Customize Conventional Commit Types",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/customize-conventional-commit-types",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/customize-conventional-commit-types",
"tags": ["nx-release"]
}
],
"isExternal": false,
@ -5700,6 +5711,17 @@
"isExternal": false,
"path": "/recipes/nx-release/update-local-registry-setup",
"tags": ["nx-release"]
},
{
"id": "customize-conventional-commit-types",
"name": "Customize Conventional Commit Types",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/customize-conventional-commit-types",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/customize-conventional-commit-types",
"tags": ["nx-release"]
}
],
"isExternal": false,
@ -5783,6 +5805,17 @@
"path": "/recipes/nx-release/update-local-registry-setup",
"tags": ["nx-release"]
},
"/recipes/nx-release/customize-conventional-commit-types": {
"id": "customize-conventional-commit-types",
"name": "Customize Conventional Commit Types",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/customize-conventional-commit-types",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/customize-conventional-commit-types",
"tags": ["nx-release"]
},
"/recipes/other": {
"id": "other",
"name": "Other",

View File

@ -1036,6 +1036,13 @@
"id": "update-local-registry-setup",
"name": "Update Your Local Registry Setup to use Nx Release",
"path": "/recipes/nx-release/update-local-registry-setup"
},
{
"description": "",
"file": "shared/recipes/nx-release/customize-conventional-commit-types",
"id": "customize-conventional-commit-types",
"name": "Customize Conventional Commit Types",
"path": "/recipes/nx-release/customize-conventional-commit-types"
}
],
"database": [

View File

@ -1155,6 +1155,12 @@
"id": "update-local-registry-setup",
"tags": ["nx-release"],
"file": "shared/recipes/nx-release/update-local-registry-setup"
},
{
"name": "Customize Conventional Commit Types",
"id": "customize-conventional-commit-types",
"tags": ["nx-release"],
"file": "shared/recipes/nx-release/customize-conventional-commit-types"
}
]
},

View File

@ -32,7 +32,7 @@ For example, if the git history looks like this:
- chore(release): 1.0.0
```
then Nx Release will select the `minor` version bump and elect to release version 1.1.0. This is because there is a `feat` commit since the last release of 1.0.0.
then Nx Release will select the `minor` version bump and elect to release version 1.1.0. This is because there is a `feat` commit since the last release of 1.0.0. To customize the version bump for different types of commits, or to trigger a version bump with custom commit types, see the [Customize Conventional Commit Types](/recipes/nx-release/customize-conventional-commit-types) recipe.
{% callout type="info" title="No changes detected" %}
If Nx Release does not find any relevant commits since the last release, it will skip releasing a new version. This works with [independent releases](/recipes/nx-release/release-projects-independently) as well, allowing for only some projects to be released and some to be skipped.

View File

@ -0,0 +1,112 @@
# Customize Conventional Commit Types
Nx Release can defer to the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard to automatically determine the next version to release. To enable this behavior for versioning, see [Automatically Version with Conventional Commits](/recipes/nx-release/automatically-version-with-conventional-commits).
This recipe will cover how to customize the types of commits that trigger version bumps, how to customize the version bump for each type, and how to customize the changelog entry for each commit type.
## Conventional Commits Usage within Nx Release
The conventional commits configuration is used in two different places within Nx Release - once in the version step for determining the version bump, and once when generating changelogs.
### Determine the Version Bump
When `release.version.conventionalCommits` is `true` in `nx.json`, Nx Release will use the commit messages since the last release to determine the version bump. It will look at the type of each commit and determine the highest version bump from the following list:
- 'feat' -> minor
- 'fix' -> patch
For example, if the git history looks like this:
```
- fix(pkg-1): fix something
- feat(pkg-2): add a new feature
- chore(pkg-3): update docs
- chore(release): 1.0.0
```
then Nx Release will select the `minor` version bump and elect to release version 1.1.0. This is because there is a `feat` commit since the last release of 1.0.0. To customize the version bump for different types of commits, or to trigger a version bump with custom commit types, see the [Configure Commit Types](#configure-commit-types) section below.
{% callout type="info" title="No changes detected" %}
If Nx Release does not find any relevant commits since the last release, it will skip releasing a new version. This works with [independent releases](/recipes/nx-release/release-projects-independently) as well, allowing for only some projects to be released and some to be skipped.
{% /callout %}
#### Breaking Changes and Major Version Bumps
Major version bumps are triggered by the presence of a `BREAKING CHANGE` in the footer of the commit message or with '!' after the commit type and scope, as specified by the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard. This is regardless of the type or scope of the commit. For example:
```
fix: remove deprecated config properties
BREAKING CHANGE: `settings` and `overrides` keys in config are no longer supported
```
```
fix!: do not trigger a workflow when user submits bad data
```
```
feat(pkg-2)!: redirect users to the new workflow page
```
When Nx Release detects a breaking change, it will bump the major version, regardless of the other commits present in the history. Breaking changes will also appear in their own section of the changelog.
### Generate Changelog Sections
Nx Release will sort changes within changelogs into sections based on the type of commit. By default, `fix`, `feat`, and `perf` commits will be included in the changelog. To customize the headers of changelog sections, include other commit types, or exclude the default commit types, see the [Configure Commit Types](#configure-commit-types) section below.
See the [Nx repo](https://github.com/nrwl/nx/releases) for an example of a changelogs generated with Nx Release.
## Configure Commit Types
Commit types are configured in the `release.conventionalCommits.types` property in `nx.json`:
```json {% fileName="nx.json" %}
{
"release": {
"conventionalCommits": {
"types": {
// disable the fix type for versioning and in the changelog
"fix": false,
"docs": {
"semverBump": "patch",
"changelog": {
"hidden": false,
"title": "Documentation Changes"
}
},
"perf": {
"semverBump": "none",
// omitting "hidden" will default it to false
"changelog": {
"title": "Performance Improvements"
}
},
"deps": {
"semverBump": "minor",
// omitting "hidden" will default it to false
"changelog": {
"title": "Dependency Updates"
}
},
// unspecified semverBump will default to "patch"
"chore": {
// "changelog.hidden" defaults to true, but setting changelog: false
// is a shortcut for setting "changelog.hidden" to false.
"changelog": false
},
// unspecified semverBump will default to "patch"
"styles": {}
}
}
}
}
```
In this example, the following types are configured:
- The `fix` type has been fully disabled, so `fix` commits will not trigger a version bump and will not be included in the changelog.
- The `docs` type will trigger a `patch` version bump and will have the "Documentation Changes" title in the changelog.
- The `perf` type will NOT trigger a version bump and will have the "Performance Improvements" title in the changelog.
- The `deps` type will trigger a `minor` version bump and will have the "Dependency Updates" title in the changelog.
- The `chore` type will trigger a `patch` version bump, which is the default for if `versionBump` is not specified, and will not be included in the changelog.
- The `styles` type will trigger a `patch` version bump, which is the default for if `versionBump` is not specified, and will be included in the changelog with the corresponding default title.

View File

@ -184,6 +184,7 @@
- [Automate GitHub Releases](/recipes/nx-release/automate-github-releases)
- [Publish Rust Crates](/recipes/nx-release/publish-rust-crates)
- [Update Your Local Registry Setup to use Nx Release](/recipes/nx-release/update-local-registry-setup)
- [Customize Conventional Commit Types](/recipes/nx-release/customize-conventional-commit-types)
- [Other](/recipes/other)
- [Rescope Packages from @nrwl to @nx](/recipes/other/rescope)
- [Showcase](/showcase)

View File

@ -0,0 +1,433 @@
import { NxJsonConfiguration } from '@nx/devkit';
import {
cleanupProject,
newProject,
readFile,
runCLI,
runCommandAsync,
uniq,
updateJson,
} from '@nx/e2e/utils';
expect.addSnapshotSerializer({
serialize(str: string) {
return (
str
// Remove all output unique to specific projects to ensure deterministic snapshots
.replaceAll(/my-pkg-\d+/g, '{project-name}')
.replaceAll(
/integrity:\s*.*/g,
'integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
)
.replaceAll(/\b[0-9a-f]{40}\b/g, '{SHASUM}')
.replaceAll(/\d*B index\.js/g, 'XXB index.js')
.replaceAll(/\d*B project\.json/g, 'XXB project.json')
.replaceAll(/\d*B package\.json/g, 'XXXB package.json')
.replaceAll(/size:\s*\d*\s?B/g, 'size: XXXB')
.replaceAll(/\d*\.\d*\s?kB/g, 'XXX.XXX kb')
.replaceAll(/[a-fA-F0-9]{7}/g, '{COMMIT_SHA}')
.replaceAll(/Test @[\w\d]+/g, 'Test @{COMMIT_AUTHOR}')
// Normalize the version title date.
.replaceAll(/\(\d{4}-\d{2}-\d{2}\)/g, '(YYYY-MM-DD)')
// We trim each line to reduce the chances of snapshot flakiness
.split('\n')
.map((r) => r.trim())
.join('\n')
);
},
test(val: string) {
return val != null && typeof val === 'string';
},
});
describe('nx release conventional commits config', () => {
let pkg1: string;
let pkg2: string;
let pkg3: string;
let pkg4: string;
let pkg5: string;
let pkg6: string;
beforeAll(async () => {
newProject({
unsetProjectNameAndRootFormat: false,
packages: ['@nx/js'],
});
pkg1 = uniq('my-pkg-1');
runCLI(`generate @nx/workspace:npm-package ${pkg1}`);
pkg2 = uniq('my-pkg-2');
runCLI(`generate @nx/workspace:npm-package ${pkg2}`);
pkg3 = uniq('my-pkg-3');
runCLI(`generate @nx/workspace:npm-package ${pkg3}`);
pkg4 = uniq('my-pkg-4');
runCLI(`generate @nx/workspace:npm-package ${pkg4}`);
pkg5 = uniq('my-pkg-5');
runCLI(`generate @nx/workspace:npm-package ${pkg5}`);
pkg6 = uniq('my-pkg-6');
runCLI(`generate @nx/workspace:npm-package ${pkg6}`);
// Update pkg2 to depend on pkg1
updateJson(`${pkg2}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${pkg1}`] = '0.0.0';
return json;
});
// no git config so that the test ensures git operations happen by default
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
projectsRelationship: 'independent',
};
return nxJson;
});
await runCommandAsync(`git add .`);
await runCommandAsync(`git commit -m "chore: initial commit"`);
await runCommandAsync(`git tag -a ${pkg1}@0.0.1 -m "${pkg1}@0.0.1"`);
await runCommandAsync(`git tag -a ${pkg2}@0.0.1 -m "${pkg2}@0.0.1"`);
await runCommandAsync(`git tag -a ${pkg3}@0.0.1 -m "${pkg3}@0.0.1"`);
await runCommandAsync(`git tag -a ${pkg4}@0.0.1 -m "${pkg4}@0.0.1"`);
await runCommandAsync(`git tag -a ${pkg5}@0.0.1 -m "${pkg5}@0.0.1"`);
await runCommandAsync(`git tag -a ${pkg6}@0.0.1 -m "${pkg6}@0.0.1"`);
}, 60000);
afterAll(() => cleanupProject());
it('should respect custom conventional commits configuration', async () => {
updateJson<NxJsonConfiguration>('nx.json', (json) => {
json.release = {
...json.release,
version: {
conventionalCommits: true,
},
changelog: {
projectChangelogs: {
renderOptions: {
authors: false, // do not show authors in the e2e snapshots
},
},
},
conventionalCommits: {
types: {
fix: false,
docs: {
semverBump: 'patch',
// no hidden property set, but the user has explicitly overridden the `docs` type, so we assume they want it to appear
changelog: {
title: 'Custom Docs Header',
},
},
customType: {
semverBump: 'minor',
changelog: {
title: 'Custom Type',
},
},
// unspecified semverBump will default to "patch"
chore: {
// "changelog.hidden" defaults to true, but setting changelog: false
// is a shortcut for setting "changelog.hidden" to false.
changelog: false,
},
perf: {},
// true should be the same as specifying {}
refactor: true,
build: {
semverBump: 'none',
// true should be the same as specifying {}
changelog: true,
},
},
},
};
return json;
});
// update my-pkg-1 with a fix commit
updateJson(`${pkg1}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg1}/package.json`);
await runCommandAsync(`git commit -m "fix: this is a fix"`);
updateJson(`${pkg6}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg6}/package.json`);
await runCommandAsync(`git commit -m "build: this is a build"`);
const versionResultNoChanges = runCLI(`release version -d`);
expect(versionResultNoChanges).toContain(
`${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.`
);
expect(versionResultNoChanges).toContain(
`${pkg2} 🚫 Skipping versioning "@proj/${pkg2}" as no changes were detected.`
);
expect(versionResultNoChanges).toContain(
`${pkg3} 🚫 Skipping versioning "@proj/${pkg3}" as no changes were detected.`
);
expect(versionResultNoChanges).toContain(
`${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.`
);
expect(versionResultNoChanges).toContain(
`${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.`
);
expect(versionResultNoChanges).toContain(
`${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.`
);
// update my-pkg-3 with a fix commit
updateJson(`${pkg3}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg3}/package.json`);
await runCommandAsync(`git commit -m "docs: this is a doc"`);
const versionResultDocsChanges = runCLI(`release version -d`);
expect(versionResultDocsChanges).toContain(
`${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.`
);
expect(versionResultDocsChanges).toContain(
`${pkg2} 🚫 Skipping versioning "@proj/${pkg2}" as no changes were detected.`
);
expect(versionResultDocsChanges).toContain(
`${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json`
);
expect(versionResultDocsChanges).toContain(
`${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.`
);
expect(versionResultDocsChanges).toContain(
`${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.`
);
expect(versionResultDocsChanges).toContain(
`${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.`
);
// update my-pkg-2 with a fix commit
updateJson(`${pkg2}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg2}/package.json`);
await runCommandAsync(
`git commit -m "customType(${pkg2}): this is a custom type"`
);
const versionResultCustomTypeChanges = runCLI(`release version -d`);
expect(versionResultCustomTypeChanges).toContain(
`${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.`
);
expect(versionResultCustomTypeChanges).toContain(
`${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json`
);
expect(versionResultCustomTypeChanges).toContain(
`${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json`
);
expect(versionResultCustomTypeChanges).toContain(
`${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.`
);
expect(versionResultCustomTypeChanges).toContain(
`${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.`
);
expect(versionResultCustomTypeChanges).toContain(
`${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.`
);
updateJson(`${pkg1}/package.json`, (json) => ({
...json,
license: 'UNLICENSED',
}));
await runCommandAsync(`git add ${pkg1}/package.json`);
await runCommandAsync(
`git commit -m "customType(${pkg1})!: this is a breaking change"`
);
const versionResultCustomTypeBreakingChanges = runCLI(`release version -d`);
expect(versionResultCustomTypeBreakingChanges).toContain(
`${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json`
);
expect(versionResultCustomTypeBreakingChanges).toContain(
`${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json`
);
expect(versionResultCustomTypeBreakingChanges).toContain(
`${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json`
);
expect(versionResultCustomTypeBreakingChanges).toContain(
`${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.`
);
expect(versionResultCustomTypeBreakingChanges).toContain(
`${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.`
);
expect(versionResultCustomTypeBreakingChanges).toContain(
`${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.`
);
updateJson(`${pkg4}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg4}/package.json`);
await runCommandAsync(`git commit -m "chore: this is a chore"`);
const versionResultChoreChanges = runCLI(`release version -d`);
expect(versionResultChoreChanges).toContain(
`${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json`
);
expect(versionResultChoreChanges).toContain(
`${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json`
);
expect(versionResultChoreChanges).toContain(
`${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json`
);
expect(versionResultChoreChanges).toContain(
`${pkg4} ✍️ New version 0.0.2 written to ${pkg4}/package.json`
);
expect(versionResultChoreChanges).toContain(
`${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.`
);
expect(versionResultChoreChanges).toContain(
`${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.`
);
updateJson(`${pkg5}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg5}/package.json`);
await runCommandAsync(
`git commit -m "perf: this is a performance improvement"`
);
const versionResultPerfChanges = runCLI(`release version -d`);
expect(versionResultPerfChanges).toContain(
`${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json`
);
expect(versionResultPerfChanges).toContain(
`${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json`
);
expect(versionResultPerfChanges).toContain(
`${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json`
);
expect(versionResultPerfChanges).toContain(
`${pkg4} ✍️ New version 0.0.2 written to ${pkg4}/package.json`
);
expect(versionResultPerfChanges).toContain(
`${pkg5} ✍️ New version 0.0.2 written to ${pkg5}/package.json`
);
expect(versionResultPerfChanges).toContain(
`${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.`
);
updateJson(`${pkg6}/package.json`, (json) => ({
...json,
license: 'GNU GPLv3',
}));
await runCommandAsync(`git add ${pkg6}/package.json`);
await runCommandAsync(`git commit -m "refactor: this is refactor"`);
const versionResultRefactorChanges = runCLI(`release version -d`);
expect(versionResultRefactorChanges).toContain(
`${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json`
);
expect(versionResultRefactorChanges).toContain(
`${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json`
);
expect(versionResultRefactorChanges).toContain(
`${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json`
);
expect(versionResultRefactorChanges).toContain(
`${pkg4} ✍️ New version 0.0.2 written to ${pkg4}/package.json`
);
expect(versionResultRefactorChanges).toContain(
`${pkg5} ✍️ New version 0.0.2 written to ${pkg5}/package.json`
);
expect(versionResultRefactorChanges).toContain(
`${pkg6} ✍️ New version 0.0.2 written to ${pkg6}/package.json`
);
// Normally, users would use `nx release` or the programmatic api to ensure that
// changelogs are generated for the above version bumps, but for the sake of this
// test, we just want to ensure that each commit is included/excluded as expected.
// Therefore, any version number will work - in this case it's 1.0.0.
runCLI(`release changelog 1.0.0`);
const pkg1Changelog = readFile(`${pkg1}/CHANGELOG.md`);
expect(pkg1Changelog).toMatchInlineSnapshot(`
# 1.0.0 (YYYY-MM-DD)
### Custom Type
- **{project-name}:** this is a breaking change
#### Breaking Changes
- **{project-name}:** this is a breaking change
`);
const pkg2Changelog = readFile(`${pkg2}/CHANGELOG.md`);
expect(pkg2Changelog).toMatchInlineSnapshot(`
# 1.0.0 (YYYY-MM-DD)
### Custom Type
- **{project-name}:** this is a custom type
`);
const pkg3Changelog = readFile(`${pkg3}/CHANGELOG.md`);
expect(pkg3Changelog).toMatchInlineSnapshot(`
# 1.0.0 (YYYY-MM-DD)
### Custom Docs Header
- this is a doc
`);
// NOTE: pkg4 should not have changes here, since its only commit is a chore.
// Chore commits are hidden from changelogs in the above config via `changelog: false`.
const pkg4Changelog = readFile(`${pkg4}/CHANGELOG.md`);
expect(pkg4Changelog).toMatchInlineSnapshot(`
# 1.0.0 (YYYY-MM-DD)
This was a version bump only for {project-name} to align it with other projects, there were no code changes.
`);
const pkg5Changelog = readFile(`${pkg5}/CHANGELOG.md`);
expect(pkg5Changelog).toMatchInlineSnapshot(`
# 1.0.0 (YYYY-MM-DD)
### 🔥 Performance
- this is a performance improvement
`);
const pkg6Changelog = readFile(`${pkg6}/CHANGELOG.md`);
expect(pkg6Changelog).toMatchInlineSnapshot(`
# 1.0.0 (YYYY-MM-DD)
### 💅 Refactors
- this is refactor
### 📦 Build
- this is a build
`);
});
});

View File

@ -271,7 +271,7 @@ describe('nx release - private JS packages', () => {
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to http://localhost:4873 with tag "latest"
Published to ${e2eRegistryUrl} with tag "latest"
> nx run {public-project-name}:nx-release-publish
@ -292,7 +292,7 @@ describe('nx release - private JS packages', () => {
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to http://localhost:4873 with tag "latest"
Published to ${e2eRegistryUrl} with tag "latest"

View File

@ -332,7 +332,8 @@ To fix this you will either need to add a package.json file at that location, or
specifier = await resolveSemverSpecifierFromConventionalCommits(
previousVersionRef,
options.projectGraph,
affectedProjects
affectedProjects,
options.conventionalCommitsConfig
);
if (!specifier) {

View File

@ -1,3 +1,4 @@
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from '../../src/command-line/release/config/conventional-commits';
import type { GitCommit } from '../../src/command-line/release/utils/git';
import defaultChangelogRenderer from './index';
@ -180,6 +181,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
});
expect(markdown).toMatchInlineSnapshot(`
"## v1.1.0
@ -213,6 +215,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: false,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
});
expect(markdown).toMatchInlineSnapshot(`
"# v1.0.0
@ -242,6 +245,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
};
expect(
@ -333,6 +337,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
};
expect(
@ -363,6 +368,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
};
expect(
@ -435,6 +441,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
});
expect(markdown).toMatchInlineSnapshot(`
@ -535,6 +542,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
});
expect(markdown).toMatchInlineSnapshot(`""`);
@ -580,6 +588,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
});
expect(markdown).toMatchInlineSnapshot(`
@ -641,6 +650,7 @@ describe('defaultChangelogRenderer()', () => {
changelogRenderOptions: {
authors: true,
},
conventionalCommitsConfig: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
});
expect(markdown).toMatchInlineSnapshot(`

View File

@ -1,4 +1,5 @@
import { major } from 'semver';
import { NxReleaseConfig } from '../../src/command-line/release/config/config';
import type { GitCommit } from '../../src/command-line/release/utils/git';
import {
RepoSlug,
@ -37,6 +38,7 @@ export type ChangelogRenderer = (config: {
entryWhenNoChanges: string | false;
changelogRenderOptions: DefaultChangelogRenderOptions;
repoSlug?: RepoSlug;
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits'];
}) => Promise<string> | string;
/**
@ -73,26 +75,12 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
entryWhenNoChanges,
changelogRenderOptions,
repoSlug,
conventionalCommitsConfig,
}): Promise<string> => {
const commitTypes = conventionalCommitsConfig.types;
const markdownLines: string[] = [];
const breakingChanges = [];
const commitTypes = {
feat: { title: '🚀 Features' },
perf: { title: '🔥 Performance' },
fix: { title: '🩹 Fixes' },
refactor: { title: '💅 Refactors' },
docs: { title: '📖 Documentation' },
build: { title: '📦 Build' },
types: { title: '🌊 Types' },
chore: { title: '🏡 Chore' },
examples: { title: '🏀 Examples' },
test: { title: '✅ Tests' },
style: { title: '🎨 Styles' },
ci: { title: '🤖 CI' },
revert: { title: '⏪ Revert' },
};
// If the current range of commits contains both a commit and its revert, we strip them both from the final list
for (const commit of commits) {
if (commit.type === 'revert') {
@ -139,7 +127,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
continue;
}
markdownLines.push('', '### ' + commitTypes[type].title, '');
markdownLines.push('', '### ' + commitTypes[type].changelog.title, '');
/**
* In order to make the final changelog most readable, we organize commits as follows:
@ -215,7 +203,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
continue;
}
markdownLines.push('', `### ${commitTypes[type].title}`, '');
markdownLines.push('', `### ${commitTypes[type].changelog.title}`, '');
const commitsInChronologicalOrder = group.reverse();
for (const commit of commitsInChronologicalOrder) {

View File

@ -221,6 +221,9 @@
}
}
},
"conventionalCommits": {
"$ref": "#/definitions/NxReleaseConventionalCommitsConfiguration"
},
"projectsRelationship": {
"type": "string",
"enum": ["fixed", "independent"]
@ -636,6 +639,55 @@
"ChangelogRenderOptions": {
"type": "object",
"additionalProperties": true
},
"NxReleaseConventionalCommitsConfiguration": {
"type": "object",
"properties": {
"types": {
"type": "object",
"description": "A map of commit types to their configuration. If a type is set to 'true', then it will be enabled with the default 'semverBump' of 'patch' and will appear in the changelog. If a type is set to 'false', then it will not trigger a version bump and will be hidden from the changelog.",
"additionalProperties": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "object",
"properties": {
"semverBump": {
"type": "string",
"enum": ["major", "minor", "patch", "none"],
"description": "The semver bump to apply to the version of the project(s) when a commit of this type is included in the release.",
"default": "patch"
},
"changelog": {
"description": "Configuration for the changelog section for commits of this type. If set to 'true', then commits of this type will be included in the changelog with their default title for the type. If set to 'false', then commits of this type will not be included in the changelog.",
"oneOf": [
{
"type": "boolean"
},
{
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the section in the changelog for commits of this type"
},
"hidden": {
"type": "boolean",
"description": "Whether or not to include commits of this type in the changelog",
"default": false
}
}
}
]
}
}
}
]
}
}
}
}
}
}

View File

@ -241,7 +241,8 @@ export async function releaseChangelog(
const workspaceChangelogCommits = await getCommits(
workspaceChangelogFromSHA,
toSHA
toSHA,
nxReleaseConfig.conventionalCommits
);
const workspaceChangelog = await generateChangelogForWorkspace(
@ -317,7 +318,11 @@ export async function releaseChangelog(
if (!fromRef && useAutomaticFromRef) {
const firstCommit = await getFirstGitCommit();
const allCommits = await getCommits(firstCommit, toSHA);
const allCommits = await getCommits(
firstCommit,
toSHA,
nxReleaseConfig.conventionalCommits
);
const commitsForProject = allCommits.filter((c) =>
c.affectedFiles.find((f) => f.startsWith(project.data.root))
);
@ -338,7 +343,11 @@ export async function releaseChangelog(
}
if (!commits) {
commits = await getCommits(fromRef, toSHA);
commits = await getCommits(
fromRef,
toSHA,
nxReleaseConfig.conventionalCommits
);
}
const projectChangelogs = await generateChangelogForProjects(
@ -347,9 +356,9 @@ export async function releaseChangelog(
projectGraph,
commits,
projectsVersionData,
postGitTasks,
releaseGroup,
[project]
[project],
nxReleaseConfig
);
let hasPushed = false;
@ -402,7 +411,11 @@ export async function releaseChangelog(
// Make sure that the fromRef is actually resolvable
const fromSHA = await getCommitHash(fromRef);
const commits = await getCommits(fromSHA, toSHA);
const commits = await getCommits(
fromSHA,
toSHA,
nxReleaseConfig.conventionalCommits
);
const projectChangelogs = await generateChangelogForProjects(
tree,
@ -410,9 +423,9 @@ export async function releaseChangelog(
projectGraph,
commits,
projectsVersionData,
postGitTasks,
releaseGroup,
projectNodes
projectNodes,
nxReleaseConfig
);
let hasPushed = false;
@ -744,6 +757,7 @@ async function generateChangelogForWorkspace(
repoSlug: githubRepoSlug,
entryWhenNoChanges: config.entryWhenNoChanges,
changelogRenderOptions: config.renderOptions,
conventionalCommitsConfig: nxReleaseConfig.conventionalCommits,
});
/**
@ -806,9 +820,9 @@ async function generateChangelogForProjects(
projectGraph: ProjectGraph,
commits: GitCommit[],
projectsVersionData: VersionData,
postGitTasks: PostGitTask[],
releaseGroup: ReleaseGroupWithName,
projects: ProjectGraphProjectNode[]
projects: ProjectGraphProjectNode[],
nxReleaseConfig: NxReleaseConfig
): Promise<NxReleaseChangelogResult['projectChangelogs']> {
const config = releaseGroup.changelog;
// The entire feature is disabled at the release group level, exit early
@ -879,6 +893,7 @@ async function generateChangelogForProjects(
})
: false,
changelogRenderOptions: config.renderOptions,
conventionalCommitsConfig: nxReleaseConfig.conventionalCommits,
});
/**
@ -960,17 +975,22 @@ function checkChangelogFilesEnabled(nxReleaseConfig: NxReleaseConfig): boolean {
return false;
}
async function getCommits(fromSHA: string, toSHA: string) {
async function getCommits(
fromSHA: string,
toSHA: string,
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
): Promise<GitCommit[]> {
const rawCommits = await getGitDiff(fromSHA, toSHA);
// Parse as conventional commits
return parseCommits(rawCommits).filter((c) => {
const type = c.type;
// Always ignore non user-facing commits for now
// TODO: allow this filter to be configurable via config in a future release
if (type === 'feat' || type === 'fix' || type === 'perf') {
return true;
}
const typeConfig = conventionalCommitsConfig.types[type];
if (!typeConfig) {
// don't include commits with unknown types
return false;
}
return !typeConfig.changelog.hidden;
});
}

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ import { output } from '../../../utils/output';
import { PackageJson } from '../../../utils/package-json';
import { workspaceRoot } from '../../../utils/workspace-root';
import { resolveNxJsonConfigErrorMessage } from '../utils/resolve-nx-json-error-message';
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from './conventional-commits';
type DeepRequired<T> = Required<{
[K in keyof T]: T[K] extends Required<T[K]> ? T[K] : DeepRequired<T[K]>;
@ -39,6 +40,22 @@ type RemoveTrueFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
[U in keyof T]: RemoveTrueFromProperties<T[U], K>;
};
type RemoveFalseFromType<T> = T extends false ? never : T;
type RemoveFalseFromProperties<T, K extends keyof T> = {
[P in keyof T]: P extends K ? RemoveFalseFromType<T[P]> : T[P];
};
type RemoveFalseFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
[U in keyof T]: RemoveFalseFromProperties<T[U], K>;
};
type RemoveBooleanFromType<T> = T extends boolean ? never : T;
type RemoveBooleanFromProperties<T, K extends keyof T> = {
[P in keyof T]: P extends K ? RemoveBooleanFromType<T[P]> : T[P];
};
type RemoveBooleanFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
[U in keyof T]: RemoveBooleanFromProperties<T[U], K>;
};
export const IMPLICIT_DEFAULT_RELEASE_GROUP = '__default__';
/**
@ -64,6 +81,20 @@ export type NxReleaseConfig = Omit<
DeepRequired<NxJsonConfiguration['release']['changelog']>,
'workspaceChangelog' | 'projectChangelogs'
>;
// Remove the false shorthand from the conventionalCommits config types, it will be normalized to a semver bump of "none" and to be hidden on the changelog
conventionalCommits: {
types: RemoveBooleanFromPropertiesOnEach<
DeepRequired<
RemoveBooleanFromProperties<
DeepRequired<
NxJsonConfiguration['release']['conventionalCommits']['types']
>,
string
>
>,
'changelog'
>;
};
}
>,
// projects is just a shorthand for the default group's projects configuration, it does not exist in the final config
@ -210,6 +241,7 @@ export async function createNxReleaseConfig(
(workspaceProjectsRelationship === 'independent'
? defaultIndependentReleaseTagPattern
: defaultFixedReleaseTagPattern),
conventionalCommits: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
};
const groupProjectsRelationship =
@ -286,6 +318,16 @@ export async function createNxReleaseConfig(
>
);
const rootConventionalCommitsConfig: NxReleaseConfig['conventionalCommits'] =
deepMergeDefaults(
[WORKSPACE_DEFAULTS.conventionalCommits],
fillUnspecifiedConventionalCommitsProperties(
normalizeConventionalCommitsConfig(
userConfig.conventionalCommits
) as NxReleaseConfig['conventionalCommits']
)
);
// these options are not supported at the group level, only the root/command level
const rootVersionWithoutGlobalOptions = { ...rootVersionConfig };
delete rootVersionWithoutGlobalOptions.git;
@ -470,6 +512,7 @@ export async function createNxReleaseConfig(
version: rootVersionConfig,
changelog: rootChangelogConfig,
groups: releaseGroups,
conventionalCommits: rootConventionalCommitsConfig,
},
};
}
@ -483,6 +526,103 @@ function normalizeTrueToEmptyObject<T>(value: T | boolean): T | {} {
return value === true ? {} : value;
}
function normalizeConventionalCommitsConfig(
userConventionalCommitsConfig: NxJsonConfiguration['release']['conventionalCommits']
): NxJsonConfiguration['release']['conventionalCommits'] {
if (!userConventionalCommitsConfig || !userConventionalCommitsConfig.types) {
return userConventionalCommitsConfig;
}
const types: NxJsonConfiguration['release']['conventionalCommits']['types'] =
{};
for (const [t, typeConfig] of Object.entries(
userConventionalCommitsConfig.types
)) {
if (typeConfig === false) {
types[t] = {
semverBump: 'none',
changelog: {
hidden: true,
},
};
continue;
}
if (typeConfig === true) {
types[t] = {};
continue;
}
if (typeConfig.changelog === false) {
types[t] = {
...typeConfig,
changelog: {
hidden: true,
},
};
continue;
}
if (typeConfig.changelog === true) {
types[t] = {
...typeConfig,
changelog: {},
};
continue;
}
types[t] = typeConfig;
}
return {
...userConventionalCommitsConfig,
types,
};
}
/**
* New, custom types specified by users will not be given the appropriate
* defaults with `deepMergeDefaults`, so we need to fill in the gaps here.
*/
function fillUnspecifiedConventionalCommitsProperties(
config: NxReleaseConfig['conventionalCommits']
) {
if (!config || !config.types) {
return config;
}
const types: NxReleaseConfig['conventionalCommits']['types'] = {};
for (const [t, typeConfig] of Object.entries(config.types)) {
const defaultTypeConfig = DEFAULT_CONVENTIONAL_COMMITS_CONFIG.types[t];
const semverBump =
typeConfig.semverBump ||
// preserve our default semver bump if it's not 'none'
// this prevents a 'feat' from becoming a 'patch' just
// because they modified the changelog config for 'feat'
(defaultTypeConfig?.semverBump !== 'none' &&
defaultTypeConfig?.semverBump) ||
'patch';
// don't preserve our default behavior for hidden, ever.
// we should assume that if users are explicitly enabling a
// type, then they intend it to be visible in the changelog
const hidden = typeConfig.changelog?.hidden || false;
const title =
typeConfig.changelog?.title ||
// our default title is better than just the unmodified type name
defaultTypeConfig?.changelog.title ||
t;
types[t] = {
semverBump,
changelog: {
hidden,
title,
},
};
}
return {
...config,
types,
};
}
export async function handleNxReleaseConfigError(
error: CreateNxReleaseConfigError
): Promise<never> {

View File

@ -0,0 +1,98 @@
import { NxReleaseConfig } from './config';
export const DEFAULT_CONVENTIONAL_COMMITS_CONFIG: NxReleaseConfig['conventionalCommits'] =
{
types: {
feat: {
semverBump: 'minor',
changelog: {
title: '🚀 Features',
hidden: false,
},
},
fix: {
semverBump: 'patch',
changelog: {
title: '🩹 Fixes',
hidden: false,
},
},
perf: {
semverBump: 'none',
changelog: {
title: '🔥 Performance',
hidden: false,
},
},
refactor: {
semverBump: 'none',
changelog: {
title: '💅 Refactors',
hidden: true,
},
},
docs: {
semverBump: 'none',
changelog: {
title: '📖 Documentation',
hidden: true,
},
},
build: {
semverBump: 'none',
changelog: {
title: '📦 Build',
hidden: true,
},
},
types: {
semverBump: 'none',
changelog: {
title: '🌊 Types',
hidden: true,
},
},
chore: {
semverBump: 'none',
changelog: {
title: '🏡 Chore',
hidden: true,
},
},
examples: {
semverBump: 'none',
changelog: {
title: '🏀 Examples',
hidden: true,
},
},
test: {
semverBump: 'none',
changelog: {
title: '✅ Tests',
hidden: true,
},
},
style: {
semverBump: 'none',
changelog: {
title: '🎨 Styles',
hidden: true,
},
},
ci: {
semverBump: 'none',
changelog: {
title: '🤖 CI',
hidden: true,
},
},
revert: {
semverBump: 'none',
changelog: {
title: '⏪ Revert',
hidden: true,
},
},
},
};

View File

@ -1,5 +1,6 @@
import { type ProjectGraph } from '../../../devkit-exports';
import { IMPLICIT_DEFAULT_RELEASE_GROUP, NxReleaseConfig } from './config';
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from './conventional-commits';
import { filterReleaseGroups } from './filter-release-groups';
describe('filterReleaseGroups()', () => {
@ -49,6 +50,7 @@ describe('filterReleaseGroups()', () => {
tagArgs: '',
stageChanges: false,
},
conventionalCommits: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
};
projectGraph = {
nodes: {

View File

@ -1,27 +1,16 @@
import { prompt } from 'enquirer';
import { RELEASE_TYPES, valid } from 'semver';
import { ProjectGraph } from '../../../config/project-graph';
import { createFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils';
import { NxReleaseConfig } from '../config/config';
import { getGitDiff, parseCommits } from './git';
import { ConventionalCommitsConfig, determineSemverChange } from './semver';
import { determineSemverChange } from './semver';
import { getCommitsRelevantToProjects } from './shared';
// TODO: Extract config to nx.json configuration when adding changelog customization
const CONVENTIONAL_COMMITS_CONFIG: ConventionalCommitsConfig = {
types: {
feat: {
semver: 'minor',
},
fix: {
semver: 'patch',
},
},
};
export async function resolveSemverSpecifierFromConventionalCommits(
from: string,
projectGraph: ProjectGraph,
projectNames: string[]
projectNames: string[],
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
): Promise<string | null> {
const commits = await getGitDiff(from);
const parsedCommits = parseCommits(commits);
@ -30,7 +19,7 @@ export async function resolveSemverSpecifierFromConventionalCommits(
parsedCommits,
projectNames
);
return determineSemverChange(relevantCommits, CONVENTIONAL_COMMITS_CONFIG);
return determineSemverChange(relevantCommits, conventionalCommitsConfig);
}
export async function resolveSemverSpecifierFromPrompt(

View File

@ -1,9 +1,7 @@
import { NxReleaseConfig } from '../config/config';
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from '../config/conventional-commits';
import { GitCommit } from './git';
import {
ConventionalCommitsConfig,
deriveNewSemverVersion,
determineSemverChange,
} from './semver';
import { deriveNewSemverVersion, determineSemverChange } from './semver';
describe('semver', () => {
describe('deriveNewSemverVersion()', () => {
@ -76,16 +74,19 @@ describe('semver', () => {
});
// tests for determineSemverChange()
describe('determineSemverChange()', () => {
const config: ConventionalCommitsConfig = {
const config: NxReleaseConfig['conventionalCommits'] = {
types: {
feat: {
semver: 'minor',
semverBump: 'minor',
changelog: DEFAULT_CONVENTIONAL_COMMITS_CONFIG.types.feat.changelog,
},
fix: {
semver: 'patch',
semverBump: 'patch',
changelog: DEFAULT_CONVENTIONAL_COMMITS_CONFIG.types.fix.changelog,
},
chore: {
semver: 'patch',
semverBump: 'patch',
changelog: DEFAULT_CONVENTIONAL_COMMITS_CONFIG.types.chore.changelog,
},
},
};

View File

@ -4,6 +4,7 @@
*/
import { RELEASE_TYPES, ReleaseType, inc, valid } from 'semver';
import { NxReleaseConfig } from '../config/config';
import { GitCommit } from './git';
export function isRelativeVersionKeyword(val: string): val is ReleaseType {
@ -16,28 +17,22 @@ export function isValidSemverSpecifier(specifier: string): boolean {
);
}
export interface ConventionalCommitsConfig {
types: {
[type: string]: {
semver: 'patch' | 'minor' | 'major';
};
};
}
// https://github.com/unjs/changelogen/blob/main/src/semver.ts
export function determineSemverChange(
commits: GitCommit[],
config: ConventionalCommitsConfig
config: NxReleaseConfig['conventionalCommits']
): 'patch' | 'minor' | 'major' | null {
let [hasMajor, hasMinor, hasPatch] = [false, false, false];
for (const commit of commits) {
const semverType = config.types[commit.type]?.semver;
const semverType = config.types[commit.type]?.semverBump;
if (semverType === 'major' || commit.isBreaking) {
hasMajor = true;
} else if (semverType === 'minor') {
hasMinor = true;
} else if (semverType === 'patch') {
hasPatch = true;
} else if (semverType === 'none' || !semverType) {
// do not report a change
}
}

View File

@ -22,6 +22,7 @@ import { parseGeneratorString } from '../generate/generate';
import { getGeneratorInformation } from '../generate/generator-utils';
import { VersionOptions } from './command-object';
import {
NxReleaseConfig,
createNxReleaseConfig,
handleNxReleaseConfigError,
} from './config/config';
@ -71,6 +72,7 @@ export interface ReleaseVersionGeneratorSchema {
skipLockFileUpdate?: boolean;
installArgs?: string;
installIgnoreScripts?: boolean;
conventionalCommitsConfig?: NxReleaseConfig['conventionalCommits'];
}
export interface NxReleaseVersionResult {
@ -210,7 +212,8 @@ export async function releaseVersion(
args.generatorOptionsOverrides,
projectNames,
releaseGroup,
versionData
versionData,
nxReleaseConfig.conventionalCommits
);
// Capture the callback so that we can run it after flushing the changes to disk
generatorCallbacks.push(async () => {
@ -341,7 +344,8 @@ export async function releaseVersion(
args.generatorOptionsOverrides,
projectNames,
releaseGroup,
versionData
versionData,
nxReleaseConfig.conventionalCommits
);
// Capture the callback so that we can run it after flushing the changes to disk
generatorCallbacks.push(async () => {
@ -466,7 +470,8 @@ async function runVersionOnProjects(
generatorOverrides: Record<string, unknown> | undefined,
projectNames: string[],
releaseGroup: ReleaseGroupWithName,
versionData: VersionData
versionData: VersionData,
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
): Promise<ReleaseVersionGeneratorResult['callback']> {
const generatorOptions: ReleaseVersionGeneratorSchema = {
// Always ensure a string to avoid generator schema validation errors
@ -479,6 +484,7 @@ async function runVersionOnProjects(
projectGraph,
releaseGroup,
firstRelease: args.firstRelease ?? false,
conventionalCommitsConfig,
};
// Apply generator defaults from schema.json file etc

View File

@ -143,6 +143,36 @@ export interface NxReleaseGitConfiguration {
tagArgs?: string;
}
export interface NxReleaseConventionalCommitsConfiguration {
types?: Record<
string,
/**
* A map of commit types to their configuration.
* If a type is set to 'true', then it will be enabled with the default 'semverBump' of 'patch' and will appear in the changelog.
* If a type is set to 'false', then it will not trigger a version bump and will be hidden from the changelog.
*/
| {
/**
* The semver bump to apply when a commit of this type is found.
* If set to "none", the commit will be ignored for versioning purposes.
*/
semverBump?: 'patch' | 'minor' | 'major' | 'none';
/**
* Configuration for the changelog section for commits of this type.
* If set to 'true', then commits of this type will be included in the changelog with their default title for the type.
* If set to 'false', then commits of this type will not be included in the changelog.
*/
changelog?:
| {
title?: string;
hidden?: boolean;
}
| boolean;
}
| boolean
>;
}
interface NxReleaseConfiguration {
/**
* Shorthand for amending the projects which will be included in the implicit default release group (all projects by default).
@ -254,6 +284,7 @@ interface NxReleaseConfiguration {
* Enable and configure automatic git operations as part of the release
*/
git?: NxReleaseGitConfiguration;
conventionalCommits?: NxReleaseConventionalCommitsConfiguration;
}
/**