Skip to content

Commit 803c05b

Browse files
authored
feat(cz-commitlint): support customizable commit prompt with emojis (#4540)
* feat(cz-commitlint): add emoji to title * docs(cz-commitlint): add emoji to title
1 parent c4d419b commit 803c05b

File tree

7 files changed

+425
-7
lines changed

7 files changed

+425
-7
lines changed

β€Ž@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.test.tsβ€Ž

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { describe, test, expect } from "vitest";
21
import { RuleConfigSeverity } from "@commitlint/types";
2+
import { describe, expect, test } from "vitest";
33

44
import { setPromptConfig } from "../store/prompts.js";
55
import { setRules } from "../store/rules.js";
@@ -253,6 +253,276 @@ describe("enum list", () => {
253253
expect(enumList?.[1]).toBe("lint");
254254
expect(enumList?.[2]).toBe("cli");
255255
});
256+
257+
test("should format enum items with emojis and descriptions, ensuring consistent spacing", () => {
258+
const ENUM_RULE_LIST = ["feat", "fix", "chore"];
259+
setRules({
260+
"type-enum": [RuleConfigSeverity.Error, "always", ENUM_RULE_LIST],
261+
} as any);
262+
263+
setPromptConfig({
264+
questions: {
265+
type: {
266+
enum: {
267+
feat: {
268+
description: "Features",
269+
emoji: "✨",
270+
},
271+
fix: {
272+
description: "Bug fixes",
273+
emoji: "πŸ›",
274+
},
275+
chore: {
276+
description: "Chore",
277+
emoji: "♻️",
278+
},
279+
},
280+
},
281+
},
282+
});
283+
284+
const enumList = getRuleQuestionConfig("type")?.enumList;
285+
expect(enumList).toEqual([
286+
{
287+
name: "✨ feat: Features",
288+
value: "feat",
289+
short: "feat",
290+
},
291+
{
292+
name: "πŸ› fix: Bug fixes",
293+
value: "fix",
294+
short: "fix",
295+
},
296+
{
297+
name: "♻️ chore: Chore",
298+
value: "chore",
299+
short: "chore",
300+
},
301+
]);
302+
});
303+
304+
test("should handle custom alignment for emoji and description with extra space", () => {
305+
const ENUM_RULE_LIST = ["feat", "fix", "chore"];
306+
setRules({
307+
"type-enum": [RuleConfigSeverity.Error, "always", ENUM_RULE_LIST],
308+
} as any);
309+
310+
setPromptConfig({
311+
questions: {
312+
type: {
313+
enum: {
314+
feat: {
315+
description: "Features",
316+
emoji: "✨ ",
317+
},
318+
fix: {
319+
description: "Bug fixes",
320+
emoji: "πŸ› ",
321+
},
322+
chore: {
323+
description: "Chore",
324+
emoji: "♻️ ",
325+
},
326+
},
327+
},
328+
},
329+
});
330+
331+
const enumList = getRuleQuestionConfig("type")?.enumList;
332+
expect(enumList).toEqual([
333+
{
334+
name: "✨ feat: Features",
335+
value: "feat",
336+
short: "feat",
337+
},
338+
{
339+
name: "πŸ› fix: Bug fixes",
340+
value: "fix",
341+
short: "fix",
342+
},
343+
{
344+
name: "♻️ chore: Chore",
345+
value: "chore",
346+
short: "chore",
347+
},
348+
]);
349+
});
350+
351+
test("should handle inconsistent emoji usage by using 4 blank spaces as a prefix", () => {
352+
const ENUM_RULE_LIST = ["feat", "fix", "chore"];
353+
setRules({
354+
"type-enum": [RuleConfigSeverity.Error, "always", ENUM_RULE_LIST],
355+
} as any);
356+
357+
setPromptConfig({
358+
questions: {
359+
type: {
360+
enum: {
361+
feat: {
362+
description: "Features",
363+
emoji: "✨",
364+
},
365+
fix: {
366+
description: "Bug fixes",
367+
},
368+
chore: {
369+
description: "Chore",
370+
},
371+
},
372+
},
373+
},
374+
});
375+
376+
const enumList = getRuleQuestionConfig("type")?.enumList;
377+
expect(enumList).toEqual([
378+
{
379+
name: "✨ feat: Features",
380+
value: "feat",
381+
short: "feat",
382+
},
383+
{
384+
name: " fix: Bug fixes",
385+
value: "fix",
386+
short: "fix",
387+
},
388+
{
389+
name: " chore: Chore",
390+
value: "chore",
391+
short: "chore",
392+
},
393+
]);
394+
});
395+
396+
test("should handle no enums having emojis correctly", () => {
397+
const ENUM_RULE_LIST = ["feat", "fix", "chore"];
398+
setRules({
399+
"type-enum": [RuleConfigSeverity.Error, "always", ENUM_RULE_LIST],
400+
} as any);
401+
402+
setPromptConfig({
403+
questions: {
404+
type: {
405+
enum: {
406+
feat: {
407+
description: "Features",
408+
},
409+
fix: {
410+
description: "Bug fixes",
411+
},
412+
chore: {
413+
description: "Chore",
414+
},
415+
},
416+
},
417+
},
418+
});
419+
420+
const enumList = getRuleQuestionConfig("type")?.enumList;
421+
expect(enumList).toEqual([
422+
{
423+
name: "feat: Features",
424+
value: "feat",
425+
short: "feat",
426+
},
427+
{
428+
name: "fix: Bug fixes",
429+
value: "fix",
430+
short: "fix",
431+
},
432+
{
433+
name: "chore: Chore",
434+
value: "chore",
435+
short: "chore",
436+
},
437+
]);
438+
});
439+
440+
test("should include the emoji in the value when `emojiInHeader` is true", () => {
441+
const ENUM_RULE_LIST = ["feat", "fix"];
442+
setRules({
443+
"type-enum": [RuleConfigSeverity.Error, "always", ENUM_RULE_LIST],
444+
} as any);
445+
446+
setPromptConfig({
447+
questions: {
448+
type: {
449+
emojiInHeader: true,
450+
enum: {
451+
feat: {
452+
description: "Features",
453+
emoji: "✨",
454+
},
455+
fix: {
456+
description: "Bug fixes",
457+
emoji: "πŸ›",
458+
},
459+
},
460+
},
461+
},
462+
});
463+
464+
const enumList = getRuleQuestionConfig("type")?.enumList;
465+
expect(enumList).toEqual([
466+
{
467+
name: "✨ feat: Features",
468+
value: "✨ feat",
469+
short: "feat",
470+
},
471+
{
472+
name: "πŸ› fix: Bug fixes",
473+
value: "πŸ› fix",
474+
short: "fix",
475+
},
476+
]);
477+
});
478+
479+
test("should trim empty spaces from emoji in the answer", () => {
480+
const ENUM_RULE_LIST = ["feat", "fix", "chore"];
481+
setRules({
482+
"type-enum": [RuleConfigSeverity.Error, "always", ENUM_RULE_LIST],
483+
} as any);
484+
485+
setPromptConfig({
486+
questions: {
487+
type: {
488+
emojiInHeader: true,
489+
enum: {
490+
feat: {
491+
description: "Features",
492+
emoji: "✨ ",
493+
},
494+
fix: {
495+
description: "Bug fixes",
496+
emoji: "πŸ› ",
497+
},
498+
chore: {
499+
description: "Chore",
500+
emoji: "♻️ ",
501+
},
502+
},
503+
},
504+
},
505+
});
506+
507+
const enumList = getRuleQuestionConfig("type")?.enumList;
508+
expect(enumList).toEqual([
509+
{
510+
name: "✨ feat: Features",
511+
value: "✨ feat",
512+
short: "feat",
513+
},
514+
{
515+
name: "πŸ› fix: Bug fixes",
516+
value: "πŸ› fix",
517+
short: "fix",
518+
},
519+
{
520+
name: "♻️ chore: Chore",
521+
value: "♻️ chore",
522+
short: "chore",
523+
},
524+
]);
525+
});
256526
});
257527

258528
test("should return correct question config", () => {

β€Ž@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.tsβ€Ž

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,41 @@ export default function (rulePrefix: RuleField): QuestionConfig | null {
3535

3636
if (enumRuleList) {
3737
const enumDescriptions = questionSettings?.["enum"];
38+
const emojiInHeader = questionSettings?.emojiInHeader;
3839

3940
if (enumDescriptions) {
4041
const enumNames = Object.keys(enumDescriptions);
4142
const longest = Math.max(
4243
...enumRuleList.map((enumName) => enumName.length),
4344
);
44-
// TODO emoji + title
45+
const firstHasEmoji =
46+
(enumDescriptions[enumNames[0]]?.emoji?.length ?? 0) > 0;
47+
const hasConsistentEmojiUsage = !enumRuleList.some(
48+
(enumName) =>
49+
(enumDescriptions[enumName]?.emoji?.length ?? 0) > 0 !==
50+
firstHasEmoji,
51+
);
4552
enumList = enumRuleList
4653
.sort((a, b) => enumNames.indexOf(a) - enumNames.indexOf(b))
4754
.map((enumName) => {
4855
const enumDescription = enumDescriptions[enumName]?.description;
4956
if (enumDescription) {
50-
return {
51-
name: `${enumName}:`.padEnd(longest + 4) + enumDescription,
52-
value: enumName,
53-
short: enumName,
54-
};
57+
const emoji = enumDescriptions[enumName]?.emoji;
58+
59+
const emojiPrefix = emoji
60+
? `${emoji} `
61+
: hasConsistentEmojiUsage
62+
? ""
63+
: " ";
64+
65+
const paddedName = `${enumName}:`.padEnd(longest + 4);
66+
67+
const name = `${emojiPrefix}${paddedName}${enumDescription}`;
68+
69+
const value =
70+
emojiInHeader && emoji ? `${emoji.trim()} ${enumName}` : enumName;
71+
72+
return { name, value, short: enumName };
5573
} else {
5674
return enumName;
5775
}

β€Ž@commitlint/types/src/prompt.tsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export type PromptConfig = {
3434
emoji?: string;
3535
};
3636
};
37+
emojiInHeader?: boolean;
3738
}
3839
>
3940
>;
-25.9 KB
Loading
9.79 KB
Loading
26.1 KB
Loading

0 commit comments

Comments
Β (0)