Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand commitlint fundamentals ๐ฏ
- Apply commit standards in real projects ๐๏ธ
- Debug common commit issues ๐
- Write type-safe commit configurations โจ
๐ฏ Introduction
Welcome to this essential tutorial on CommitLint! ๐ In this guide, weโll explore how to enforce commit message standards in your TypeScript projects.
Youโll discover how commitlint can transform your git workflow experience. Whether youโre building web applications ๐, server-side code ๐ฅ๏ธ, or libraries ๐, understanding commit message standards is essential for maintaining a clean, readable project history.
By the end of this tutorial, youโll feel confident setting up and configuring commitlint in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding CommitLint
๐ค What is CommitLint?
CommitLint is like a grammar checker for your git commits ๐จ. Think of it as a quality control tool that helps you maintain consistent, meaningful commit messages throughout your projectโs history.
In TypeScript projects, commitlint enforces structured commit messages that are clear and informative ๐. This means you can:
- โจ Maintain consistent commit message format
- ๐ Generate automated changelogs and releases
- ๐ก๏ธ Improve team collaboration through clear communication
๐ก Why Use CommitLint?
Hereโs why developers love commitlint:
- Consistency ๐: All team members follow the same commit format
- Automation ๐ป: Enables automatic changelog generation
- Communication ๐: Commits become self-documenting
- Quality ๐ง: Prevents unclear or meaningless commit messages
Real-world example: Imagine working on a shopping cart ๐. With commitlint, you can immediately understand what each commit does: feat(cart): add item quantity selector
vs a vague fixed stuff
.
๐ง Basic Syntax and Usage
๐ Simple Setup
Letโs start with a friendly example:
# ๐ Install commitlint and config
npm install --save-dev @commitlint/cli @commitlint/config-conventional
# ๐จ Create commitlint config
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
๐ก Explanation: Weโre installing commitlint with the conventional config, which follows industry standards!
๐ฏ Common Commit Format
Hereโs the format youโll use daily:
type(scope): description
[optional body]
[optional footer]
// ๐๏ธ Example TypeScript commit types
type CommitType =
| "feat" // โจ New feature
| "fix" // ๐ Bug fix
| "docs" // ๐ Documentation
| "style" // ๐จ Code style changes
| "refactor" // ๐ง Code refactoring
| "test" // ๐งช Adding tests
| "chore"; // ๐ Maintenance tasks
// ๐จ Example scopes for a TypeScript project
type CommitScope =
| "auth" // ๐ Authentication
| "cart" // ๐ Shopping cart
| "api" // ๐ API layer
| "types" // ๐ Type definitions
| "utils"; // ๐ ๏ธ Utility functions
๐ก Practical Examples
๐ Example 1: E-commerce Project Commits
Letโs see real commit messages:
// ๐๏ธ Define commit message interface
interface CommitMessage {
type: string;
scope?: string;
description: string;
body?: string;
emoji: string;
}
// ๐ Shopping cart feature commits
const ecommerceCommits: CommitMessage[] = [
{
type: "feat",
scope: "cart",
description: "add item quantity selector",
emoji: "โจ"
},
{
type: "fix",
scope: "checkout",
description: "resolve payment validation error",
body: "Payment form now properly validates credit card numbers",
emoji: "๐"
},
{
type: "docs",
scope: "api",
description: "update product endpoints documentation",
emoji: "๐"
},
{
type: "test",
scope: "cart",
description: "add unit tests for cart calculations",
emoji: "๐งช"
}
];
// ๐ฎ Format commit message
const formatCommit = (commit: CommitMessage): string => {
const scope = commit.scope ? `(${commit.scope})` : "";
return `${commit.type}${scope}: ${commit.description}`;
};
// ๐ Display formatted commits
ecommerceCommits.forEach(commit => {
console.log(`${commit.emoji} ${formatCommit(commit)}`);
});
๐ฏ Try it yourself: Add commits for a user profile feature!
๐ฎ Example 2: Game Development Commits
Letโs make it fun with game commits:
// ๐ Game development commit tracker
interface GameCommit {
type: "feat" | "fix" | "perf" | "style";
scope: "player" | "enemy" | "ui" | "audio" | "physics";
description: string;
impact: "low" | "medium" | "high";
emoji: string;
}
class GameCommitTracker {
private commits: GameCommit[] = [];
// ๐ฎ Add new commit
addCommit(commit: GameCommit): void {
this.commits.push(commit);
console.log(`${commit.emoji} Added: ${this.formatCommit(commit)}`);
// ๐ Celebrate high-impact commits
if (commit.impact === "high") {
console.log("๐ High-impact commit! Great work!");
}
}
// ๐ Format commit message
private formatCommit(commit: GameCommit): string {
return `${commit.type}(${commit.scope}): ${commit.description}`;
}
// ๐ Get commit statistics
getStats(): void {
const types = this.commits.reduce((acc, commit) => {
acc[commit.type] = (acc[commit.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log("๐ Commit Statistics:");
Object.entries(types).forEach(([type, count]) => {
console.log(` ${type}: ${count} commits`);
});
}
}
// ๐ฎ Test it out!
const gameTracker = new GameCommitTracker();
gameTracker.addCommit({
type: "feat",
scope: "player",
description: "add double jump ability",
impact: "high",
emoji: "๐ฆ"
});
gameTracker.addCommit({
type: "fix",
scope: "enemy",
description: "correct collision detection bug",
impact: "medium",
emoji: "๐"
});
๐ Advanced Concepts
๐งโโ๏ธ Custom Rules Configuration
When youโre ready to level up, try custom commitlint rules:
// ๐ฏ Advanced commitlint configuration
interface CommitlintConfig {
extends: string[];
rules: Record<string, [number, string, any?]>;
prompt?: {
questions: Record<string, any>;
};
}
// ๐ช Custom TypeScript project config
const advancedConfig: CommitlintConfig = {
extends: ['@commitlint/config-conventional'],
rules: {
// ๐จ Custom type enum
'type-enum': [
2,
'always',
[
'feat', // โจ New feature
'fix', // ๐ Bug fix
'docs', // ๐ Documentation
'style', // ๐จ Formatting
'refactor', // ๐ง Code restructure
'perf', // ๐ Performance
'test', // ๐งช Testing
'build', // ๐๏ธ Build system
'ci', // ๐ค CI/CD
'chore', // ๐ Maintenance
'revert' // โช Revert changes
]
],
// ๐ Description length limits
'subject-max-length': [2, 'always', 50],
'subject-min-length': [2, 'always', 10],
// ๐ฏ Scope validation
'scope-enum': [
2,
'always',
[
'api', // ๐ API layer
'auth', // ๐ Authentication
'cart', // ๐ Shopping functionality
'ui', // ๐จ User interface
'types', // ๐ TypeScript types
'utils', // ๐ ๏ธ Utilities
'tests', // ๐งช Test files
'docs' // ๐ Documentation
]
]
}
};
๐๏ธ Husky Integration
For the brave developers who want automated enforcement:
// ๐ Husky hooks configuration
interface HuskyConfig {
hooks: Record<string, string>;
}
const huskySetup: HuskyConfig = {
hooks: {
// ๐ Check commit message before commit
'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
// ๐ฏ Run tests before push
'pre-push': 'npm run test && npm run typecheck'
}
};
// ๐ ๏ธ Package.json scripts for automation
const packageScripts = {
"prepare": "husky install",
"commit": "git-cz", // ๐จ Interactive commits
"typecheck": "tsc --noEmit", // โ
Type checking
"lint": "eslint . --ext .ts,.tsx", // ๐ Code linting
"test": "jest" // ๐งช Run tests
};
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Vague Commit Messages
// โ Wrong way - vague and unhelpful!
const badCommits = [
"fix stuff", // ๐ฐ What stuff?
"update code", // ๐คทโโ๏ธ What code?
"changes", // ๐ฅ What changes?
"wip" // ๐ซ Work in progress forever?
];
// โ
Correct way - clear and descriptive!
const goodCommits = [
"fix(auth): resolve JWT token expiration handling", // ๐ Clear scope and issue
"feat(cart): implement persistent cart storage", // ๐ Specific feature
"docs(api): add authentication endpoint examples", // ๐ Clear documentation update
"test(utils): add unit tests for date formatting" // ๐งช Specific test addition
];
๐คฏ Pitfall 2: Ignoring Conventional Format
// โ Non-standard format - breaks automation!
interface BadCommitFormat {
message: string;
problems: string[];
}
const badFormats: BadCommitFormat[] = [
{
message: "Fixed the bug in shopping cart",
problems: ["โ No type prefix", "โ No scope", "โ Vague description"]
},
{
message: "FEATURE: Added new user authentication system!!!",
problems: ["โ Wrong case", "โ No scope", "โ Excessive punctuation"]
}
];
// โ
Correct conventional format!
const correctFormat = {
structure: "type(scope): description",
examples: [
"feat(auth): implement OAuth2 integration",
"fix(cart): resolve quantity update race condition",
"docs(readme): update installation instructions"
]
};
๐ ๏ธ Best Practices
- ๐ฏ Be Specific: Use clear scopes and descriptive messages
- ๐ Follow Convention: Stick to conventional commit format
- ๐ก๏ธ Use Present Tense: โadd featureโ not โadded featureโ
- ๐จ Keep It Short: Aim for 50 characters or less in subject
- โจ Include Context: Add body for complex changes
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Commit Message Validator
Create a TypeScript commit message validator:
๐ Requirements:
- โ Validate commit message format
- ๐ท๏ธ Check allowed types and scopes
- ๐ค Provide helpful error messages
- ๐ Support custom rules
- ๐จ Include emoji suggestions!
๐ Bonus Points:
- Add interactive commit message builder
- Implement severity levels (error, warning, info)
- Create commit message templates
๐ก Solution
๐ Click to see solution
// ๐ฏ Our commit message validator!
interface CommitRule {
name: string;
pattern: RegExp;
message: string;
severity: "error" | "warning" | "info";
}
interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
suggestions: string[];
}
class CommitMessageValidator {
private allowedTypes = [
"feat", "fix", "docs", "style", "refactor",
"perf", "test", "build", "ci", "chore", "revert"
];
private allowedScopes = [
"api", "auth", "cart", "ui", "types", "utils", "tests", "docs"
];
private rules: CommitRule[] = [
{
name: "conventional-format",
pattern: /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,50}$/,
message: "Commit must follow conventional format: type(scope): description",
severity: "error"
},
{
name: "no-uppercase",
pattern: /^[a-z]/,
message: "Commit message should start with lowercase letter",
severity: "warning"
}
];
// โ
Validate commit message
validate(commitMessage: string): ValidationResult {
const result: ValidationResult = {
valid: true,
errors: [],
warnings: [],
suggestions: []
};
// ๐ Parse commit message
const parsed = this.parseCommit(commitMessage);
if (!parsed) {
result.valid = false;
result.errors.push("โ Invalid commit format");
result.suggestions.push("๐ก Use: type(scope): description");
return result;
}
// ๐ฏ Validate type
if (!this.allowedTypes.includes(parsed.type)) {
result.valid = false;
result.errors.push(`โ Invalid type: ${parsed.type}`);
result.suggestions.push(`โจ Allowed types: ${this.allowedTypes.join(", ")}`);
}
// ๐ท๏ธ Validate scope (if provided)
if (parsed.scope && !this.allowedScopes.includes(parsed.scope)) {
result.warnings.push(`โ ๏ธ Unknown scope: ${parsed.scope}`);
result.suggestions.push(`๐ฏ Common scopes: ${this.allowedScopes.join(", ")}`);
}
// ๐ Check description length
if (parsed.description.length > 50) {
result.warnings.push("โ ๏ธ Description too long (>50 chars)");
result.suggestions.push("โ๏ธ Consider shortening the description");
}
return result;
}
// ๐ Parse commit message
private parseCommit(message: string): { type: string; scope?: string; description: string } | null {
const match = message.match(/^(\w+)(\(([^)]+)\))?: (.+)$/);
if (!match) return null;
return {
type: match[1],
scope: match[3],
description: match[4]
};
}
// ๐จ Generate example commits
generateExamples(): string[] {
return [
"feat(auth): add OAuth2 integration ๐",
"fix(cart): resolve item duplication bug ๐",
"docs(api): update endpoint documentation ๐",
"test(utils): add date formatting tests ๐งช",
"refactor(types): simplify user interface ๐จ"
];
}
// ๐ Display validation results
displayResults(result: ValidationResult): void {
if (result.valid) {
console.log("โ
Commit message is valid! ๐");
} else {
console.log("โ Commit message validation failed");
}
result.errors.forEach(error => console.log(error));
result.warnings.forEach(warning => console.log(warning));
result.suggestions.forEach(suggestion => console.log(suggestion));
}
}
// ๐ฎ Test the validator!
const validator = new CommitMessageValidator();
// โ
Test valid commit
const validResult = validator.validate("feat(cart): add item recommendations");
validator.displayResults(validResult);
// โ Test invalid commit
const invalidResult = validator.validate("Fixed some stuff");
validator.displayResults(invalidResult);
// ๐จ Show examples
console.log("\n๐ก Example commits:");
validator.generateExamples().forEach(example => console.log(example));
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Setup commitlint with confidence ๐ช
- โ Write conventional commits that follow standards ๐ก๏ธ
- โ Configure custom rules for your project needs ๐ฏ
- โ Integrate with CI/CD pipelines like a pro ๐
- โ Build better team workflows with TypeScript! ๐
Remember: Consistent commit messages are your projectโs story! They help everyone understand what happened and when. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered CommitLint!
Hereโs what to do next:
- ๐ป Set up commitlint in your current project
- ๐๏ธ Configure custom rules for your teamโs needs
- ๐ Move on to our next tutorial: Husky Git Hooks Integration
- ๐ Share your clean commit history with others!
Remember: Every great codebase has a clear history. Keep committing with purpose, keep learning, and most importantly, have fun! ๐
Happy coding! ๐๐โจ