lint-tools-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +371 -0
- package/package.json +35 -0
- package/src/commitlint.js +110 -0
- package/src/eslint.js +138 -0
- package/src/githooks.js +190 -0
- package/src/index.js +401 -0
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Lint Tools SDK
|
|
2
|
+
|
|
3
|
+
一个用于在多个Git仓库中统一代码质量管理的前端代码规范工具SDK(npm包形式)。
|
|
4
|
+
|
|
5
|
+
## 核心功能
|
|
6
|
+
|
|
7
|
+
- ✅ **ESLint集成**:支持ESLint v9+ flat config格式,实现代码格式化校验
|
|
8
|
+
- ✅ **commitlint集成**:提交信息规范校验,确保提交信息格式统一
|
|
9
|
+
- ✅ **Git Hooks自动触发**:通过husky在提交代码时自动对暂存文件进行格式化校验
|
|
10
|
+
- ✅ **lint-staged**:仅对Git暂存文件执行ESLint校验与修复
|
|
11
|
+
- ✅ **配置覆盖机制**:项目级配置自动合并覆盖SDK默认配置
|
|
12
|
+
- ✅ **CLI命令支持**:提供丰富的命令行工具,方便使用和集成
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 全局安装(可选)
|
|
18
|
+
npm install -g lint-tools-sdk
|
|
19
|
+
|
|
20
|
+
# 局部安装
|
|
21
|
+
npm install --save-dev lint-tools-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 快速开始
|
|
25
|
+
|
|
26
|
+
### 1. 初始化项目
|
|
27
|
+
|
|
28
|
+
在项目根目录下执行:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx lint-tools-sdk init
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
此命令会:
|
|
35
|
+
- 安装Git Hooks(pre-commit和commit-msg)
|
|
36
|
+
- 创建默认配置文件(eslint.config.js、.commitlintrc.js、.lintstagedrc.js)
|
|
37
|
+
- 更新package.json脚本
|
|
38
|
+
|
|
39
|
+
### 2. 基本使用
|
|
40
|
+
|
|
41
|
+
#### 代码校验
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 全量代码校验
|
|
45
|
+
npx lint-tools-sdk lint
|
|
46
|
+
|
|
47
|
+
# 自动修复代码问题
|
|
48
|
+
npx lint-tools-sdk lint:fix
|
|
49
|
+
|
|
50
|
+
# 仅校验暂存文件
|
|
51
|
+
npx lint-tools-sdk lint:staged
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### 提交信息校验
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 校验提交信息
|
|
58
|
+
npx lint-tools-sdk commitlint "feat: add new feature"
|
|
59
|
+
|
|
60
|
+
# 使用git commit时会自动触发校验
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### 查看诊断信息
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 查看当前配置和服务状态
|
|
67
|
+
npx lint-tools-sdk status
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 配置说明
|
|
71
|
+
|
|
72
|
+
### 1. 项目级配置
|
|
73
|
+
|
|
74
|
+
可以通过以下方式覆盖SDK的默认配置:
|
|
75
|
+
|
|
76
|
+
#### a) package.json中的lintToolsConfig
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"lintToolsConfig": {
|
|
81
|
+
"eslint": {
|
|
82
|
+
"enabled": true,
|
|
83
|
+
"fix": true
|
|
84
|
+
},
|
|
85
|
+
"commitlint": {
|
|
86
|
+
"enabled": true
|
|
87
|
+
},
|
|
88
|
+
"githooks": {
|
|
89
|
+
"enabled": true
|
|
90
|
+
},
|
|
91
|
+
"lintStaged": {
|
|
92
|
+
"enabled": true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### b) .linttoolsrc.js配置文件
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
module.exports = {
|
|
102
|
+
eslint: {
|
|
103
|
+
enabled: true,
|
|
104
|
+
fix: true
|
|
105
|
+
},
|
|
106
|
+
commitlint: {
|
|
107
|
+
enabled: true
|
|
108
|
+
},
|
|
109
|
+
githooks: {
|
|
110
|
+
enabled: true
|
|
111
|
+
},
|
|
112
|
+
lintStaged: {
|
|
113
|
+
enabled: true
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. ESLint配置
|
|
119
|
+
|
|
120
|
+
SDK使用ESLint v9+的flat config格式,默认配置文件为`eslint.config.js`:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
import globals from "globals";
|
|
124
|
+
import pluginJs from "@eslint/js";
|
|
125
|
+
|
|
126
|
+
export default [
|
|
127
|
+
{
|
|
128
|
+
files: ["**/*.js"],
|
|
129
|
+
languageOptions: {
|
|
130
|
+
ecmaVersion: "latest",
|
|
131
|
+
sourceType: "module",
|
|
132
|
+
globals: {
|
|
133
|
+
...globals.node,
|
|
134
|
+
...globals.browser
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
rules: {
|
|
138
|
+
...pluginJs.configs.recommended.rules,
|
|
139
|
+
// 项目自定义规则
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. commitlint配置
|
|
146
|
+
|
|
147
|
+
默认配置文件为`.commitlintrc.js`:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
module.exports = {
|
|
151
|
+
extends: ['@commitlint/config-conventional']
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 4. lint-staged配置
|
|
156
|
+
|
|
157
|
+
默认配置文件为`.lintstagedrc.js`:
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
module.exports = {
|
|
161
|
+
'*.js': ['eslint --fix', 'git add'],
|
|
162
|
+
'*.jsx': ['eslint --fix', 'git add'],
|
|
163
|
+
'*.ts': ['eslint --fix', 'git add'],
|
|
164
|
+
'*.tsx': ['eslint --fix', 'git add']
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## CLI命令
|
|
169
|
+
|
|
170
|
+
| 命令 | 描述 |
|
|
171
|
+
|------|------|
|
|
172
|
+
| `init` | 初始化项目,安装Git Hooks并创建默认配置 |
|
|
173
|
+
| `lint` | 全量代码校验 |
|
|
174
|
+
| `lint:fix` | 自动修复代码问题 |
|
|
175
|
+
| `lint:staged` | 仅校验暂存文件 |
|
|
176
|
+
| `commitlint <message>` | 校验提交信息格式 |
|
|
177
|
+
| `status` | 查看当前配置和服务状态 |
|
|
178
|
+
|
|
179
|
+
## Git Hooks
|
|
180
|
+
|
|
181
|
+
### pre-commit
|
|
182
|
+
|
|
183
|
+
执行lint-staged,对暂存文件进行ESLint校验与修复:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npx lint-staged
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### commit-msg
|
|
190
|
+
|
|
191
|
+
执行commitlint,校验提交信息格式:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npx commitlint --edit "$1"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 项目结构
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
lint-tools-sdk/
|
|
201
|
+
├── src/
|
|
202
|
+
│ ├── index.js # SDK主入口和CLI命令处理
|
|
203
|
+
│ ├── eslint.js # ESLint服务实现
|
|
204
|
+
│ ├── commitlint.js # commitlint服务实现
|
|
205
|
+
│ └── githooks.js # Git Hooks服务实现
|
|
206
|
+
├── package.json
|
|
207
|
+
└── README.md
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 模块化架构
|
|
211
|
+
|
|
212
|
+
### 1. LintToolsSDK (主类)
|
|
213
|
+
|
|
214
|
+
整合所有服务,提供统一的API和CLI接口。
|
|
215
|
+
|
|
216
|
+
### 2. ESLintService
|
|
217
|
+
|
|
218
|
+
- 配置加载与管理
|
|
219
|
+
- 文件校验与修复
|
|
220
|
+
- 暂存文件处理
|
|
221
|
+
|
|
222
|
+
### 3. CommitLintService
|
|
223
|
+
|
|
224
|
+
- 提交信息校验
|
|
225
|
+
- 配置文件管理
|
|
226
|
+
|
|
227
|
+
### 4. GitHooksService
|
|
228
|
+
|
|
229
|
+
- husky钩子安装与管理
|
|
230
|
+
- 钩子脚本生成
|
|
231
|
+
|
|
232
|
+
## 技术栈
|
|
233
|
+
|
|
234
|
+
- **Node.js**:运行环境
|
|
235
|
+
- **ESLint v9+**:代码质量检查
|
|
236
|
+
- **commitlint**:提交信息规范
|
|
237
|
+
- **husky**:Git Hooks管理
|
|
238
|
+
- **lint-staged**:暂存文件处理
|
|
239
|
+
|
|
240
|
+
## 兼容性
|
|
241
|
+
|
|
242
|
+
- **ESLint**:^9.0.0
|
|
243
|
+
- **husky**:^8.0.0 || ^9.0.0
|
|
244
|
+
- **lint-staged**:^15.0.0 || ^16.0.0
|
|
245
|
+
|
|
246
|
+
## 项目构建与发布
|
|
247
|
+
|
|
248
|
+
### 1. 构建项目
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# 安装依赖
|
|
252
|
+
npm install
|
|
253
|
+
|
|
254
|
+
# 验证代码(检查语法和类型错误)
|
|
255
|
+
npm run lint
|
|
256
|
+
|
|
257
|
+
# 运行测试(如有)
|
|
258
|
+
npm run test
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 2. 发布到npm
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# 确保已登录npm
|
|
265
|
+
npm login
|
|
266
|
+
|
|
267
|
+
# 更新版本号(根据语义化版本规范)
|
|
268
|
+
npm version patch # 修复bug,版本号第三位加1
|
|
269
|
+
npm version minor # 新增功能,版本号第二位加1
|
|
270
|
+
npm version major # 大版本更新,版本号第一位加1
|
|
271
|
+
|
|
272
|
+
# 发布到npm
|
|
273
|
+
npm publish
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## 在其他项目中使用与验证
|
|
277
|
+
|
|
278
|
+
### 1. 安装SDK
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# 从npm安装
|
|
282
|
+
npm install --save-dev lint-tools-sdk
|
|
283
|
+
|
|
284
|
+
# 或从本地开发版本安装
|
|
285
|
+
npm install --save-dev ../path/to/lint-tools-sdk
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 2. 初始化配置
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
npx lint-tools-sdk init
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 3. 验证功能
|
|
295
|
+
|
|
296
|
+
#### a) 代码校验
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# 创建一个包含错误的测试文件
|
|
300
|
+
echo "const a = 1 console.log(a)" > test.js
|
|
301
|
+
|
|
302
|
+
# 执行代码校验,应该能检测到错误
|
|
303
|
+
npx lint-tools-sdk lint
|
|
304
|
+
|
|
305
|
+
# 尝试自动修复
|
|
306
|
+
npx lint-tools-sdk lint:fix
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### b) 提交信息校验
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# 测试正确的提交信息(应该通过)
|
|
313
|
+
npx lint-tools-sdk commitlint "feat: add new feature"
|
|
314
|
+
|
|
315
|
+
# 测试错误的提交信息(应该失败)
|
|
316
|
+
npx lint-tools-sdk commitlint "add new feature"
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### c) Git Hooks验证
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# 将测试文件添加到暂存区
|
|
323
|
+
git add test.js
|
|
324
|
+
|
|
325
|
+
# 尝试提交(应该触发pre-commit钩子)
|
|
326
|
+
git commit -m "fix: update test file"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## 贡献
|
|
330
|
+
|
|
331
|
+
欢迎提交Issue和Pull Request!
|
|
332
|
+
|
|
333
|
+
## 许可证
|
|
334
|
+
|
|
335
|
+
ISC
|
|
336
|
+
|
|
337
|
+
## 项目构建与发布
|
|
338
|
+
|
|
339
|
+
### 发布说明
|
|
340
|
+
该项目是一个Node.js SDK,主要包含JavaScript源代码,**不需要**使用webpack、rollup等工具进行打包。由于项目结构简单,直接发布源代码即可正常工作。
|
|
341
|
+
|
|
342
|
+
### 确保发布内容正确
|
|
343
|
+
通过以下配置确保只发布必要的文件:
|
|
344
|
+
1. `.gitignore`:排除node_modules、test-project等非必要文件
|
|
345
|
+
2. `package.json`的`files`字段:明确指定需要发布的文件和目录
|
|
346
|
+
|
|
347
|
+
### 发布步骤
|
|
348
|
+
1. 更新版本号:
|
|
349
|
+
```bash
|
|
350
|
+
npm version patch # 或 minor/major
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
2. 登录npm:
|
|
354
|
+
```bash
|
|
355
|
+
npm login
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
3. 发布到npm:
|
|
359
|
+
```bash
|
|
360
|
+
npm publish
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
4. (可选)发布前验证:
|
|
364
|
+
```bash
|
|
365
|
+
# 查看将被发布的文件
|
|
366
|
+
npm pack --dry-run
|
|
367
|
+
|
|
368
|
+
# 或生成tgz包检查内容
|
|
369
|
+
npm pack
|
|
370
|
+
tar -tf lint-tools-sdk-*.tgz
|
|
371
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lint-tools-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A linting tools SDK for consistent code quality across multiple git repositories",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lint-tools-sdk": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"prepare": "husky install"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["eslint", "commitlint", "git-hooks", "linting", "code-quality"],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@commitlint/cli": "^20.2.0",
|
|
18
|
+
"@commitlint/config-conventional": "^20.2.0",
|
|
19
|
+
"@eslint/js": "^9.39.2",
|
|
20
|
+
"eslint": "^9.39.2",
|
|
21
|
+
"globals": "^16.0.0",
|
|
22
|
+
"husky": "^9.1.7",
|
|
23
|
+
"lint-staged": "^16.2.7"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
|
27
|
+
"husky": "^8.0.0 || ^9.0.0",
|
|
28
|
+
"lint-staged": "^15.0.0 || ^16.0.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src/",
|
|
32
|
+
"README.md",
|
|
33
|
+
"package.json"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
class CommitLintService {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
findCommitlintConfig() {
|
|
12
|
+
const configFiles = [
|
|
13
|
+
'.commitlintrc.js',
|
|
14
|
+
'.commitlintrc.cjs',
|
|
15
|
+
'.commitlintrc.yaml',
|
|
16
|
+
'.commitlintrc.yml',
|
|
17
|
+
'.commitlintrc.json',
|
|
18
|
+
'package.json'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const configFile of configFiles) {
|
|
22
|
+
const filePath = path.join(this.projectRoot, configFile);
|
|
23
|
+
if (fs.existsSync(filePath)) {
|
|
24
|
+
if (configFile === 'package.json') {
|
|
25
|
+
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
26
|
+
if (packageJson.commitlint) {
|
|
27
|
+
return filePath;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
return filePath;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lintCommitMessage(message) {
|
|
39
|
+
try {
|
|
40
|
+
const configPath = this.findCommitlintConfig();
|
|
41
|
+
|
|
42
|
+
if (!configPath) {
|
|
43
|
+
console.error('No commitlint configuration found');
|
|
44
|
+
return { success: false };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Use temporary file to avoid pipe/quoting issues on Windows
|
|
48
|
+
const tempFile = path.join(this.projectRoot, '.temp_commit_message.txt');
|
|
49
|
+
fs.writeFileSync(tempFile, message);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const result = execSync(
|
|
53
|
+
`npx commitlint --edit "${tempFile}"`,
|
|
54
|
+
{ stdio: ['pipe', 'pipe', 'pipe'], cwd: this.projectRoot }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return { success: true, output: result.toString() };
|
|
58
|
+
} finally {
|
|
59
|
+
// Clean up temporary file
|
|
60
|
+
if (fs.existsSync(tempFile)) {
|
|
61
|
+
fs.unlinkSync(tempFile);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: error.stderr ? error.stderr.toString() : error.message,
|
|
68
|
+
output: error.stderr ? error.stderr.toString() : error.message
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
lintCommitFromFile(filePath) {
|
|
74
|
+
try {
|
|
75
|
+
const commitMessage = fs.readFileSync(filePath, 'utf8');
|
|
76
|
+
return this.lintCommitMessage(commitMessage);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Failed to read commit message file:', error);
|
|
79
|
+
return { success: false, error: error.message };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
createDefaultConfig() {
|
|
84
|
+
const configPath = path.join(this.projectRoot, '.commitlintrc.js');
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(configPath)) {
|
|
87
|
+
const defaultConfig = `module.exports = {
|
|
88
|
+
extends: ['@commitlint/config-conventional']
|
|
89
|
+
};`;
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(configPath, defaultConfig);
|
|
92
|
+
console.log('Created default .commitlintrc.js');
|
|
93
|
+
return configPath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return configPath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Get commitlint version info
|
|
100
|
+
getVersion() {
|
|
101
|
+
try {
|
|
102
|
+
const version = execSync('npx commitlint --version', { encoding: 'utf8', cwd: this.projectRoot });
|
|
103
|
+
return version.trim();
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = CommitLintService;
|
package/src/eslint.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const { ESLint } = require('eslint');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
class ESLintService {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async createESLintInstance() {
|
|
12
|
+
// For ESLint v9, we use flat config format
|
|
13
|
+
const eslint = new ESLint({
|
|
14
|
+
fix: this.options.fix || false
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return eslint;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
findESLintConfig() {
|
|
21
|
+
// ESLint v9 uses eslint.config.* files
|
|
22
|
+
const configFiles = [
|
|
23
|
+
'eslint.config.js',
|
|
24
|
+
'eslint.config.cjs',
|
|
25
|
+
'eslint.config.mjs',
|
|
26
|
+
'.eslintrc.js',
|
|
27
|
+
'.eslintrc.cjs',
|
|
28
|
+
'.eslintrc.yaml',
|
|
29
|
+
'.eslintrc.yml',
|
|
30
|
+
'.eslintrc.json',
|
|
31
|
+
'package.json'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const configFile of configFiles) {
|
|
35
|
+
const filePath = path.join(this.projectRoot, configFile);
|
|
36
|
+
if (fs.existsSync(filePath)) {
|
|
37
|
+
if (configFile === 'package.json') {
|
|
38
|
+
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
39
|
+
if (packageJson.eslintConfig) {
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
return filePath;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async lintFiles(files) {
|
|
52
|
+
try {
|
|
53
|
+
const eslint = await this.createESLintInstance();
|
|
54
|
+
const results = await eslint.lintFiles(files);
|
|
55
|
+
|
|
56
|
+
// Fix and write changes if requested
|
|
57
|
+
if (this.options.fix) {
|
|
58
|
+
await ESLint.outputFixes(results);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Format results for output
|
|
62
|
+
const formatter = await eslint.loadFormatter('stylish');
|
|
63
|
+
const resultText = formatter.format(results);
|
|
64
|
+
|
|
65
|
+
// Check if there are any errors
|
|
66
|
+
const hasErrors = results.some(result => result.errorCount > 0);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
success: !hasErrors,
|
|
70
|
+
results: results,
|
|
71
|
+
output: resultText
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('ESLint failed:', error);
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: error.message,
|
|
78
|
+
output: `ESLint error: ${error.message}`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async lintStagedFiles() {
|
|
84
|
+
const lintStagedConfig = this.findLintStagedConfig();
|
|
85
|
+
|
|
86
|
+
if (!lintStagedConfig) {
|
|
87
|
+
console.error('No lint-staged configuration found');
|
|
88
|
+
return { success: false };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Get staged files using git
|
|
93
|
+
const { execSync } = require('child_process');
|
|
94
|
+
const stagedFiles = execSync('git diff --name-only --cached', { encoding: 'utf8' })
|
|
95
|
+
.trim()
|
|
96
|
+
.split('\n')
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
|
|
99
|
+
if (stagedFiles.length === 0) {
|
|
100
|
+
return { success: true, output: 'No staged files to lint' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return await this.lintFiles(stagedFiles);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Failed to get staged files:', error);
|
|
106
|
+
return { success: false, error: error.message };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
findLintStagedConfig() {
|
|
111
|
+
const configFiles = [
|
|
112
|
+
'.lintstagedrc.js',
|
|
113
|
+
'.lintstagedrc.cjs',
|
|
114
|
+
'.lintstagedrc.yaml',
|
|
115
|
+
'.lintstagedrc.yml',
|
|
116
|
+
'.lintstagedrc.json',
|
|
117
|
+
'package.json'
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
for (const configFile of configFiles) {
|
|
121
|
+
const filePath = path.join(this.projectRoot, configFile);
|
|
122
|
+
if (fs.existsSync(filePath)) {
|
|
123
|
+
if (configFile === 'package.json') {
|
|
124
|
+
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
125
|
+
if (packageJson['lint-staged']) {
|
|
126
|
+
return packageJson['lint-staged'];
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
return require(filePath);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = ESLintService;
|
package/src/githooks.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
class GitHooksService {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
9
|
+
this.huskyDir = path.join(this.projectRoot, '.husky');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Initialize husky
|
|
13
|
+
initHusky() {
|
|
14
|
+
try {
|
|
15
|
+
execSync('npx husky install', { stdio: 'inherit', cwd: this.projectRoot });
|
|
16
|
+
console.log('Husky initialized successfully');
|
|
17
|
+
return true;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('Failed to initialize husky:', error);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Install pre-commit hook
|
|
25
|
+
installPreCommitHook(script = null) {
|
|
26
|
+
try {
|
|
27
|
+
const hookScript = script || this.getDefaultPreCommitScript();
|
|
28
|
+
const hookPath = path.join(this.huskyDir, 'pre-commit');
|
|
29
|
+
|
|
30
|
+
// Ensure husky directory exists
|
|
31
|
+
if (!fs.existsSync(this.huskyDir)) {
|
|
32
|
+
if (!this.initHusky()) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Write and make executable
|
|
38
|
+
fs.writeFileSync(hookPath, hookScript);
|
|
39
|
+
fs.chmodSync(hookPath, '755');
|
|
40
|
+
|
|
41
|
+
console.log('Pre-commit hook installed successfully');
|
|
42
|
+
return true;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to install pre-commit hook:', error);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Install commit-msg hook
|
|
50
|
+
installCommitMsgHook(script = null) {
|
|
51
|
+
try {
|
|
52
|
+
const hookScript = script || this.getDefaultCommitMsgScript();
|
|
53
|
+
const hookPath = path.join(this.huskyDir, 'commit-msg');
|
|
54
|
+
|
|
55
|
+
// Ensure husky directory exists
|
|
56
|
+
if (!fs.existsSync(this.huskyDir)) {
|
|
57
|
+
if (!this.initHusky()) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Write and make executable
|
|
63
|
+
fs.writeFileSync(hookPath, hookScript);
|
|
64
|
+
fs.chmodSync(hookPath, '755');
|
|
65
|
+
|
|
66
|
+
console.log('Commit-msg hook installed successfully');
|
|
67
|
+
return true;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Failed to install commit-msg hook:', error);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Install all required hooks
|
|
75
|
+
installAllHooks() {
|
|
76
|
+
const preCommitSuccess = this.installPreCommitHook();
|
|
77
|
+
const commitMsgSuccess = this.installCommitMsgHook();
|
|
78
|
+
|
|
79
|
+
return preCommitSuccess && commitMsgSuccess;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Default pre-commit hook script
|
|
83
|
+
getDefaultPreCommitScript() {
|
|
84
|
+
return `npx lint-staged`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Default commit-msg hook script
|
|
88
|
+
getDefaultCommitMsgScript() {
|
|
89
|
+
return `npx commitlint --edit "$1"`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Uninstall a hook
|
|
93
|
+
uninstallHook(hookName) {
|
|
94
|
+
try {
|
|
95
|
+
const hookPath = path.join(this.huskyDir, hookName);
|
|
96
|
+
|
|
97
|
+
if (fs.existsSync(hookPath)) {
|
|
98
|
+
fs.unlinkSync(hookPath);
|
|
99
|
+
console.log(`${hookName} hook uninstalled successfully`);
|
|
100
|
+
return true;
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`${hookName} hook not found`);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`Failed to uninstall ${hookName} hook:`, error);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// List installed hooks
|
|
112
|
+
listHooks() {
|
|
113
|
+
try {
|
|
114
|
+
if (!fs.existsSync(this.huskyDir)) {
|
|
115
|
+
console.log('No husky hooks directory found');
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const hooks = fs.readdirSync(this.huskyDir)
|
|
120
|
+
.filter(file => file !== '_' && fs.statSync(path.join(this.huskyDir, file)).isFile());
|
|
121
|
+
|
|
122
|
+
console.log('Installed hooks:', hooks);
|
|
123
|
+
return hooks;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Failed to list hooks:', error);
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Update package.json with prepare script
|
|
131
|
+
updatePrepareScript() {
|
|
132
|
+
try {
|
|
133
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
134
|
+
|
|
135
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
136
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
137
|
+
|
|
138
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
139
|
+
|
|
140
|
+
// Only add prepare script if it doesn't exist
|
|
141
|
+
if (!packageJson.scripts.prepare) {
|
|
142
|
+
packageJson.scripts.prepare = 'husky install';
|
|
143
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
144
|
+
console.log('Added prepare script to package.json');
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check if existing prepare script already includes husky install
|
|
149
|
+
if (packageJson.scripts.prepare.includes('husky install')) {
|
|
150
|
+
console.log('Prepare script already includes husky install');
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Append husky install to existing prepare script
|
|
155
|
+
packageJson.scripts.prepare += ' && husky install';
|
|
156
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
157
|
+
console.log('Updated prepare script to include husky install');
|
|
158
|
+
return true;
|
|
159
|
+
} else {
|
|
160
|
+
console.error('package.json not found');
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('Failed to update prepare script:', error);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check if git repository
|
|
170
|
+
isGitRepository() {
|
|
171
|
+
try {
|
|
172
|
+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', cwd: this.projectRoot });
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Get current git branch
|
|
180
|
+
getCurrentBranch() {
|
|
181
|
+
try {
|
|
182
|
+
const branch = execSync('git symbolic-ref --short HEAD', { encoding: 'utf8', cwd: this.projectRoot });
|
|
183
|
+
return branch.trim();
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = GitHooksService;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// Import service modules
|
|
8
|
+
const ESLintService = require('./eslint');
|
|
9
|
+
const CommitLintService = require('./commitlint');
|
|
10
|
+
const GitHooksService = require('./githooks');
|
|
11
|
+
|
|
12
|
+
class LintToolsSDK {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.projectRoot = process.cwd();
|
|
16
|
+
this.sdkRoot = path.resolve(__dirname, '..');
|
|
17
|
+
|
|
18
|
+
// Initialize service instances
|
|
19
|
+
this.eslintService = new ESLintService({ projectRoot: this.projectRoot, ...options.eslint });
|
|
20
|
+
this.commitLintService = new CommitLintService({ projectRoot: this.projectRoot, ...options.commitlint });
|
|
21
|
+
this.gitHooksService = new GitHooksService({ projectRoot: this.projectRoot, ...options.githooks });
|
|
22
|
+
|
|
23
|
+
// Load configuration (project config overrides SDK defaults)
|
|
24
|
+
this.config = this.loadConfig();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Load configuration with project-level overrides
|
|
28
|
+
loadConfig() {
|
|
29
|
+
const sdkConfig = {
|
|
30
|
+
eslint: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
configFile: null,
|
|
33
|
+
fix: true
|
|
34
|
+
},
|
|
35
|
+
commitlint: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
configFile: null
|
|
38
|
+
},
|
|
39
|
+
githooks: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
preCommit: true,
|
|
42
|
+
commitMsg: true
|
|
43
|
+
},
|
|
44
|
+
lintStaged: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
configFile: null
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Load project-level config from package.json
|
|
51
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
52
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
55
|
+
if (packageJson.lintToolsConfig) {
|
|
56
|
+
// Merge project config with SDK defaults
|
|
57
|
+
this.mergeConfig(sdkConfig, packageJson.lintToolsConfig);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn('Failed to load project config from package.json:', error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Load dedicated config file if exists
|
|
65
|
+
const configFilePath = path.join(this.projectRoot, '.linttoolsrc.js');
|
|
66
|
+
if (fs.existsSync(configFilePath)) {
|
|
67
|
+
try {
|
|
68
|
+
const projectConfig = require(configFilePath);
|
|
69
|
+
this.mergeConfig(sdkConfig, projectConfig);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.warn('Failed to load project config from .linttoolsrc.js:', error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Merge with constructor options
|
|
76
|
+
this.mergeConfig(sdkConfig, this.options);
|
|
77
|
+
|
|
78
|
+
return sdkConfig;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Deep merge two configurations
|
|
82
|
+
mergeConfig(target, source) {
|
|
83
|
+
for (const key in source) {
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
85
|
+
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
|
86
|
+
if (!target[key]) {
|
|
87
|
+
target[key] = {};
|
|
88
|
+
}
|
|
89
|
+
this.mergeConfig(target[key], source[key]);
|
|
90
|
+
} else {
|
|
91
|
+
target[key] = source[key];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async init() {
|
|
98
|
+
console.log('Initializing Lint Tools SDK...');
|
|
99
|
+
|
|
100
|
+
// Install Git hooks
|
|
101
|
+
if (this.config.githooks.enabled) {
|
|
102
|
+
await this.installHooks();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Create default configs if enabled
|
|
106
|
+
if (this.config.eslint.enabled || this.config.commitlint.enabled) {
|
|
107
|
+
this.createDefaultConfigs();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Update package scripts
|
|
111
|
+
this.updatePackageScripts();
|
|
112
|
+
|
|
113
|
+
console.log('Lint Tools SDK initialized successfully!');
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async installHooks() {
|
|
118
|
+
try {
|
|
119
|
+
const success = this.gitHooksService.installAllHooks();
|
|
120
|
+
if (success) {
|
|
121
|
+
console.log('All Git hooks installed successfully!');
|
|
122
|
+
return true;
|
|
123
|
+
} else {
|
|
124
|
+
console.error('Failed to install some Git hooks');
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('Failed to install Git hooks:', error);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
createDefaultConfigs() {
|
|
134
|
+
console.log('Creating default configurations (if not exist)...');
|
|
135
|
+
|
|
136
|
+
// Create default eslint config if not exists and enabled
|
|
137
|
+
if (this.config.eslint.enabled) {
|
|
138
|
+
// ESLint v9 uses eslint.config.js format
|
|
139
|
+
const eslintConfigPath = path.join(this.projectRoot, 'eslint.config.js');
|
|
140
|
+
if (!fs.existsSync(eslintConfigPath)) {
|
|
141
|
+
const defaultEslintConfig = `// ESLint v9 configuration
|
|
142
|
+
import globals from "globals";
|
|
143
|
+
import pluginJs from "@eslint/js";
|
|
144
|
+
|
|
145
|
+
export default [
|
|
146
|
+
{
|
|
147
|
+
files: ["**/*.js"],
|
|
148
|
+
languageOptions: {
|
|
149
|
+
ecmaVersion: "latest",
|
|
150
|
+
sourceType: "module",
|
|
151
|
+
globals: {
|
|
152
|
+
...globals.node,
|
|
153
|
+
...globals.browser
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
rules: {
|
|
157
|
+
...pluginJs.configs.recommended.rules,
|
|
158
|
+
// Add your project-specific rules here
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
];`;
|
|
162
|
+
fs.writeFileSync(eslintConfigPath, defaultEslintConfig);
|
|
163
|
+
console.log('Created default eslint.config.js');
|
|
164
|
+
} else {
|
|
165
|
+
console.log('Using existing eslint.config.js');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Create default commitlint config if not exists and enabled
|
|
170
|
+
if (this.config.commitlint.enabled) {
|
|
171
|
+
this.commitLintService.createDefaultConfig();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create default lint-staged config if not exists and enabled
|
|
175
|
+
if (this.config.lintStaged.enabled) {
|
|
176
|
+
const lintStagedConfigPath = path.join(this.projectRoot, '.lintstagedrc.js');
|
|
177
|
+
if (!fs.existsSync(lintStagedConfigPath)) {
|
|
178
|
+
const defaultLintStagedConfig = `module.exports = {
|
|
179
|
+
'*.js': ['eslint --fix', 'git add'],
|
|
180
|
+
'*.jsx': ['eslint --fix', 'git add'],
|
|
181
|
+
'*.ts': ['eslint --fix', 'git add'],
|
|
182
|
+
'*.tsx': ['eslint --fix', 'git add']
|
|
183
|
+
};`;
|
|
184
|
+
fs.writeFileSync(lintStagedConfigPath, defaultLintStagedConfig);
|
|
185
|
+
console.log('Created default .lintstagedrc.js');
|
|
186
|
+
} else {
|
|
187
|
+
console.log('Using existing .lintstagedrc.js');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
updatePackageScripts() {
|
|
193
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
194
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
195
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
196
|
+
|
|
197
|
+
// Add or update scripts
|
|
198
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
199
|
+
|
|
200
|
+
if (this.config.eslint.enabled) {
|
|
201
|
+
packageJson.scripts.lint = packageJson.scripts.lint || 'eslint .';
|
|
202
|
+
packageJson.scripts['lint:fix'] = packageJson.scripts['lint:fix'] || 'eslint . --fix';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (this.config.githooks.enabled) {
|
|
206
|
+
packageJson.scripts['prepare'] = packageJson.scripts['prepare'] || 'husky install';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
210
|
+
console.log('Updated package.json scripts');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async runLint() {
|
|
215
|
+
try {
|
|
216
|
+
if (!this.config.eslint.enabled) {
|
|
217
|
+
console.log('ESLint is disabled in configuration');
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result = await this.eslintService.lintFiles(['.']);
|
|
222
|
+
console.log(result.output);
|
|
223
|
+
return result.success;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('Lint failed!', error);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async runLintFix() {
|
|
231
|
+
try {
|
|
232
|
+
if (!this.config.eslint.enabled) {
|
|
233
|
+
console.log('ESLint is disabled in configuration');
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Temporarily enable fix option
|
|
238
|
+
const originalFixOption = this.eslintService.options.fix;
|
|
239
|
+
this.eslintService.options.fix = true;
|
|
240
|
+
|
|
241
|
+
const result = await this.eslintService.lintFiles(['.']);
|
|
242
|
+
console.log(result.output);
|
|
243
|
+
|
|
244
|
+
// Restore original fix option
|
|
245
|
+
this.eslintService.options.fix = originalFixOption;
|
|
246
|
+
|
|
247
|
+
return result.success;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Lint fix failed!', error);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async runLintStaged() {
|
|
255
|
+
try {
|
|
256
|
+
if (!this.config.eslint.enabled || !this.config.lintStaged.enabled) {
|
|
257
|
+
console.log('ESLint or lint-staged is disabled in configuration');
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const result = await this.eslintService.lintStagedFiles();
|
|
262
|
+
console.log(result.output);
|
|
263
|
+
return result.success;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Lint staged failed!', error);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
runCommitLint(commitMessage) {
|
|
271
|
+
try {
|
|
272
|
+
if (!this.config.commitlint.enabled) {
|
|
273
|
+
console.log('Commitlint is disabled in configuration');
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const result = this.commitLintService.lintCommitMessage(commitMessage);
|
|
278
|
+
if (result.output) {
|
|
279
|
+
console.log(result.output);
|
|
280
|
+
}
|
|
281
|
+
return result.success;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error('Commit message lint failed!', error);
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
runCommitLintFromFile(filePath) {
|
|
289
|
+
try {
|
|
290
|
+
if (!this.config.commitlint.enabled) {
|
|
291
|
+
console.log('Commitlint is disabled in configuration');
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const result = this.commitLintService.lintCommitFromFile(filePath);
|
|
296
|
+
if (result.output) {
|
|
297
|
+
console.log(result.output);
|
|
298
|
+
}
|
|
299
|
+
return result.success;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error('Commit message lint from file failed!', error);
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Get diagnostic information about the current configuration
|
|
307
|
+
getDiagnostics() {
|
|
308
|
+
return {
|
|
309
|
+
projectRoot: this.projectRoot,
|
|
310
|
+
sdkRoot: this.sdkRoot,
|
|
311
|
+
config: this.config,
|
|
312
|
+
services: {
|
|
313
|
+
eslint: {
|
|
314
|
+
configFile: this.eslintService.findESLintConfig(),
|
|
315
|
+
version: this.getESLintVersion()
|
|
316
|
+
},
|
|
317
|
+
commitlint: {
|
|
318
|
+
configFile: this.commitLintService.findCommitlintConfig(),
|
|
319
|
+
version: this.commitLintService.getVersion()
|
|
320
|
+
},
|
|
321
|
+
githooks: {
|
|
322
|
+
hooks: this.gitHooksService.listHooks(),
|
|
323
|
+
huskyDir: this.gitHooksService.huskyDir
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Helper to get ESLint version
|
|
330
|
+
getESLintVersion() {
|
|
331
|
+
try {
|
|
332
|
+
const version = execSync('npx eslint --version', { encoding: 'utf8', cwd: this.projectRoot });
|
|
333
|
+
return version.trim();
|
|
334
|
+
} catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = LintToolsSDK;
|
|
341
|
+
|
|
342
|
+
// CLI entry point
|
|
343
|
+
if (require.main === module) {
|
|
344
|
+
const sdk = new LintToolsSDK();
|
|
345
|
+
const command = process.argv[2];
|
|
346
|
+
const args = process.argv.slice(3);
|
|
347
|
+
|
|
348
|
+
// Wrap async commands in IIFE for proper async handling
|
|
349
|
+
(async () => {
|
|
350
|
+
try {
|
|
351
|
+
switch (command) {
|
|
352
|
+
case 'init':
|
|
353
|
+
await sdk.init();
|
|
354
|
+
break;
|
|
355
|
+
case 'lint': {
|
|
356
|
+
const lintSuccess = await sdk.runLint();
|
|
357
|
+
process.exit(lintSuccess ? 0 : 1);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case 'lint:fix': {
|
|
361
|
+
const fixSuccess = await sdk.runLintFix();
|
|
362
|
+
process.exit(fixSuccess ? 0 : 1);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case 'lint:staged': {
|
|
366
|
+
const stagedSuccess = await sdk.runLintStaged();
|
|
367
|
+
process.exit(stagedSuccess ? 0 : 1);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case 'commitlint': {
|
|
371
|
+
if (args.length === 0) {
|
|
372
|
+
console.error('Error: Commit message is required');
|
|
373
|
+
console.log('Usage: npx lint-tools-sdk commitlint "your commit message"');
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
const commitLintSuccess = sdk.runCommitLint(args.join(' '));
|
|
377
|
+
process.exit(commitLintSuccess ? 0 : 1);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case 'status': {
|
|
381
|
+
const diagnostics = sdk.getDiagnostics();
|
|
382
|
+
console.log(JSON.stringify(diagnostics, null, 2));
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
default:
|
|
386
|
+
console.log('Usage: npx lint-tools-sdk [command]');
|
|
387
|
+
console.log('Commands:');
|
|
388
|
+
console.log(' init Initialize the lint tools in your project');
|
|
389
|
+
console.log(' lint Run eslint on your project');
|
|
390
|
+
console.log(' lint:fix Run eslint with --fix on your project');
|
|
391
|
+
console.log(' lint:staged Run eslint on staged files only');
|
|
392
|
+
console.log(' commitlint Lint a commit message');
|
|
393
|
+
console.log(' status Show diagnostic information');
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.error('Error:', error.message);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
})();
|
|
401
|
+
}
|