From 8560dbd1ac1a85424c33a41a1af59a3f89f4fc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85smund=20Grammeltvedt?= Date: Tue, 13 Apr 2021 14:17:38 +0200 Subject: [PATCH 1/5] feat: Support generating change files from conventional commits --- src/__e2e__/publishE2E.test.ts | 10 ++++- src/__e2e__/publishGit.test.ts | 2 + src/__e2e__/publishRegistry.test.ts | 5 +++ src/__e2e__/syncE2E.test.ts | 3 ++ .../changefile/conventionalCommits.test.ts | 13 ++++++ src/changefile/conventionalCommits.ts | 43 +++++++++++++++++++ src/changefile/promptForChange.ts | 23 ++++++---- src/options/getCliOptions.ts | 10 ++++- src/options/getDefaultOptions.ts | 1 + src/types/BeachballOptions.ts | 3 +- 10 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/changefile/conventionalCommits.test.ts create mode 100644 src/changefile/conventionalCommits.ts diff --git a/src/__e2e__/publishE2E.test.ts b/src/__e2e__/publishE2E.test.ts index 4e298a3a8..adb92618d 100644 --- a/src/__e2e__/publishE2E.test.ts +++ b/src/__e2e__/publishE2E.test.ts @@ -72,6 +72,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -140,6 +141,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -230,6 +232,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -323,6 +326,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -396,6 +400,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -474,6 +479,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -547,6 +553,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -627,6 +634,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -634,7 +642,7 @@ describe('publish command (e2e)', () => { bump: true, generateChangelog: true, hooks: { - postpublish: packagePath => { + postpublish: (packagePath) => { const packageJsonPath = path.join(packagePath, 'package.json'); const packageJson = fs.readJSONSync(packageJsonPath); if (packageJson.afterPublish) { diff --git a/src/__e2e__/publishGit.test.ts b/src/__e2e__/publishGit.test.ts index ff9645293..68e5882c5 100644 --- a/src/__e2e__/publishGit.test.ts +++ b/src/__e2e__/publishGit.test.ts @@ -64,6 +64,7 @@ describe('publish command (git)', () => { package: 'foo', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -125,6 +126,7 @@ describe('publish command (git)', () => { package: 'foo', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', diff --git a/src/__e2e__/publishRegistry.test.ts b/src/__e2e__/publishRegistry.test.ts index 1663b0009..9952f8b17 100644 --- a/src/__e2e__/publishRegistry.test.ts +++ b/src/__e2e__/publishRegistry.test.ts @@ -78,6 +78,7 @@ describe('publish command (registry)', () => { package: 'foo', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -136,6 +137,7 @@ describe('publish command (registry)', () => { package: 'foo', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -220,6 +222,7 @@ describe('publish command (registry)', () => { package: 'foopkg', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -300,6 +303,7 @@ describe('publish command (registry)', () => { package: 'foopkg', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -385,6 +389,7 @@ describe('publish command (registry)', () => { package: 'foopkg', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', diff --git a/src/__e2e__/syncE2E.test.ts b/src/__e2e__/syncE2E.test.ts index 56b0dcbf2..bec2011b1 100644 --- a/src/__e2e__/syncE2E.test.ts +++ b/src/__e2e__/syncE2E.test.ts @@ -91,6 +91,7 @@ describe('sync command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -145,6 +146,7 @@ describe('sync command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -208,6 +210,7 @@ describe('sync command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', diff --git a/src/__tests__/changefile/conventionalCommits.test.ts b/src/__tests__/changefile/conventionalCommits.test.ts new file mode 100644 index 000000000..08a29b782 --- /dev/null +++ b/src/__tests__/changefile/conventionalCommits.test.ts @@ -0,0 +1,13 @@ +import { parseConventionalCommit } from '../../changefile/conventionalCommits'; + +describe.each<[string, ReturnType]>([ + ['fix: change message\nbody', { type: 'patch', message: 'change message' }], + ['chore: change', { type: 'patch', message: 'change' }], + ['feat: change', { type: 'minor', message: 'change' }], + ['feat(scope): change', { type: 'minor', message: 'change' }], + ['feat!: change', { type: 'major', message: 'change' }], + ['feat(scope)!: change', { type: 'major', message: 'change' }], + ['foo', undefined], +])('parse(%s)', (s, expected) => { + test('should parse correctly', () => expect(parseConventionalCommit(s)).toEqual(expected)); +}); diff --git a/src/changefile/conventionalCommits.ts b/src/changefile/conventionalCommits.ts new file mode 100644 index 000000000..267923494 --- /dev/null +++ b/src/changefile/conventionalCommits.ts @@ -0,0 +1,43 @@ +import { ChangeType } from '../types/ChangeInfo'; + +/** + * 1. type + * 2. scope + * 3. breaking + * 4. message + */ +const COMMIT_RE = /([a-z]+)(?:\(([a-z]+)\))?(!)?: (.+)/; + +interface ConventionalCommit { + type: string; + scope?: string; + breaking: boolean; + message: string; +} + +interface Change { + type: ChangeType; + message: string; +} + +export function parseConventionalCommit(commitMessage: string): Change | undefined { + const match = commitMessage.match(COMMIT_RE); + const data: ConventionalCommit | undefined = match + ? { type: match[1], scope: match[2], breaking: !!match[3], message: match[4] } + : undefined; + return data && map(data); +} + +function map(d: ConventionalCommit): Change | undefined { + if (d.breaking) { + return { type: 'major', message: d.message }; + } + + switch (d.type) { + case 'fix': + case 'chore': + return { type: 'patch', message: d.message }; + case 'feat': + return { type: 'minor', message: d.message }; + } +} diff --git a/src/changefile/promptForChange.ts b/src/changefile/promptForChange.ts index 4baf84f63..98297101d 100644 --- a/src/changefile/promptForChange.ts +++ b/src/changefile/promptForChange.ts @@ -9,6 +9,7 @@ import { getPackageGroups } from '../monorepo/getPackageGroups'; import { isValidChangeType } from '../validation/isValidChangeType'; import { DefaultPrompt } from '../types/ChangeFilePrompt'; import { getDisallowedChangeTypes } from './getDisallowedChangeTypes'; +import { parseConventionalCommit } from './conventionalCommits'; /** * Uses `prompts` package to prompt for change type and description, fills in git user.email and scope @@ -27,6 +28,11 @@ export async function promptForChange(options: BeachballOptions): Promise(obj: T | undefined): obj is T => !!obj)) || + []; + for (let pkg of changedPackages) { console.log(''); console.log(`Please describe the changes for: ${pkg}`); @@ -47,7 +53,7 @@ export async function promptForChange(options: BeachballOptions): Promise !disallowedChangeTypes?.includes(choice.value as ChangeType)), + ].filter((choice) => !disallowedChangeTypes?.includes(choice.value as ChangeType)), }; if (changeTypePrompt.choices!.length === 0) { @@ -64,16 +70,17 @@ export async function promptForChange(options: BeachballOptions): Promise { - return Promise.resolve([...recentMessages.filter(msg => msg.startsWith(input)), input]); + suggest: (input) => { + return Promise.resolve([...recentMessages.filter((msg) => msg.startsWith(input)), input]); }, }; - const showChangeTypePrompt = !options.type && changeTypePrompt.choices!.length > 1; + const allowedConventionalCommit = fromConventionalCommits.find((c) => !disallowedChangeTypes?.includes(c.type)); + const showChangeTypePrompt = !options.type && !allowedConventionalCommit && changeTypePrompt.choices!.length > 1; const defaultPrompt: DefaultPrompt = { changeType: showChangeTypePrompt ? changeTypePrompt : undefined, - description: !options.message ? descriptionPrompt : undefined, + description: !options.message && !allowedConventionalCommit ? descriptionPrompt : undefined, }; let questions = [defaultPrompt.changeType, defaultPrompt.description]; @@ -86,11 +93,11 @@ export async function promptForChange(options: BeachballOptions): Promise !!q); + questions = questions.filter((q) => !!q); let response: { comment: string; type: ChangeType } = { - type: options.type || 'none', - comment: options.message || '', + type: options.type || allowedConventionalCommit?.type || 'none', + comment: options.message || allowedConventionalCommit?.message || '', }; if (questions.length > 0) { diff --git a/src/options/getCliOptions.ts b/src/options/getCliOptions.ts index b4e8359b0..3f2abb3fc 100644 --- a/src/options/getCliOptions.ts +++ b/src/options/getCliOptions.ts @@ -24,7 +24,15 @@ function getCliOptionsUncached(argv: string[]): CliOptions { const args = parser(trimmedArgv, { string: ['branch', 'tag', 'message', 'package', 'since', 'dependent-change-type', 'config'], array: ['scope', 'disallowed-change-types'], - boolean: ['git-tags', 'keep-change-files', 'force', 'disallow-deleted-change-files', 'no-commit', 'fetch'], + boolean: [ + 'git-tags', + 'keep-change-files', + 'force', + 'disallow-deleted-change-files', + 'no-commit', + 'fetch', + 'use-conventional-commits', + ], number: ['depth'], alias: { authType: ['a'], diff --git a/src/options/getDefaultOptions.ts b/src/options/getDefaultOptions.ts index 213fd7e71..0b5c0b97f 100644 --- a/src/options/getDefaultOptions.ts +++ b/src/options/getDefaultOptions.ts @@ -19,6 +19,7 @@ export function getDefaultOptions() { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, version: false, disallowedChangeTypes: null, diff --git a/src/types/BeachballOptions.ts b/src/types/BeachballOptions.ts index 4c24d2406..12ec85cc0 100644 --- a/src/types/BeachballOptions.ts +++ b/src/types/BeachballOptions.ts @@ -44,6 +44,7 @@ export interface CliOptions timeout?: number; token: string; type?: ChangeType | null; + useConventionalCommits: boolean; verbose?: boolean; version?: boolean; yes: boolean; @@ -141,7 +142,7 @@ export interface HooksOptions { * Runs for each package, before writing changelog and package.json updates * to the filesystem. May be called multiple times during publish. */ - prebump?: (packagePath: string, name: string, version: string) => void | Promise; + prebump?: (packagePath: string, name: string, version: string) => void | Promise; /** * Runs for each package, after writing changelog and package.json updates From da9760f5c7ec25309f09b8b2758c917df482ac2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85smund=20Grammeltvedt?= Date: Tue, 13 Apr 2021 14:23:06 +0200 Subject: [PATCH 2/5] Change files --- change/beachball-38e088f5-413c-44fe-98c1-6fccdbe43be0.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/beachball-38e088f5-413c-44fe-98c1-6fccdbe43be0.json diff --git a/change/beachball-38e088f5-413c-44fe-98c1-6fccdbe43be0.json b/change/beachball-38e088f5-413c-44fe-98c1-6fccdbe43be0.json new file mode 100644 index 000000000..8c125c035 --- /dev/null +++ b/change/beachball-38e088f5-413c-44fe-98c1-6fccdbe43be0.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Support generating change files from conventional commits", + "packageName": "beachball", + "email": "asgramme@microsoft.com", + "dependentChangeType": "patch" +} From 5261efb977907fdad610c2a0ea7a3d7d2435aa87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85smund=20Grammeltvedt?= Date: Tue, 13 Apr 2021 16:11:44 +0200 Subject: [PATCH 3/5] Add some comments --- src/changefile/promptForChange.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/changefile/promptForChange.ts b/src/changefile/promptForChange.ts index 98297101d..0416a8c24 100644 --- a/src/changefile/promptForChange.ts +++ b/src/changefile/promptForChange.ts @@ -28,6 +28,10 @@ export async function promptForChange(options: BeachballOptions): Promise(obj: T | undefined): obj is T => !!obj)) || @@ -75,6 +79,7 @@ export async function promptForChange(options: BeachballOptions): Promise !disallowedChangeTypes?.includes(c.type)); const showChangeTypePrompt = !options.type && !allowedConventionalCommit && changeTypePrompt.choices!.length > 1; From a0bb896a9860fe1f54379111bde23e970b044ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85smund=20Grammeltvedt?= Date: Fri, 20 Aug 2021 12:06:24 +0200 Subject: [PATCH 4/5] Chore commits should map to 'none' type beachball changes --- src/__tests__/changefile/conventionalCommits.test.ts | 3 ++- src/changefile/conventionalCommits.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/__tests__/changefile/conventionalCommits.test.ts b/src/__tests__/changefile/conventionalCommits.test.ts index 08a29b782..306b81e7e 100644 --- a/src/__tests__/changefile/conventionalCommits.test.ts +++ b/src/__tests__/changefile/conventionalCommits.test.ts @@ -2,12 +2,13 @@ import { parseConventionalCommit } from '../../changefile/conventionalCommits'; describe.each<[string, ReturnType]>([ ['fix: change message\nbody', { type: 'patch', message: 'change message' }], - ['chore: change', { type: 'patch', message: 'change' }], + ['chore: change', { type: 'none', message: 'change' }], ['feat: change', { type: 'minor', message: 'change' }], ['feat(scope): change', { type: 'minor', message: 'change' }], ['feat!: change', { type: 'major', message: 'change' }], ['feat(scope)!: change', { type: 'major', message: 'change' }], ['foo', undefined], + ['fix(foo-bar): change', { type: 'patch', message: 'change' }], ])('parse(%s)', (s, expected) => { test('should parse correctly', () => expect(parseConventionalCommit(s)).toEqual(expected)); }); diff --git a/src/changefile/conventionalCommits.ts b/src/changefile/conventionalCommits.ts index 267923494..abc4a6f66 100644 --- a/src/changefile/conventionalCommits.ts +++ b/src/changefile/conventionalCommits.ts @@ -6,7 +6,7 @@ import { ChangeType } from '../types/ChangeInfo'; * 3. breaking * 4. message */ -const COMMIT_RE = /([a-z]+)(?:\(([a-z]+)\))?(!)?: (.+)/; +const COMMIT_RE = /([a-z]+)(?:\(([a-z\-]+)\))?(!)?: (.+)/; interface ConventionalCommit { type: string; @@ -34,8 +34,9 @@ function map(d: ConventionalCommit): Change | undefined { } switch (d.type) { - case 'fix': case 'chore': + return { type: 'none', message: d.message }; + case 'fix': return { type: 'patch', message: d.message }; case 'feat': return { type: 'minor', message: d.message }; From 0de404a430b886478f92ee308e8684cbd1d3e23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85smund=20Grammeltvedt?= Date: Thu, 19 May 2022 09:47:59 +0200 Subject: [PATCH 5/5] Minor fixes --- src/__e2e__/publishE2E.test.ts | 7 ++++--- src/changefile/conventionalCommits.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/__e2e__/publishE2E.test.ts b/src/__e2e__/publishE2E.test.ts index adb92618d..a3517eb40 100644 --- a/src/__e2e__/publishE2E.test.ts +++ b/src/__e2e__/publishE2E.test.ts @@ -642,7 +642,7 @@ describe('publish command (e2e)', () => { bump: true, generateChangelog: true, hooks: { - postpublish: (packagePath) => { + postpublish: packagePath => { const packageJsonPath = path.join(packagePath, 'package.json'); const packageJson = fs.readJSONSync(packageJsonPath); if (packageJson.afterPublish) { @@ -708,6 +708,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: false, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -779,6 +780,7 @@ describe('publish command (e2e)', () => { package: '', changehint: 'Run "beachball change" to create a change file', type: null, + useConventionalCommits: false, fetch: true, disallowedChangeTypes: null, defaultNpmTag: 'latest', @@ -786,7 +788,7 @@ describe('publish command (e2e)', () => { bump: true, generateChangelog: true, dependentChangeType: null, - depth: 10 + depth: 10, }); const showResult = npm(['--registry', registry.getUrl(), 'show', 'foo', '--json']); @@ -801,5 +803,4 @@ describe('publish command (e2e)', () => { // no fetch when flag set to false expect(depthString).toEqual('--depth=10'); }); - }); diff --git a/src/changefile/conventionalCommits.ts b/src/changefile/conventionalCommits.ts index abc4a6f66..c15844603 100644 --- a/src/changefile/conventionalCommits.ts +++ b/src/changefile/conventionalCommits.ts @@ -6,7 +6,7 @@ import { ChangeType } from '../types/ChangeInfo'; * 3. breaking * 4. message */ -const COMMIT_RE = /([a-z]+)(?:\(([a-z\-]+)\))?(!)?: (.+)/; +const COMMIT_RE = /([a-z]+)(?:\(([a-z\-]+)\))?(!)?: (.+)/i; interface ConventionalCommit { type: string;