fenge 0.1.0 → 0.1.2
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/CHANGELOG.md +18 -0
- package/README.md +33 -7
- package/package.json +3 -3
- package/src/bin/cli.js +5 -0
- package/src/command/install.js +11 -1
- package/src/command/uninstall.js +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# fenge
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 21a6435: fix(fenge): throw error when installing git hook files if it is already existing
|
|
8
|
+
- c8cf96d: chore: upgrade deps
|
|
9
|
+
- ee3b1e3: feat(fenge): add uninstall command
|
|
10
|
+
- Updated dependencies [e7376a3]
|
|
11
|
+
- Updated dependencies [c8cf96d]
|
|
12
|
+
- Updated dependencies [84e0219]
|
|
13
|
+
- @fenge/eslint-config@0.1.2
|
|
14
|
+
|
|
15
|
+
## 0.1.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- @fenge/eslint-config@0.1.1
|
|
20
|
+
|
|
3
21
|
## 0.1.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -23,18 +23,14 @@
|
|
|
23
23
|
<details>
|
|
24
24
|
<summary>简体中文</summary>
|
|
25
25
|
|
|
26
|
+
### 核心:`类型安全`、`Formatting` 和 `Linting`
|
|
27
|
+
|
|
26
28
|
经过多年实践,我们发现,最影响现代 JavaScript 工程的代码质量和开发体验的主要有 3 个方面:
|
|
27
29
|
|
|
28
30
|
- **类型安全**:用于提前发现类型、拼写错误,例如对象方法是否正确调用、函数参数传递的类型是否符合函数体的期望等。
|
|
29
31
|
- **Formatting**:用于统一格式,提升代码可读性,减少代码冲突。主要关注例如缩进、换行、单/双引号、带/不带分号等问题。
|
|
30
32
|
- **Linting**:用于提前发现逻辑漏洞和糟粕用法,减少 Bug,降低维护成本。其关注点可以是除了 `Formatting` 之外的任何地方,例如重复定义变量、switch 不带 break、圈复杂度等。
|
|
31
33
|
|
|
32
|
-
> Note: `类型安全` 和 `Linting` 的关注点可能存在一定的交集,例如:“函数入参数量对不上”,既可能被类型安全的工具(如 TypeScript)检测到,也可能被 Linter(如 ESLint)检测出来。
|
|
33
|
-
|
|
34
|
-
> Note:`Formatting` 和 `Linting` 的关注点,原则上不存在交集。早期 ESLint 也被用于格式化,但是近年来,`Linter` 和 `Formatter` 分开已经被社区越来越广泛采纳,例如[ESLint 废弃 Formatting Rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules)、Deno 和 Biome 均把 `Linter` 和 `Formatter` 分开。
|
|
35
|
-
>
|
|
36
|
-
> 有些人会将后两者 `Formatting` 和 `Linting` 合并起来一并处理,例如 [@antfu/eslint-config](https://github.com/antfu/eslint-config)。我们强烈**不建议**这样做。首先是因为它们目的不一样,专业的事情应该交给专业的工具。其次是它们的造成心智负担不同,Review 代码时,我们往往不需要关注 Formatting 的改动,但是我们必须要仔细检查确认 Linting 的改动,因为 Formatting 的改动一般是安全的,但是 Linting 的改动可存在错误的修复。
|
|
37
|
-
|
|
38
34
|
这 3 个方面也是更先进的运行时 [Deno](https://deno.com) 所内置的功能,[Node](https://nodejs.org) 并没有内置支持,取而代之的是社区里百花齐放的工具:TypeScript、Flow、Biome、ESLint、oxc-lint、Prettier、dprint。这些工具用在 Node 项目中存在 3 个非常影响**开发体验**的问题:
|
|
39
35
|
|
|
40
36
|
- **工具选型问题**:我应该选择哪些工具集合来优化上述 3 个问题?选择后,下一个 Node 项目又选择不同工具集怎么办?
|
|
@@ -43,6 +39,36 @@
|
|
|
43
39
|
|
|
44
40
|
为了解决上述问题,现在有非常多教程文章讲解 TypeScript + Prettier + ESLint 的配置和实践,这些文章教程能缓解一部分问题,但仍然将<u>杂乱的工具链和繁琐的配置暴露给用户</u>。这不是我们的目标,我们的目标是**提供一个统一的工具屏蔽这些复杂的实践细节,给用户带来简单一致、开箱即用的良好开发体验**。
|
|
45
41
|
|
|
42
|
+
### `类型安全`、`Formatting` 和 `Linting` 的关系
|
|
43
|
+
|
|
44
|
+
为了阐述三者之间的关系,这里以三者最具代表性的解决方案 `TypeScript`、`Prettier` 和 `ESLint` 作为例子。
|
|
45
|
+
|
|
46
|
+
| - | 类型安全 | Formatting | Linting |
|
|
47
|
+
| ----------- | ---------- | ---------- | ------- |
|
|
48
|
+
| 代表 | TypeScript | Prettier | ESLint |
|
|
49
|
+
| 关注逻辑 | ✅ | ❌ | ✅ |
|
|
50
|
+
| Auto Fixing | ❌ | ✅ | ✅ |
|
|
51
|
+
|
|
52
|
+
经过多年演进,三者关注点存在一定的交集:
|
|
53
|
+
|
|
54
|
+
1. `类型安全`和 `Linting` 关注点的交集:例如,“函数入参数量对不上”,既可能被 TypeScript 检测到,也可能被 ESLint检测出来。
|
|
55
|
+
2. `Formatting` 和 `Linting` 关注点的交集:例如,“是否使用分号结尾”、“使用单引号还是双引号”等,既可以被 Prettier 检测出来并执行格式化,也可以被 ESLint 检测出来并执行修复。
|
|
56
|
+
|
|
57
|
+
虽然当下情况是三者存在一定的交集,但这不是最理想的情况,最理想的情况是:**类型安全、Formatting 和 Linting 关注不同的领域,不存在交集**。
|
|
58
|
+
|
|
59
|
+
### 为什么把 Formatting 和 Linting 分开
|
|
60
|
+
|
|
61
|
+
虽然类型安全也可能和 Linting 的关注点重合,但是社区主流做法也不会将 TypeScript 和 ESLint 混为一谈,所以这里不过多赘述。然而,社区内不少人将 `Formatting` 和 `Linting` 合并起来一并处理,例如 [@antfu/eslint-config](https://github.com/antfu/eslint-config)。我们强烈**不建议**这样做,主要有以下原因:
|
|
62
|
+
|
|
63
|
+
1. 首先是因为它们目的不一样,专业的事情应该交给专业的工具。
|
|
64
|
+
2. Formatting 和 Linting 它们造成的心智负担不同,Review 代码时,我们往往不需要关注 Formatting 的改动,但是我们必须要仔细检查确认 Linting 的改动,因为 Formatting 的改动一般是安全的,但是 Linting 的改动可能存在错误的修复。
|
|
65
|
+
3. 因为 Linting 的改动可能存在错误的修复,配合 Git Hooks 时,如果 Linting 的修复和 Formatting 的修复混合在一起,代码提交时容易让错误的修复直接进入 Git Commit,导致 bug 更难被发现。
|
|
66
|
+
4. 社区主流趋势是将 Formatter 和 Linter 分开。例如:早期 ESLint 也被用于格式化,但是从 v8.53.0 开始, [ESLint 废弃 Formatting Rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules)。Deno 和 Biome 也均把 `Linter` 和 `Formatter` 分开。
|
|
67
|
+
|
|
68
|
+
### 小结
|
|
69
|
+
|
|
70
|
+
总而言之,类型安全、Formatting 和 Linting 是未来 JavaScript 和 TypeScript 代码生态绕不过去的三个方面。这三板斧在 Node 官方提供解决方案之前,都是割裂、难用、影响代码质量的方面。我们等不急 Node 官方提供相关方案,所以创建了 `Fenge`,尽可能屏蔽复杂,暴露简单,让开发者专注于业务代码。
|
|
71
|
+
|
|
46
72
|
</details>
|
|
47
73
|
|
|
48
74
|
<details>
|
|
@@ -138,7 +164,7 @@ Other File (eg: `src/other-file.ts`)
|
|
|
138
164
|
|
|
139
165
|
<!-- prettier-ignore-start -->
|
|
140
166
|
```ts
|
|
141
|
-
console.log(JSON.parse('{"foo":"foo"}').bar);
|
|
167
|
+
console.log(JSON.parse('{"foo":"foo"}').bar.length);
|
|
142
168
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ❌ Object is of type 'unknown'.
|
|
143
169
|
```
|
|
144
170
|
<!-- prettier-ignore-end -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fenge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A CLI tool for code quality",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"eslint": "8.57.0",
|
|
47
47
|
"lilconfig": "3.1.2",
|
|
48
48
|
"lint-staged": "15.2.10",
|
|
49
|
-
"ora": "8.1.
|
|
49
|
+
"ora": "8.1.1",
|
|
50
50
|
"prettier": "3.3.3",
|
|
51
|
-
"@fenge/eslint-config": "0.1.
|
|
51
|
+
"@fenge/eslint-config": "0.1.2",
|
|
52
52
|
"@fenge/prettier-config": "0.1.0",
|
|
53
53
|
"@fenge/tsconfig": "0.1.0",
|
|
54
54
|
"@fenge/types": "0.1.0",
|
package/src/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
import { format } from "../command/format.js";
|
|
7
7
|
import { install } from "../command/install.js";
|
|
8
8
|
import { lint } from "../command/lint.js";
|
|
9
|
+
import { uninstall } from "../command/uninstall.js";
|
|
9
10
|
import { importJson } from "../utils.js";
|
|
10
11
|
|
|
11
12
|
const pkgJson = await importJson(import.meta.url, "../../package.json");
|
|
@@ -70,6 +71,10 @@ program
|
|
|
70
71
|
.option("--no-format", "skip formatting code on git 'pre-commit' stage")
|
|
71
72
|
.option("--no-lint", "skip linting code on git 'pre-commit' stage")
|
|
72
73
|
.action(async (options) => await install(options));
|
|
74
|
+
program
|
|
75
|
+
.command("uninstall")
|
|
76
|
+
.description("remove `pre-commit` hook file from `.git/hooks` folder")
|
|
77
|
+
.action(async () => await uninstall());
|
|
73
78
|
|
|
74
79
|
setup(program, {
|
|
75
80
|
initCommand: "init-tsconfig",
|
package/src/command/install.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
-
import { dir, exists, getBinPath } from "../utils.js";
|
|
5
|
+
import { dir, exists, getBinPath, importJson } from "../utils.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {string} file
|
|
@@ -20,6 +20,16 @@ async function writeGitHook(file, content) {
|
|
|
20
20
|
await fs.mkdir(hooksPath, { recursive: true });
|
|
21
21
|
|
|
22
22
|
const hookFilePath = path.resolve(hooksPath, file);
|
|
23
|
+
if (await exists(hookFilePath)) {
|
|
24
|
+
const pkgJsonName = (
|
|
25
|
+
await importJson(import.meta.url, "../../package.json")
|
|
26
|
+
).name; // fenge
|
|
27
|
+
if (!(await fs.readFile(hookFilePath, "utf8")).includes(pkgJsonName)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Cannot install git hook file since ${hookFilePath} is already existing. Please remove it first.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
23
33
|
await fs.writeFile(hookFilePath, content);
|
|
24
34
|
await fs.chmod(hookFilePath, "777");
|
|
25
35
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { exists, importJson } from "../utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} file
|
|
9
|
+
*/
|
|
10
|
+
async function removeGitHook(file) {
|
|
11
|
+
const pkgJsonName = (await importJson(import.meta.url, "../../package.json"))
|
|
12
|
+
.name; // fenge
|
|
13
|
+
const hookFilePath = path.resolve(process.cwd(), ".git", "hooks", file);
|
|
14
|
+
if (
|
|
15
|
+
(await exists(hookFilePath)) &&
|
|
16
|
+
(await fs.readFile(hookFilePath, "utf8")).includes(pkgJsonName)
|
|
17
|
+
) {
|
|
18
|
+
await fs.rm(hookFilePath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function uninstall() {
|
|
23
|
+
await removeGitHook("pre-commit");
|
|
24
|
+
}
|