Skip to content

Commit ac8fb25

Browse files
committed
feat: add commitlint
1 parent 1387b0e commit ac8fb25

7 files changed

Lines changed: 272 additions & 176 deletions

File tree

app/branch.helper.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env node
2+
3+
import { existsSync, lstatSync, readFileSync } from 'node:fs';
4+
import { dirname, join, isAbsolute, parse, resolve } from 'node:path';
5+
6+
/**
7+
* Conventional Branch Naming Convention
8+
* @see https://conventional-branch.github.io
9+
*/
10+
const conventionalBranchRegex = /^(master|((bugfix|chore|feature|hotfix|release)\/[a-z0-9]+([.-][a-z0-9]+)*))$/;
11+
12+
/**
13+
* Finds the actual git directory. Handles standard .git folders and worktree .git files.
14+
* @returns Current git directory.
15+
*/
16+
function getGitDir(): string {
17+
let currentDir = process.cwd();
18+
19+
while (currentDir !== parse(currentDir).root) {
20+
const dotGitPath = join(currentDir, '.git');
21+
22+
if (existsSync(dotGitPath)) {
23+
const stats = lstatSync(dotGitPath);
24+
if (stats.isFile()) {
25+
const content = readFileSync(dotGitPath, 'utf-8');
26+
const match = /^gitdir:\s+(.*)$/m.exec(content);
27+
if (match?.[1]) {
28+
const gitDir = match[1].trim();
29+
// If worktree path is relative, resolve it relative to the .git file location
30+
return isAbsolute(gitDir) ? gitDir : resolve(currentDir, gitDir);
31+
}
32+
}
33+
return dotGitPath;
34+
}
35+
currentDir = dirname(currentDir);
36+
}
37+
38+
process.stderr.write('Error: Not a git repository (no .git found up the tree).\n');
39+
process.exit(1);
40+
}
41+
42+
/**
43+
* Reads the current branch name from the filesystem.
44+
* @returns Current branch name.
45+
*/
46+
function getCurrentBranch(): string {
47+
try {
48+
const gitDir = getGitDir();
49+
const headPath = join(gitDir, 'HEAD');
50+
51+
if (!existsSync(headPath)) throw new Error(`HEAD not found at ${headPath}`);
52+
53+
const headContent = readFileSync(headPath, 'utf-8').trim();
54+
if (headContent.startsWith('ref: ')) return headContent.replace('ref: refs/heads/', '');
55+
56+
return headContent;
57+
} catch (error) {
58+
process.stderr.write(`Error reading branch: ${error instanceof Error ? error.message : String(error)}\n`);
59+
process.exit(1);
60+
}
61+
}
62+
63+
/**
64+
* Ensures the current branch name follows a naming convention and subsequent checks.
65+
* Exits with code 1 if the branch name is invalid.
66+
*/
67+
function main() {
68+
const branch = getCurrentBranch();
69+
70+
if (conventionalBranchRegex.test(branch)) {
71+
process.stdout.write(`Branch name '${branch}' is valid.\n`);
72+
process.exit(0);
73+
} else {
74+
process.stderr.write(`
75+
Error: Invalid branch name '${branch}'.
76+
77+
This project enforces Conventional Branches.
78+
See: https://conventional-branch.github.io
79+
Example: feature/login-page or bugfix/ticket-123
80+
81+
Rename your current branch with:
82+
git branch -m <type>/<description>
83+
`);
84+
process.exit(1);
85+
}
86+
}
87+
88+
main();

0 commit comments

Comments
 (0)