Skip to content

Commit 0d66257

Browse files
committed
feat(commitlint): support file-path check & *-exclude
1 parent 4060bf5 commit 0d66257

8 files changed

Lines changed: 167 additions & 56 deletions

File tree

.lintstagedrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"**/*.{js,ts,jsx,tsx,md}": [
33
"eslint --fix"
4+
],
5+
"*": [
6+
"node packages/commitlint/dist/index.js --file-path"
47
]
58
}

packages/commitlint/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
# @deot/dev-commitlint
22

3+
- commit message
4+
5+
```
6+
dd-commitlint --message [file-path] --message-exclude
7+
```
8+
9+
- commit file-path (kebabCase: `xxx-xxx-xxx`)
10+
11+
```
12+
dd-commitlint --file-path [file-path1] [file-path2] --file-path-exclude
13+
```
14+

packages/commitlint/__tests__/fixtures/filepath

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
any
Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,41 @@
1-
import { run } from '@deot/dev-commitlint';
1+
import * as path from 'node:path';
2+
import { Message, FilePath } from '@deot/dev-commitlint';
23

34
describe('index.ts', () => {
4-
it('allow', async () => {
5+
it('message/allow', async () => {
56
expect.hasAssertions();
6-
expect(run('feat: any')).toBeFalsy();
7-
expect(run('void: any\n')).toBeFalsy();
8-
expect(run('revert: feat: any')).toBeFalsy();
9-
expect(run('Revert "style: any"')).toBeFalsy();
10-
expect(run(`Merge remote-tracking branch 'origin/xx' into test`)).toBeFalsy();
7+
expect(Message.lint('feat: any')).toBeFalsy();
8+
expect(Message.lint('void: any\n')).toBeFalsy();
9+
expect(Message.lint('revert: feat: any')).toBeFalsy();
10+
expect(Message.lint('Revert "style: any"')).toBeFalsy();
11+
expect(Message.lint(`Merge remote-tracking branch 'origin/xx' into test`)).toBeFalsy();
1112

12-
expect(run(`Merge branch 'foo' into 'bar'`)).toBeFalsy();
13+
expect(Message.lint(`Merge branch 'foo' into 'bar'`)).toBeFalsy();
1314
});
1415

15-
it('throw error', async () => {
16+
it('message/throw error', async () => {
1617
expect.hasAssertions();
17-
expect(run('any')).toBeTruthy();
18-
expect(run('aaa: any')).toBeTruthy();
18+
expect(Message.lint('any')).toBeTruthy();
19+
expect(Message.lint('aaa: any')).toBeTruthy();
20+
});
21+
22+
it('message/allow/run', async () => {
23+
const fakePath = path.resolve(__dirname, './fixtures/message');
24+
expect.hasAssertions();
25+
expect(Message.run(1, ['--message', fakePath])).toBeTruthy();
26+
expect(Message.run(1, ['--message', fakePath, '--message-exclude', 'any,any1'])).toBeFalsy();
27+
});
28+
29+
it('file-path/allow/run', async () => {
30+
const fakePath = path.resolve(__dirname, './fixtures/message');
31+
expect.hasAssertions();
32+
expect(FilePath.run(1, ['--file-path', fakePath])).toBeFalsy();
33+
expect(FilePath.run(1, ['--file-path', fakePath, '--file-path-exclude', 'message'])).toBeFalsy();
34+
});
35+
36+
it('file-path/throw error', async () => {
37+
expect.hasAssertions();
38+
expect(FilePath.lint('Any')).toBeTruthy();
39+
expect(FilePath.lint('any')).toBeFalsy();
1940
});
2041
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as fs from 'node:fs';
2+
import * as path from 'node:path';
3+
4+
const cwd = process.cwd();
5+
6+
export class FilePath {
7+
static lint = (filePath: string) => {
8+
if (/[A-Z]/g.test(filePath)) {
9+
return filePath;
10+
}
11+
};
12+
13+
/* istanbul ignore next -- @preserve */
14+
static run = (argvStartIndex: number, argv = process.argv) => {
15+
const filePaths = argv.slice(argvStartIndex, argv.length);
16+
17+
const excludeIndex = argv.findIndex(i => i === '--file-path-exclude');
18+
const excludes = excludeIndex !== -1 && argv[excludeIndex + 1]
19+
? new RegExp(`(${argv[excludeIndex + 1].replace(/,/g, '|')})`)
20+
: void 0;
21+
22+
const errors = [] as string[];
23+
for (let i = 0; i < filePaths.length; i++) {
24+
const filePath = filePaths[i];
25+
if (filePath && fs.existsSync(filePath) && path.extname(filePath) !== '.md') {
26+
const filePathV2 = path.relative(cwd, filePath);
27+
if (excludes && excludes.test(filePath)) continue;
28+
const error = FilePath.lint(filePathV2);
29+
error && errors.push(error);
30+
}
31+
}
32+
if (errors.length) {
33+
return `Invalid Commit FilePaths: \n\n${errors.join('\n')}\n\nAllowed Filepath: xxx-xxx-xxx\n`;
34+
}
35+
};
36+
};

packages/commitlint/src/index.ts

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,31 @@
1-
import * as fs from 'node:fs';
1+
import { Message } from './message.ts';
2+
import { FilePath } from './file-path.ts';
23

3-
// eslint-disable-next-line @stylistic/max-len
4-
const commitRE = /^(revert:? "?|Revert "?)?(void|fix|feat|docs|style|perf|test|types|build|chore|refactor|workflow|ci|wip|release|breaking change)(\(.+\))?: .{1,50}/;
5-
const mergeRE = /Merge (remote-tracking )?branch /;
4+
export { Message, FilePath };
65

7-
export const run = (commitMessage: string) => {
8-
let content = '';
9-
if (!commitRE.test(commitMessage) && !mergeRE.test(commitMessage)) {
10-
content += `\nInvalid commit message: "${commitMessage}".\n`;
11-
content += `\nExamples: \n`;
12-
content += ` - fix(Button): incorrect style\n`;
13-
content += ` - feat(Button): incorrect style\n`;
14-
content += ` - docs(Button): fix typo\n`;
15-
content += `\nAllowed Types:\n`;
16-
content += ` - fix:修补bug\n`;
17-
content += ` - feat:新功能(feature)\n`;
18-
content += ` - docs:文档(documentation)\n`;
19-
content += ` - style:不影响代码含义的更改,可能与代码格式有关,例如空格、缺少分号等\n`;
20-
content += ` - test:包括新的或更正以前的测试\n`;
21-
content += ` - chore:构建过程或辅助工具的变动\n`;
22-
content += ` - refactor:重构(即不是新增功能,也不是修改bug的代码变动)\n`;
23-
content += ` - perf:性能改进(performance improvements)\n`;
24-
content += ` - types:类型\n`;
25-
content += ` - build:影响构建系统或外部依赖项的更改\n`;
26-
content += ` - ci: 持续集成相关\n`;
27-
content += ` - breaking change:破坏性修改\n`;
28-
content += ` - void:无类型,通常用于初始化\n`;
29-
content += ` - Merge branch 'foo' into 'bar'\n`;
30-
content += ` - Revert ""\n`;
31-
}
32-
return content;
33-
};
6+
/**
7+
* 一次只执行一项任务
8+
* @returns error
9+
*/
10+
const check = () => {
11+
for (let i = 0; i < process.argv.length; i++) {
12+
/* istanbul ignore next -- @preserve */
13+
if (['--edit', '--message'].includes(process.argv[i])) { // @deprecated(--edit)
14+
/* istanbul ignore next -- @preserve */
15+
return Message.run(i + 1);
16+
}
3417

35-
const index = process.argv.findIndex(arg => arg === '--edit');
36-
/* istanbul ignore next -- @preserve */
37-
const filepath = index !== -1 && process.argv[index + 1];
38-
/* istanbul ignore next -- @preserve */
39-
const message = filepath && fs.existsSync(filepath)
40-
? fs.readFileSync(filepath, 'utf-8').trim()
41-
: (filepath || '');
18+
/* istanbul ignore next -- @preserve */
19+
if (['--file-path'].includes(process.argv[i])) {
20+
/* istanbul ignore next -- @preserve */
21+
return FilePath.run(i + 1);
22+
}
23+
};
24+
};
4225

26+
const error = check();
4327
/* istanbul ignore next -- @preserve */
44-
if (message) {
45-
const error = run(message);
46-
if (error) {
47-
console.error(error);
48-
process.exit(1);
49-
}
50-
}
28+
if (error) {
29+
console.error(error);
30+
process.exit(1);
31+
};

packages/commitlint/src/message.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as fs from 'node:fs';
2+
3+
// eslint-disable-next-line @stylistic/max-len
4+
const commitRE = /^(revert:? "?|Revert "?)?(void|fix|feat|docs|style|perf|test|types|build|chore|refactor|workflow|ci|wip|release|breaking change)(\(.+\))?: .{1,50}/;
5+
const mergeRE = /Merge (remote-tracking )?branch /;
6+
7+
export class Message {
8+
static lint = (commitMessage: string) => {
9+
let content = '';
10+
if (!commitRE.test(commitMessage) && !mergeRE.test(commitMessage)) {
11+
content += `\nInvalid Commit Message: "${commitMessage}".\n`;
12+
content += `\nExamples: \n`;
13+
content += ` - fix(Button): incorrect style\n`;
14+
content += ` - feat(Button): incorrect style\n`;
15+
content += ` - docs(Button): fix typo\n`;
16+
content += `\nAllowed Types:\n`;
17+
content += ` - fix:修补bug\n`;
18+
content += ` - feat:新功能(feature)\n`;
19+
content += ` - docs:文档(documentation)\n`;
20+
content += ` - style:不影响代码含义的更改,可能与代码格式有关,例如空格、缺少分号等\n`;
21+
content += ` - test:包括新的或更正以前的测试\n`;
22+
content += ` - chore:构建过程或辅助工具的变动\n`;
23+
content += ` - refactor:重构(即不是新增功能,也不是修改bug的代码变动)\n`;
24+
content += ` - perf:性能改进(performance improvements)\n`;
25+
content += ` - types:类型\n`;
26+
content += ` - build:影响构建系统或外部依赖项的更改\n`;
27+
content += ` - ci: 持续集成相关\n`;
28+
content += ` - breaking change:破坏性修改\n`;
29+
content += ` - void:无类型,通常用于初始化\n`;
30+
content += ` - Merge branch 'foo' into 'bar'\n`;
31+
content += ` - Revert ""\n`;
32+
}
33+
return content;
34+
};
35+
36+
/* istanbul ignore next -- @preserve */
37+
static run = (argvStartIndex: number, argv = process.argv) => {
38+
const filepath = argv[argvStartIndex];
39+
40+
const message = filepath && fs.existsSync(filepath)
41+
? fs.readFileSync(filepath, 'utf-8').trim()
42+
: (filepath || '');
43+
44+
const excludeIndex = argv.findIndex(i => i === '--message-exclude');
45+
const excludes = excludeIndex !== -1 && argv[excludeIndex + 1]
46+
? new RegExp(`(${argv[excludeIndex + 1].replace(/,/g, '|')})`)
47+
: void 0;
48+
49+
/* istanbul ignore next -- @preserve */
50+
if (message) {
51+
if (excludes && excludes.test(message)) {
52+
return;
53+
}
54+
return Message.lint(message);
55+
}
56+
};
57+
};

0 commit comments

Comments
 (0)