diff --git a/bump.mjs b/bump.mjs index 1a4ffdb..facbcac 100644 --- a/bump.mjs +++ b/bump.mjs @@ -1,59 +1,74 @@ /** * We have to manually figure out how to bump the package version based on the commits * as commit-and-tag-version doesn't seem to work with monorepos where scopes are used to - * differentiate packages. We build on the conventional-recommended-bump package to determine + * differentiate packages. We build on the @conventional-changelog/git-client package to determine * the bump type based on the commits since the last release for the current package. */ import * as path from 'path'; -import { Bumper } from 'conventional-recommended-bump'; import { readFileSync } from "fs"; +import { ConventionalGitClient } from '@conventional-changelog/git-client' const gitPath = path.resolve('./'); -const bumper = new Bumper(gitPath); -const packageName = JSON.parse(readFileSync('./package.json', 'utf8')).name; -await getBumpType(bumper, packageName); +const packageJson = JSON.parse(readFileSync('./package.json', 'utf8')) +const currentVersion = packageJson.version +const packageName = packageJson.name; +await getBumpType(packageName); - -async function getBumpType(bumper, packageName) { +async function getBumpType(packageName) { const PATCH = 0; const MINOR = 1; const MAJOR = 2; - await bumper.loadPreset('conventionalcommits') + const gitClient = new ConventionalGitClient(gitPath); - const recommendation = await bumper.bump( - (commits) => { - let recommendation = PATCH; + const commits = gitClient.getCommits({ + format: '%B%n-hash-%n%H', + from: `${packageName}@${currentVersion}`, + }) - // The last commit is the one that bumped the version for the current package - // It appears it is included in the list of commits, so we need to exclude it - const allCommitsAfterLastRelease = commits.slice(0, -1) + let recommendation = PATCH; - for (let commit of allCommitsAfterLastRelease) { - if (commit.scope === packageName) { - if (recommendation < MAJOR && commit.type === 'feat') { - recommendation = MINOR; - } + for await (const commit of commits) { - if (commit.notes.some((note) => note.title === 'BREAKING CHANGE')) { - recommendation = MAJOR; - } - } - } + // ConventionalGitClient does not seem to populate the scope for breaking changes with a ! + if (!commit.scope) { + const hasScope = commit.header.includes(`(${packageName})!:`) - return { - level: recommendation, - reason: 'Based on conventional commits', - releaseType: ['patch', 'minor', 'major'][recommendation] + if (hasScope) { + commit.scope = packageName; } } - ) + if (commit.scope === packageName) { + + // If the commit type is a fix, we recommend a patch release: + // From https://www.conventionalcommits.org/en/v1.0.0: + // "fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning)." + if (recommendation < MAJOR && commit.type === 'feat') { + recommendation = MINOR; + } + + // There are two ways to trigger a major release either by having a footer + // with BREAKING CHANGE or by having a ! in the header after the scope + // From https://www.conventionalcommits.org/en/v1.0.0: + // "BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type." + const majorBecauseHeaderHasBang = commit.header.includes(`(${packageName})!:`) + const majorBecauseFooterHasBreaking = commit.footer?.includes('BREAKING CHANGE') + + if (majorBecauseHeaderHasBang || majorBecauseFooterHasBreaking) { + recommendation = MAJOR; + + // We can break out of the loop as we have found a major release and the release type will not change + // for the rest of the commits once a major release is recommended + break; + } + } + } - console.log(recommendation.releaseType) + const releaseType = ['patch', 'minor', 'major'][recommendation] - // TODO: Process doesn't seem to exit - probably something async still running - process.exit(0) + // This is the logged out to be used as the release type for the package + console.log(releaseType) } diff --git a/guides/7.DEVELOPMENT.md b/guides/7.DEVELOPMENT.md index 8e9d48f..3d4bb58 100644 --- a/guides/7.DEVELOPMENT.md +++ b/guides/7.DEVELOPMENT.md @@ -43,6 +43,9 @@ In conventional commits, you can use the `!` to indicate a breaking change like This will increment the major version on release, i.e. v1.0.0 -> v2.0.0. `feat` will trigger a minor release update, i.e. v1.0.0 -> v1.1.0 and `fix` will trigger a patch, i.e. v1.0.0 -> v1.0.1. +>[!NOTE] +> The library used to write the CHANGELOG and bump the appropriate package on release, `commit-and-tag-version` is not able to correctly recommend the correct version when bumping the package. Instead we work around this using the `bump.mjs` script which accurately determines what the new package version should be, based on the conventional commit specification. It then passes this `commit-and-tag-version` via the `--release-as` flag which allows you to specifically set the release version. + ## Technologies Used - [TypeScript](https://www.typescriptlang.org/) - provides strong compile time typing for JavaScript diff --git a/package-lock.json b/package-lock.json index 71be2ac..422d552 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "devDependencies": { "@commitlint/cli": "17.1.2", "@commitlint/config-conventional": "17.1.0", + "@conventional-changelog/git-client": "^1.0.1", "@eslint/json": "0.9.0", "@eslint/markdown": "6.2.1", "@swc/jest": "0.2.36", @@ -2245,6 +2246,24 @@ "node": ">=v14" } }, + "node_modules/@commitlint/parse/node_modules/conventional-commits-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.3.5", + "meow": "^8.1.2", + "split2": "^3.2.2" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@commitlint/read": { "version": "17.8.1", "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.8.1.tgz", @@ -2333,6 +2352,31 @@ "node": ">=v14" } }, + "node_modules/@conventional-changelog/git-client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-1.0.1.tgz", + "integrity": "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==", + "dev": true, + "dependencies": { + "@types/semver": "^7.5.5", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -7348,6 +7392,37 @@ "node": ">=14" } }, + "node_modules/commit-and-tag-version/node_modules/conventional-commits-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", + "dev": true, + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/commit-and-tag-version/node_modules/conventional-commits-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.3.5", + "meow": "^8.1.2", + "split2": "^3.2.2" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/commit-and-tag-version/node_modules/conventional-recommended-bump": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-7.0.1.tgz", @@ -7678,6 +7753,24 @@ "node": ">=14" } }, + "node_modules/conventional-changelog-core/node_modules/conventional-commits-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.3.5", + "meow": "^8.1.2", + "split2": "^3.2.2" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/conventional-changelog-core/node_modules/git-raw-commits": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", @@ -7781,6 +7874,19 @@ "node": ">=14" } }, + "node_modules/conventional-changelog-writer/node_modules/conventional-commits-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", + "dev": true, + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/conventional-changelog/node_modules/conventional-changelog-conventionalcommits": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", @@ -7795,36 +7901,39 @@ } }, "node_modules/conventional-commits-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", - "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, - "license": "MIT", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.1" - }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/conventional-commits-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", - "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz", + "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", "dev": true, - "license": "MIT", "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.3.5", - "meow": "^8.1.2", - "split2": "^3.2.2" + "meow": "^13.0.0" }, "bin": { - "conventional-commits-parser": "cli.js" + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=14" + "node": ">=18" + } + }, + "node_modules/conventional-commits-parser/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/conventional-recommended-bump": { @@ -7846,31 +7955,6 @@ "node": ">=18" } }, - "node_modules/conventional-recommended-bump/node_modules/@conventional-changelog/git-client": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-1.0.1.tgz", - "integrity": "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==", - "dev": true, - "dependencies": { - "@types/semver": "^7.5.5", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0" - }, - "peerDependenciesMeta": { - "conventional-commits-filter": { - "optional": true - }, - "conventional-commits-parser": { - "optional": true - } - } - }, "node_modules/conventional-recommended-bump/node_modules/conventional-changelog-preset-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz", @@ -7880,30 +7964,6 @@ "node": ">=18" } }, - "node_modules/conventional-recommended-bump/node_modules/conventional-commits-filter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-recommended-bump/node_modules/conventional-commits-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz", - "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", - "dev": true, - "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/conventional-recommended-bump/node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -11965,7 +12025,6 @@ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, - "license": "MIT", "dependencies": { "text-extensions": "^1.0.0" }, @@ -13219,15 +13278,13 @@ "dev": true, "engines": [ "node >= 0.2.0" - ], - "license": "MIT" + ] }, "node_modules/JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, - "license": "(MIT OR Apache-2.0)", "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -13571,8 +13628,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", @@ -15287,7 +15343,6 @@ "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -19599,7 +19654,6 @@ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10" } diff --git a/package.json b/package.json index b090a24..507bbc9 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "devDependencies": { "@commitlint/cli": "17.1.2", "@commitlint/config-conventional": "17.1.0", + "@conventional-changelog/git-client": "^1.0.1", "@eslint/json": "0.9.0", "@eslint/markdown": "6.2.1", "@swc/jest": "0.2.36",