diff --git a/packages/cz-git/src/generator/message.ts b/packages/cz-git/src/generator/message.ts index 52a2268b1..8359fba03 100644 --- a/packages/cz-git/src/generator/message.ts +++ b/packages/cz-git/src/generator/message.ts @@ -5,7 +5,7 @@ */ import { style } from '@cz-git/inquirer' -import { getCurrentScopes, handleStandardScopes, isSingleItem, wrap } from '../shared' +import { getCurrentScopes, isSingleItem, parseStandardScopes, wrap } from '../shared' import type { Answers, CommitizenGitOptions } from '../shared' export const getAliasMessage = (config: CommitizenGitOptions, alias?: string) => { @@ -27,7 +27,7 @@ const getSingleParams = (answers: Answers, options: CommitizenGitOptions) => { singleScope: '', singeIssuePrefix: '', } - const scopeList = handleStandardScopes( + const scopeList = parseStandardScopes( getCurrentScopes(options.scopes, options.scopeOverrides, answers.type), ) if (isSingleItem(options.allowCustomScopes, options.allowEmptyScopes, scopeList)) diff --git a/packages/cz-git/src/generator/option.ts b/packages/cz-git/src/generator/option.ts index 63e034351..170fd5d36 100644 --- a/packages/cz-git/src/generator/option.ts +++ b/packages/cz-git/src/generator/option.ts @@ -11,6 +11,7 @@ import { getEnumList, getMaxLength, getMinLength, + ruleIsWarning, } from '../shared' import type { CommitizenGitOptions, UserConfig } from '../shared' @@ -54,6 +55,9 @@ export const generateOptions = (config: UserConfig): CommitizenGitOptions => { confirmColorize: promptConfig.confirmColorize ?? defaultConfig.confirmColorize, maxHeaderLength: promptConfig.maxHeaderLength ?? getMaxLength(config?.rules?.['header-max-length'] as any), maxSubjectLength: promptConfig.maxSubjectLength ?? getMaxLength(config?.rules?.['subject-max-length'] as any), + isIgnoreCheckMaxSubjectLength: promptConfig.isIgnoreCheckMaxSubjectLength + || ruleIsWarning(config?.rules?.['subject-max-length'] as any) + || ruleIsWarning(config?.rules?.['header-max-length'] as any), minSubjectLength: promptConfig.minSubjectLength ?? getMinLength(config?.rules?.['subject-min-length'] as any), defaultType: promptConfig.defaultType ?? defaultConfig.defaultType, defaultScope: promptConfig.defaultScope ?? defaultConfig.defaultScope, diff --git a/packages/cz-git/src/generator/question.ts b/packages/cz-git/src/generator/question.ts index ec4f2cda1..5fafaeb97 100644 --- a/packages/cz-git/src/generator/question.ts +++ b/packages/cz-git/src/generator/question.ts @@ -10,11 +10,11 @@ import { getCurrentScopes, getMaxSubjectLength, getProcessSubject, - handleCustomTemplate, - handlePinListTop, - handleStandardScopes, isSingleItem, log, + parseStandardScopes, + resolveListItemPinTop, + resovleCustomListTemplate, } from '../shared' import { generateMessage } from './message' @@ -35,7 +35,7 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => { message: options.messages?.type, themeColorCode: options?.themeColorCode, source: (_: unknown, input: string) => { - const typeSource = handlePinListTop( + const typeSource = resolveListItemPinTop( options.types?.concat(options.typesAppend || []) || [], options.defaultType, ) @@ -53,10 +53,10 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => { separator: options.scopeEnumSeparator, source: (answer: Answers, input: string) => { let scopeSource: Option[] = [] - scopeSource = handleStandardScopes( + scopeSource = parseStandardScopes( getCurrentScopes(options.scopes, options.scopeOverrides, answer.type), ) - scopeSource = handleCustomTemplate( + scopeSource = resovleCustomListTemplate( scopeSource, cz, options.customScopesAlign, @@ -82,7 +82,7 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => { return !isSingleItem( options.allowCustomScopes, options.allowEmptyScopes, - handleStandardScopes( + parseStandardScopes( getCurrentScopes(options.scopes, options.scopeOverrides, answer.type), ), ) @@ -126,34 +126,46 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => { ) } if (processedSubject.length > maxSubjectLength) { - return style.red( + return options.isIgnoreCheckMaxSubjectLength + ? true + : style.red( `[ERROR]subject length must be less than or equal to ${maxSubjectLength} characters`, - ) + ) } return true }, transformer: (subject: string, answers: Answers) => { - const { minSubjectLength } = options + const { minSubjectLength, isIgnoreCheckMaxSubjectLength } = options const subjectLength = subject.length const maxSubjectLength = getMaxSubjectLength(answers.type, answers.scope, options) let tooltip - if (minSubjectLength !== undefined && subjectLength < minSubjectLength) + let isWarning = false + if (typeof minSubjectLength === 'number' && subjectLength < minSubjectLength) { tooltip = `${minSubjectLength - subjectLength} more chars needed` - else if (subjectLength > maxSubjectLength) - tooltip = `${subjectLength - maxSubjectLength} chars over the limit` - else tooltip = `${maxSubjectLength - subjectLength} more chars allowed` + } + else if (subjectLength > maxSubjectLength) { + if (isIgnoreCheckMaxSubjectLength) { + tooltip = `${subjectLength - maxSubjectLength} chars over the expected` + isWarning = true + } + else { tooltip = `${subjectLength - maxSubjectLength} chars over the limit` } + } + else { + tooltip = `${maxSubjectLength - subjectLength} more chars allowed` + } + tooltip = minSubjectLength !== undefined && subjectLength >= minSubjectLength && subjectLength <= maxSubjectLength ? style.gray(`[${tooltip}]`) - : style.red(`[${tooltip}]`) + : isWarning ? style.yellow(`[${tooltip}]`) : style.red(`[${tooltip}]`) subject = minSubjectLength !== undefined && subjectLength >= minSubjectLength && subjectLength <= maxSubjectLength ? useThemeCode(subject, options.themeColorCode) - : style.red(subject) + : isWarning ? useThemeCode(subject, options.themeColorCode) : style.red(subject) return `${tooltip}\n` + ` ${subject}` }, @@ -205,7 +217,7 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => { message: options.messages?.footerPrefixsSelect, themeColorCode: options?.themeColorCode, source: (_: Answers, input: string) => { - const issuePrefixSource = handleCustomTemplate( + const issuePrefixSource = resovleCustomListTemplate( options.issuePrefixs as Option[], cz, options.customIssuePrefixsAlign, diff --git a/packages/cz-git/src/shared/types/options.ts b/packages/cz-git/src/shared/types/options.ts index a8dc0da6e..b71219c43 100644 --- a/packages/cz-git/src/shared/types/options.ts +++ b/packages/cz-git/src/shared/types/options.ts @@ -353,6 +353,14 @@ export interface CommitizenGitOptions { */ maxSubjectLength?: number + /** + * @description: Is not strict subject rule. Just provide prompt word length warning. + * Effected maxHeader and maxSubject commlitlint + * @example [1, 'always', 80] 1: mean warning. will be true + * @default: false + */ + isIgnoreCheckMaxSubjectLength?: boolean + /** * @description: Force set header width. * @note it auto check rule "subject-min-length" set the option with `@commitlint`. @@ -470,6 +478,7 @@ export const defaultConfig = Object.freeze({ confirmColorize: true, maxHeaderLength: Infinity, maxSubjectLength: Infinity, + isIgnoreCheckMaxSubjectLength: false, minSubjectLength: 0, scopeOverrides: undefined, scopeFilters: ['.DS_Store'], diff --git a/packages/cz-git/src/shared/utils/rule.ts b/packages/cz-git/src/shared/utils/rule.ts index 4e18c67b4..de633af6f 100644 --- a/packages/cz-git/src/shared/utils/rule.ts +++ b/packages/cz-git/src/shared/utils/rule.ts @@ -22,6 +22,19 @@ export function ruleIsDisabled(rule: Rule): rule is Readonly<[RuleConfigSeverity return false } +/** + * @description: rule is Warning + * @example: ruleIsDisabled([0]) => false + * @example: ruleIsDisabled([1]) => true + * @example: ruleIsDisabled([2]) => false + */ +export function ruleIsWarning(rule?: Rule): rule is Readonly<[RuleConfigSeverity.Disabled]> { + if (rule && Array.isArray(rule) && rule[0] === RuleConfigSeverity.Warning) + return true + + return false +} + /** * @description: rule is use * @example: ruleIsActive([0]) => false @@ -51,7 +64,7 @@ export function ruleIsNotApplicable( } /** - * @description: rule is can ignore + * @description: rule is not can be ignore */ export function ruleIsApplicable( rule: Rule, diff --git a/packages/cz-git/src/shared/utils/util.ts b/packages/cz-git/src/shared/utils/util.ts index cfd49cc92..876d2949f 100644 --- a/packages/cz-git/src/shared/utils/util.ts +++ b/packages/cz-git/src/shared/utils/util.ts @@ -16,48 +16,18 @@ export function log(type: 'info' | 'warm' | 'err', msg: string) { console.info(`${colorMapping[type]}[${type}]>>>: ${msg}${colorMapping.reset}`) } -export const getProcessSubject = (text: string) => { - return text.replace(/(^[\s]+|[\s\.]+$)/g, '') ?? '' -} - -const getEmojiStrLength = (options: CommitizenGitOptions, type?: string): number => { - const item = options.types?.find((i: { value?: string }) => i.value === type) - // space - return item?.emoji ? item.emoji.length + 1 : 0 -} - +/** + * @description: count header length + * + * {2}: mean ': ' + */ const countLength = (target: number, typeLength: number, scope: number, emojiLength: number) => target - typeLength - 2 - scope - emojiLength -export const getMaxSubjectLength = ( - type: Answers['type'], - scope: Answers['scope'], - options: CommitizenGitOptions, -) => { - let optionMaxLength = Infinity - if (Array.isArray(scope)) - scope = scope.join(options.scopeEnumSeparator) - const typeLength = type?.length ? type.length : 0 - const scopeLength = scope ? scope.length + 2 : 0 - const emojiLength = options.useEmoji ? getEmojiStrLength(options, type) : 0 - const maxHeaderLength = options?.maxHeaderLength ? options?.maxHeaderLength : Infinity - const maxSubjectLength = options?.maxSubjectLength ? options?.maxSubjectLength : Infinity - if (options?.maxHeaderLength === 0 || options?.maxSubjectLength === 0) { - return 0 - } - else if (maxHeaderLength === Infinity) { - return maxSubjectLength !== Infinity ? maxSubjectLength : Infinity - } - else { - optionMaxLength - = countLength(maxHeaderLength, typeLength, scopeLength, emojiLength) < maxSubjectLength - ? maxHeaderLength - : maxSubjectLength - } - return countLength(optionMaxLength, typeLength, scopeLength, emojiLength) -} - -export const handlePinListTop = ( +/** + * @description: resolve list item pin top + */ +export const resolveListItemPinTop = ( arr: { name: string value: any @@ -72,6 +42,40 @@ export const handlePinListTop = ( return [arr[index], ...arr.slice(0, index), ...arr.slice(index + 1)] } +/** + * @description: check scope list and issuePrefix is only single item (scope, issuePrefix) + */ +export const isSingleItem = (allowCustom = true, allowEmpty = true, list: Array = []) => + !allowCustom && !allowEmpty && Array.isArray(list) && list.length === 1 + +/** + * @description: parse scope configuration option to standard options + */ +export const parseStandardScopes = (scopes: ScopesType): Option[] => { + return scopes.map((scope) => { + return typeof scope === 'string' + ? { name: scope, value: scope } + : !scope.value + ? { value: scope.name, ...scope } + : { value: scope.value, name: scope.name } + }) +} + +export const getCurrentScopes = ( + scopes?: any[], + scopeOverrides?: { [x: string]: any[] }, + answerType?: string, +) => { + let result = [] + if (scopeOverrides && answerType && scopeOverrides[answerType]) + result = scopeOverrides[answerType] + + else if (Array.isArray(scopes)) + result = scopes + + return result +} + const filterCustomEmptyByOption = ( target: { name: string @@ -83,11 +87,13 @@ const filterCustomEmptyByOption = ( target = allowCustom ? target : target.filter(i => i.value !== '___CUSTOM___') return allowEmpty ? target : target.filter(i => i.value !== false) } - /** - * @description: add separator custom empty + * @description + * Handle custom list template (types, scopes) + * + * Add separator custom empty */ -export const handleCustomTemplate = ( +export const resovleCustomListTemplate = ( target: Array<{ name: string; value: string }>, cz: any, align = 'top', @@ -108,7 +114,7 @@ export const handleCustomTemplate = ( } else if (defaultValue !== '') { // pin the defaultValue to the top - target = handlePinListTop(target, defaultValue) + target = resolveListItemPinTop(target, defaultValue) } // prettier-ignore switch (align) { @@ -141,37 +147,45 @@ export const handleCustomTemplate = ( } /** - * @description: check scope list and issuePrefix is only single item + * @description: get subject word */ -export const isSingleItem = (allowCustom = true, allowEmpty = true, list: Array = []) => - !allowCustom && !allowEmpty && Array.isArray(list) && list.length === 1 +export const getProcessSubject = (text: string) => { + return text.replace(/(^[\s]+|[\s\.]+$)/g, '') ?? '' +} -/** - * @description: handle scope configuration option into standard options - * @param {ScopesType} - * @returns {Option[]} - */ -export const handleStandardScopes = (scopes: ScopesType): Option[] => { - return scopes.map((scope) => { - return typeof scope === 'string' - ? { name: scope, value: scope } - : !scope.value - ? { value: scope.name, ...scope } - : { value: scope.value, name: scope.name } - }) +const getEmojiStrLength = (options: CommitizenGitOptions, type?: string): number => { + const item = options.types?.find((i: { value?: string }) => i.value === type) + // 1: space + return item?.emoji ? item.emoji.length + 1 : 0 } -export const getCurrentScopes = ( - scopes?: any[], - scopeOverrides?: { [x: string]: any[] }, - answerType?: string, +/** + * @description: get max subject length + */ +export const getMaxSubjectLength = ( + type: Answers['type'], + scope: Answers['scope'], + options: CommitizenGitOptions, ) => { - let result = [] - if (scopeOverrides && answerType && scopeOverrides[answerType]) - result = scopeOverrides[answerType] - - else if (Array.isArray(scopes)) - result = scopes - - return result + let optionMaxLength = Infinity + if (Array.isArray(scope)) + scope = scope.join(options.scopeEnumSeparator) + const typeLength = type?.length ? type.length : 0 + const scopeLength = scope ? scope.length + 2 : 0 + const emojiLength = options.useEmoji ? getEmojiStrLength(options, type) : 0 + const maxHeaderLength = options?.maxHeaderLength ? options?.maxHeaderLength : Infinity + const maxSubjectLength = options?.maxSubjectLength ? options?.maxSubjectLength : Infinity + if (options?.maxHeaderLength === 0 || options?.maxSubjectLength === 0) { + return 0 + } + else if (maxHeaderLength === Infinity) { + return maxSubjectLength !== Infinity ? maxSubjectLength : Infinity + } + else { + optionMaxLength + = countLength(maxHeaderLength, typeLength, scopeLength, emojiLength) < maxSubjectLength + ? maxHeaderLength + : maxSubjectLength + } + return countLength(optionMaxLength, typeLength, scopeLength, emojiLength) }