From 21f24f57ea0312c69f27afea1a5afc117fbf5310 Mon Sep 17 00:00:00 2001 From: yi_Xu Date: Tue, 13 Apr 2021 13:01:09 +0800 Subject: [PATCH] feat(breakingchange): :sparkles: add breaking change section 1. add `detectBreakingChange` option. 2. add `promptBreakingChange` option. 3. add two `breakingChangeFormat`. --- package.json | 24 ++++++++++++ package.nls.json | 5 +++ package.nls.zh-cn.json | 5 +++ src/lib/commit-message.ts | 69 ++++++++++++++++++++++----------- src/lib/configuration.ts | 9 +++++ src/lib/conventional-commits.ts | 16 ++++++-- src/lib/prompts.ts | 59 +++++++++++++++++++++++++--- src/lib/prompts/prompt-types.ts | 66 +++++++++++++++++++++++++++++++ 8 files changed, 222 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 8161385..97fa8dd 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,30 @@ "type": "boolean", "default": false, "markdownDescription": "%extension.configuration.editor.keepAfterSave.markdownDescription%" + }, + "conventionalCommits.detectBreakingChange": { + "type": "boolean", + "default": false, + "markdownDescription": "%extension.configuration.detectBreakingChange.markdownDescription%" + }, + "conventionalCommits.promptBreakingChange": { + "type": "boolean", + "default": false, + "markdownDescription": "%extension.configuration.promptBreakingChange.markdownDescription%" + }, + "conventionalCommits.breakingChangeFormat": { + "type": "string", + "default": "both", + "enum": [ + "space", + "hyphen", + "both" + ], + "markdownEnumDescriptions": [ + "%extension.configuration.breakingChangeFormat.markdownEnumDescriptions.space%", + "%extension.configuration.breakingChangeFormat.markdownEnumDescriptions.hyphen%", + "%extension.configuration.breakingChangeFormat.markdownEnumDescriptions.both%" + ] } } } diff --git a/package.nls.json b/package.nls.json index c233c8f..f44b6b0 100644 --- a/package.nls.json +++ b/package.nls.json @@ -15,6 +15,11 @@ "extension.configuration.showEditor.markdownDescription": "Control whether the extension should show the commit message as a text document in a separate tab.", "extension.configuration.showNewVersionNotes.markdownDescription": "Control whether the extension should show the new version notes.", "extension.configuration.editor.keepAfterSave.markdownDescription": "Control whether the extension should allow keeping edit status after saving the commit message.", + "extension.configuration.detectBreakingChange.markdownDescription": "Control whether the extension should add optional `!` into commit message when have `BREAKING CHANGE: ` or `BREAKING-CHANGE: ` in `footer`.", + "extension.configuration.promptBreakingChange.markdownDescription": "Controls whether the extension should prompt for the `breaking change` section.", + "extension.configuration.breakingChangeFormat.markdownEnumDescriptions.space": "Display one prompt selection as `BREAKING CHANGE: `", + "extension.configuration.breakingChangeFormat.markdownEnumDescriptions.hyphen": "Display one prompt selection as `BREAKING-CHANGE: `", + "extension.configuration.breakingChangeFormat.markdownEnumDescriptions.both": "Display two prompts selection as `BREAKING CHANGE: ` and `BREAKING-CHANGE: `", "extension.sources.repositoryNotFoundInPath": "Repository not found in path: ", "extension.sources.repositoriesEmpty": "Please open a repository.", "extension.sources.promptRepositoryPlaceholder": "Choose a repository.", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 6818a66..4bbaebe 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -15,6 +15,11 @@ "extension.configuration.showEditor.markdownDescription": "是否需要在新标签页用文本编辑器展示提交信息", "extension.configuration.showNewVersionNotes.markdownDescription": "是否需要显示新版本说明。", "extension.configuration.editor.keepAfterSave.markdownDescription": "是否需要在保存提交消息后保持编辑状态。", + "extension.configuration.detectBreakingChange.markdownDescription": "当检测到 `footer` 中有 `BREAKING-CHANGE: ` 或者 `BREAKING CHANGE: ` 时,是否需要在提交信息中添加可选项 `!`。", + "extension.configuration.promptBreakingChange.markdownDescription": "是否需要提示填写 `Breaking change`", + "extension.configuration.breakingChangeFormat.markdownEnumDescriptions.space": "显示提示选项 `BREAKING CHANGE: `", + "extension.configuration.breakingChangeFormat.markdownEnumDescriptions.hyphen": "显示提示选项 `BREAKING-CHANGE: `", + "extension.configuration.breakingChangeFormat.markdownEnumDescriptions.both": "同时显示两种提示选项 `BREAKING CHANGE: ` 和 `BREAKING-CHANGE: `", "extension.sources.repositoryNotFoundInPath": "以下路径中未找到仓库:", "extension.sources.repositoriesEmpty": "请打开一个仓库。", "extension.sources.promptRepositoryPlaceholder": "请选择一个仓库。", diff --git a/src/lib/commit-message.ts b/src/lib/commit-message.ts index ce7f80d..1bdfea7 100644 --- a/src/lib/commit-message.ts +++ b/src/lib/commit-message.ts @@ -5,6 +5,7 @@ export class CommitMessage { private _type: string = ''; private _scope: string = ''; + private _breakingChange: string = ''; private _gitmoji: string = ''; private _subject: string = ''; private _body: string = ''; @@ -26,6 +27,14 @@ export class CommitMessage { this._scope = input.trim(); } + get breakingChange() { + return this._breakingChange; + } + + set breakingChange(input: string) { + this._breakingChange = input.trim(); + } + get gitmoji() { return this._gitmoji; } @@ -49,7 +58,6 @@ export class CommitMessage { set body(input: string) { this._body = input.trim(); } - get footer() { return this._footer; } @@ -77,34 +85,51 @@ export function serializeSubject(partialCommitMessage: { return result; } -export function serializeHeader(partialCommitMessage: { - type: string; - scope: string; - gitmoji: string; - subject: string; +export function serializeHeader({ + partialCommitMessage, + detectBreakingChange, +}: { + partialCommitMessage: { + type: string; + scope: string; + breakingChange: string; + gitmoji: string; + subject: string; + }; + detectBreakingChange: boolean; }) { - let result = ''; - result += partialCommitMessage.type; - const { scope } = partialCommitMessage; - if (scope) { - result += `(${scope})`; - } - result += ': '; + let result = '' + partialCommitMessage.type; + + const { scope, breakingChange } = partialCommitMessage; + if (scope) result += `(${scope})`; + + if (breakingChange && (breakingChange === '!' || detectBreakingChange)) { + result += `!: `; + } else result += ': '; + const subject = serializeSubject(partialCommitMessage); - if (subject) { - result += subject; - } + if (subject) result += subject; + return result; } -export function serialize(commitMessage: CommitMessage) { - let message = serializeHeader(commitMessage); - const { body, footer } = commitMessage; - if (body) { - message += `\n\n${body}`; +export function serialize( + commitMessage: CommitMessage, + detectBreakingChange: boolean, +) { + let message = serializeHeader({ + partialCommitMessage: commitMessage, + detectBreakingChange, + }); + const { breakingChange, body, footer } = commitMessage; + if (body) message += `\n\n${body}`; + + if (breakingChange && breakingChange != '!') { + message += `\n\n${breakingChange}`; } if (footer) { - message += `\n\n${footer}`; + message += breakingChange && breakingChange != '!' ? '\n' : '\n\n'; + message += footer; } return message; } diff --git a/src/lib/configuration.ts b/src/lib/configuration.ts index 48cd90f..cb13f4b 100644 --- a/src/lib/configuration.ts +++ b/src/lib/configuration.ts @@ -10,6 +10,12 @@ export enum EMOJI_FORMAT { emoji = 'emoji', } +export enum BREAKING_CHANGE_FORMAT { + hyphen = 'hyphen', + space = 'space', + both = 'both', +} + export type Configuration = { autoCommit: boolean; gitmoji: boolean; @@ -22,6 +28,9 @@ export type Configuration = { promptFooter: boolean; showNewVersionNotes: boolean; 'editor.keepAfterSave': boolean; + detectBreakingChange: boolean; + promptBreakingChange: boolean; + breakingChangeFormat: BREAKING_CHANGE_FORMAT; }; export function getConfiguration() { diff --git a/src/lib/conventional-commits.ts b/src/lib/conventional-commits.ts index ba2232d..ddc28be 100644 --- a/src/lib/conventional-commits.ts +++ b/src/lib/conventional-commits.ts @@ -141,6 +141,9 @@ export default function createConventionalCommits() { ); // 5. get message + const detectBreakingChange = configuration.get( + 'detectBreakingChange', + ); const commitMessage = await prompts({ gitmoji: configuration.get('gitmoji'), showEditor: configuration.get('showEditor'), @@ -151,10 +154,17 @@ export default function createConventionalCommits() { promptScopes: configuration.get('promptScopes'), promptBody: configuration.get('promptBody'), promptFooter: configuration.get('promptFooter'), + promptBreakingChange: configuration.get( + 'promptBreakingChange', + ), + detectBreakingChange: detectBreakingChange, + breakingChangeFormat: configuration.get( + 'breakingChangeFormat', + ), }); - output.info(`messageJSON:\n${JSON.stringify(commitMessage, null, 2)}`); - const message = serialize(commitMessage); - output.info(`message:\n${message}`); + output.info(`commitMessage: ${JSON.stringify(commitMessage, null, 2)}`); + let message = serialize(commitMessage, detectBreakingChange); + output.info(`message: ${message}`); // 6. switch to scm and put message into message box // or show the entire commit message in a separate tab diff --git a/src/lib/prompts.ts b/src/lib/prompts.ts index 4afd31d..0452b50 100644 --- a/src/lib/prompts.ts +++ b/src/lib/prompts.ts @@ -32,6 +32,9 @@ export default async function prompts({ promptScopes, promptBody, promptFooter, + promptBreakingChange, + detectBreakingChange, + breakingChangeFormat, }: { gitmoji: boolean; showEditor: boolean; @@ -40,6 +43,9 @@ export default async function prompts({ promptScopes: boolean; promptBody: boolean; promptFooter: boolean; + promptBreakingChange: boolean; + detectBreakingChange: boolean; + breakingChangeFormat: boolean; }): Promise { const commitMessage = new CommitMessage(); const conventionalCommitsTypes = getTypesByLocale(locale).types; @@ -114,6 +120,7 @@ export default async function prompts({ name, placeholder, configurationKey: keys.SCOPES as keyof configuration.Configuration, + noneItem, newItem: { label: getPromptLocalize('scope.newItem.label'), description: '', @@ -121,7 +128,6 @@ export default async function prompts({ alwaysShow: true, placeholder: getPromptLocalize('scope.newItem.placeholder'), }, - noneItem, newItemWithoutSetting: { label: getPromptLocalize('scope.newItemWithoutSetting.label'), description: '', @@ -146,6 +152,39 @@ export default async function prompts({ }, }, getScopePrompt(), + { + type: PROMPT_TYPES.BREAKING_CHANGE_QUICK_PICK, + name: 'breakingChange', + placeholder: getPromptLocalize('breakingChange.placeholder'), + noneItem: { + label: getPromptLocalize('breakingChange.noneItem.label'), + description: '', + detail: getPromptLocalize('breakingChange.noneItem.detail'), + alwaysShow: true, + }, + pointItem: { + label: getPromptLocalize('breakingChange.pointItem.label'), + description: '', + detail: getPromptLocalize('breakingChange.pointItem.detail'), + alwaysShow: true, + }, + spaceItem: { + label: getPromptLocalize('breakingChange.spaceItem.label'), + description: '', + detail: getPromptLocalize('breakingChange.spaceItem.detail'), + alwaysShow: true, + placeholder: getPromptLocalize('breakingChange.message.placeholder'), + }, + hyphenItem: { + label: getPromptLocalize('breakingChange.hyphenItem.label'), + description: '', + detail: getPromptLocalize('breakingChange.hyphenItem.detail'), + alwaysShow: true, + placeholder: getPromptLocalize('breakingChange.message.placeholder'), + }, + breakingChangeFormat: breakingChangeFormat, + format: lineBreakFormatter, + }, { type: PROMPT_TYPES.QUICK_PICK, name: 'gitmoji', @@ -169,7 +208,7 @@ export default async function prompts({ name: 'subject', placeholder: getPromptLocalize('subject.placeholder'), validate(input: string) { - const { type, scope, gitmoji } = commitMessage; + const { type, scope, breakingChange, gitmoji } = commitMessage; const serializedSubject = serializeSubject({ gitmoji, subject: input, @@ -188,10 +227,14 @@ export default async function prompts({ let headerError = commitlint.lintHeader( serializeHeader({ - type, - scope, - gitmoji, - subject: input, + partialCommitMessage: { + type, + scope, + breakingChange, + gitmoji, + subject: input, + }, + detectBreakingChange: detectBreakingChange, }), ); if (headerError) { @@ -241,6 +284,10 @@ export default async function prompts({ if (question.name === 'gitmoji' && !gitmoji) return false; + if (question.name === 'breakingChange') { + if (!promptBreakingChange) return false; + } + if (question.name === 'body') { if (showEditor || !promptBody) return false; } diff --git a/src/lib/prompts/prompt-types.ts b/src/lib/prompts/prompt-types.ts index 0251b65..60c4e3a 100644 --- a/src/lib/prompts/prompt-types.ts +++ b/src/lib/prompts/prompt-types.ts @@ -12,6 +12,7 @@ export enum PROMPT_TYPES { QUICK_PICK, INPUT_BOX, CONFIGURABLE_QUICK_PICK, + BREAKING_CHANGE_QUICK_PICK, } type Item = { @@ -171,8 +172,73 @@ async function createConfigurableQuickPick({ return format(selectedValue); } +type BreakingChangeQuickPickOptions = { + pointItem: Item; + hyphenItem: Item; + spaceItem: Item; + breakingChangeFormat: configuration.BREAKING_CHANGE_FORMAT; + validate?: (value: string) => string | undefined; +} & QuickPickOptions; + +async function createBreakingChangeQuickPick({ + placeholder, + format = (i) => i, + step, + totalSteps, + pointItem, + hyphenItem, + spaceItem, + noneItem, + breakingChangeFormat, + validate = () => undefined, +}: BreakingChangeQuickPickOptions): Promise { + const items: Item[] = []; + items.push(pointItem); + if (breakingChangeFormat === configuration.BREAKING_CHANGE_FORMAT.space) { + items.push(spaceItem); + } + if (breakingChangeFormat === configuration.BREAKING_CHANGE_FORMAT.hyphen) { + items.push(hyphenItem); + } + if (breakingChangeFormat === configuration.BREAKING_CHANGE_FORMAT.both) { + items.push(spaceItem, hyphenItem); + } + let selectedValue = await createQuickPick({ + placeholder, + items, + step, + totalSteps, + noneItem, + }); + if (selectedValue === spaceItem.label) { + selectedValue = + 'BREAKING CHANGE: ' + + (await createInputBox({ + placeholder: spaceItem.placeholder!, + step, + totalSteps, + validate, + })); + } + if (selectedValue === hyphenItem.label) { + selectedValue = + 'BREAKING-CHANGE: ' + + (await createInputBox({ + placeholder: hyphenItem.placeholder!, + step, + totalSteps, + validate, + })); + } + if (selectedValue === pointItem.label) { + selectedValue = '!'; + } + return format(selectedValue); +} + export default { [PROMPT_TYPES.QUICK_PICK]: createQuickPick, [PROMPT_TYPES.INPUT_BOX]: createInputBox, [PROMPT_TYPES.CONFIGURABLE_QUICK_PICK]: createConfigurableQuickPick, + [PROMPT_TYPES.BREAKING_CHANGE_QUICK_PICK]: createBreakingChangeQuickPick, };