covgate 0.2.0 → 0.2.1
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 +15 -13
- package/docs/ai-workflow.md +1 -0
- package/docs/setup.md +30 -28
- package/package.json +1 -1
- package/src/setup/agent.js +3 -1
- package/src/setup/config.js +42 -25
- package/src/setup/index.js +41 -13
- package/src/setup/project.js +82 -7
- package/templates/AGENTS.md.snippet.md +1 -0
package/README.md
CHANGED
|
@@ -46,30 +46,29 @@ covgate run
|
|
|
46
46
|
|
|
47
47
|
## 安装
|
|
48
48
|
|
|
49
|
-
###
|
|
49
|
+
### 从 npm registry 安装(推荐)
|
|
50
|
+
|
|
51
|
+
包已发布到 npm:[covgate](https://www.npmjs.com/package/covgate),当前 latest 为 **0.2.1**。
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
npm install --save-dev
|
|
54
|
+
npm install --save-dev covgate
|
|
53
55
|
npx --no-install covgate setup
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
> npm 上可能还能看到 `0.0.0-stage`,它是暂存发布占位版本,不会作为 `latest` 被安装,可以忽略。
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
npm install --save-dev git+ssh://git@gitee.com/wxzzhj/covgate.git
|
|
60
|
-
```
|
|
60
|
+
### 从 Gitee 安装(源码 / 镜像)
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
```bash
|
|
63
|
+
# HTTPS
|
|
64
|
+
npm install --save-dev git+https://gitee.com/wxzzhj/covgate.git
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
# 或 SSH
|
|
67
|
+
npm install --save-dev git+ssh://git@gitee.com/wxzzhj/covgate.git
|
|
65
68
|
|
|
66
|
-
```bash
|
|
67
|
-
npm install --save-dev covgate
|
|
68
69
|
npx --no-install covgate setup
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
发布需要 npm 登录:`npm login && npm publish`。
|
|
72
|
-
|
|
73
72
|
### 本地目录安装
|
|
74
73
|
|
|
75
74
|
```bash
|
|
@@ -96,8 +95,11 @@ setup 会:
|
|
|
96
95
|
- 检测 npm/pnpm/yarn/bun 和已有测试框架;
|
|
97
96
|
- **没有测试框架时询问是否安装 Vitest + @vitest/coverage-v8**,选择是则自动安装;
|
|
98
97
|
- 写入 `coverage:gate`、`test:coverage` 脚本和 `covgate.config.json` 起始配置;
|
|
98
|
+
- Electron 项目自动生成 `client`(`src/**`,100%)和 `server`(`desktop/**`,95%)两个 scope;
|
|
99
99
|
- 生成 `AGENTS.md`、Agent 门禁脚本、GitHub Actions(如存在 `.github/`)和 `.gitignore` 条目。
|
|
100
100
|
|
|
101
|
+
**CovGate 不生成测试,也不补测试。** 行为变更的测试必须由项目自己补;没有测试时 `test:coverage` 会直接失败,不会生成空覆盖率来蒙混过关。
|
|
102
|
+
|
|
101
103
|
`postinstall` 也会尝试同样的流程,但 **npm 11.19+ 默认不执行依赖安装脚本**。如果安装时没有弹出询问,直接运行 `npx covgate setup` 即可;详细说明见 [安装与自动配置](docs/setup.md)。
|
|
102
104
|
|
|
103
105
|
### 在 CovGate 仓库中开发
|
|
@@ -439,7 +441,7 @@ stdout 是纯 JSON,代理可以读取 `status`、`scopes[].failingFiles`、`is
|
|
|
439
441
|
文本输出示例:
|
|
440
442
|
|
|
441
443
|
```text
|
|
442
|
-
CovGate · 覆盖率门禁 v0.2.
|
|
444
|
+
CovGate · 覆盖率门禁 v0.2.1
|
|
443
445
|
模式 执行测试并检查 · 配置 covgate.config.json · 用时 12.4s
|
|
444
446
|
|
|
445
447
|
✗ 客户端 [client/client] 未通过
|
package/docs/ai-workflow.md
CHANGED
|
@@ -30,6 +30,7 @@ npx --no-install covgate setup
|
|
|
30
30
|
- 任何代码修改完成后,必须运行 `npx --no-install covgate run --json`;退出码 0 才能说完成。
|
|
31
31
|
- 退出码 1:补测试或修实现;2:修配置;3:修测试命令/报告。不要绕过。
|
|
32
32
|
- 禁止降低 covgate.config.json 中的门槛、扩大 exclude、删除豁免检查来让门禁通过;这些改动必须由人类评审。
|
|
33
|
+
- **行为变更必须新增或更新覆盖该行为的真实测试**;门禁失败不能作为完成理由,也不能用 `--passWithNoTests`、空报告或 ignore 绕过。
|
|
33
34
|
- 禁止 istanbul ignore / c8 ignore / v8 ignore / node:coverage ignore。
|
|
34
35
|
- 确实不可达的代码使用 covgate-ignore-next-line / start/end / file,写具体原因、owner 和 expires。
|
|
35
36
|
- 新增或修改的源码必须出现在覆盖率报告中;sources.include 是范围事实来源。
|
package/docs/setup.md
CHANGED
|
@@ -2,36 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## 在线安装
|
|
4
4
|
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# HTTPS(公开仓库)
|
|
9
|
-
npm install --save-dev git+https://gitee.com/wxzzhj/covgate.git
|
|
10
|
-
|
|
11
|
-
# 或 SSH
|
|
12
|
-
npm install --save-dev git+ssh://git@gitee.com/wxzzhj/covgate.git
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
安装完成后运行一次:
|
|
5
|
+
### npm registry(推荐)
|
|
16
6
|
|
|
17
7
|
```bash
|
|
8
|
+
npm install --save-dev covgate
|
|
18
9
|
npx --no-install covgate setup
|
|
19
10
|
```
|
|
20
11
|
|
|
21
|
-
|
|
12
|
+
包页面:[covgate](https://www.npmjs.com/package/covgate)。当前 latest 是 **0.2.1**。
|
|
22
13
|
|
|
23
|
-
|
|
14
|
+
> `0.0.0-stage` 是 npm 暂存发布产生的占位版本,不会被 `npm install covgate` 选中,可以忽略。
|
|
15
|
+
|
|
16
|
+
### Gitee(源码 / 镜像)
|
|
24
17
|
|
|
25
18
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```
|
|
19
|
+
# HTTPS(公开仓库)
|
|
20
|
+
npm install --save-dev git+https://gitee.com/wxzzhj/covgate.git
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
# 或 SSH
|
|
23
|
+
npm install --save-dev git+ssh://git@gitee.com/wxzzhj/covgate.git
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
npm login
|
|
34
|
-
npm publish
|
|
25
|
+
npx --no-install covgate setup
|
|
35
26
|
```
|
|
36
27
|
|
|
37
28
|
### 从本地目录安装
|
|
@@ -47,7 +38,7 @@ npm 11.19+ 默认不执行依赖的 `postinstall`,会显示类似:
|
|
|
47
38
|
|
|
48
39
|
```text
|
|
49
40
|
npm warn install-scripts 1 package has install scripts not yet covered by allowScripts
|
|
50
|
-
npm warn install-scripts covgate@0.2.
|
|
41
|
+
npm warn install-scripts covgate@0.2.1 (postinstall: node bin/covgate-postinstall.js)
|
|
51
42
|
```
|
|
52
43
|
|
|
53
44
|
因此:
|
|
@@ -70,7 +61,7 @@ npm warn install-scripts covgate@0.2.0 (postinstall: node bin/covgate-postinst
|
|
|
70
61
|
3. 添加 `npm scripts`:
|
|
71
62
|
- `coverage:gate`:`covgate run`
|
|
72
63
|
- `test:coverage`:Vitest/Jest 的 JSON + LCOV 覆盖率命令
|
|
73
|
-
4. 生成 `covgate.config.json
|
|
64
|
+
4. 生成 `covgate.config.json`(普通项目单 scope;Electron 自动生成 client + server;已存在则不覆盖):
|
|
74
65
|
|
|
75
66
|
```json
|
|
76
67
|
{
|
|
@@ -93,6 +84,8 @@ npm warn install-scripts covgate@0.2.0 (postinstall: node bin/covgate-postinst
|
|
|
93
84
|
- 若已有 `CLAUDE.md`、`.cursor`、`.github/copilot-instructions.md`,同步写入对应说明。
|
|
94
85
|
6. 输出下一步命令。
|
|
95
86
|
|
|
87
|
+
> **CovGate 不生成测试,也不补测试。** setup 只配置测试框架和门禁;本次行为变更的测试必须由项目自己补。没有测试时 `test:coverage` 会直接失败(已移除 `--passWithNoTests`),不会产生可蒙混过关的空覆盖率。
|
|
88
|
+
|
|
96
89
|
## setup 选项
|
|
97
90
|
|
|
98
91
|
| 选项 | 作用 |
|
|
@@ -122,21 +115,30 @@ npx covgate setup --no-install --no-config
|
|
|
122
115
|
npx covgate setup --profile client --source "web/src/**/*.{ts,tsx}"
|
|
123
116
|
```
|
|
124
117
|
|
|
125
|
-
##
|
|
118
|
+
## 生成的 scope
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
普通项目只生成一个 scope:
|
|
128
121
|
|
|
129
|
-
-
|
|
130
|
-
-
|
|
122
|
+
- 有源码目录时自动选择源码(优先 `src/`,其次 `app.js`、`app/`、`lib/`);
|
|
123
|
+
- 有根目录或 `src/index.html`、React/Vue/Svelte 等依赖、Electron 时选 `client`;
|
|
131
124
|
- 否则选 `server`。
|
|
132
125
|
|
|
133
|
-
|
|
126
|
+
Electron 项目会生成两个 scope:
|
|
127
|
+
|
|
128
|
+
- `client`(profile=client):`src/**` 等渲染进程源码,目标 100%;
|
|
129
|
+
- `server`(profile=server):`desktop/**` 主进程/预加载源码,目标 95%。
|
|
130
|
+
|
|
131
|
+
两个 scope 共用同一个测试命令和覆盖率报告;CovGate 会按各自源码范围计算覆盖率。若使用 `--profile` 或 `--source` 手工覆盖,则只生成一个 scope。
|
|
132
|
+
|
|
133
|
+
生成配置后请先运行:
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
136
|
npx --no-install covgate validate
|
|
137
137
|
npx --no-install covgate run
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
门禁失败时,正确做法是让项目补覆盖该行为的真实测试;CovGate 不会替项目补测试,也不会接受空报告、`--passWithNoTests` 或降低门槛。
|
|
141
|
+
|
|
140
142
|
## AI 代理如何真正做到“自动跑”
|
|
141
143
|
|
|
142
144
|
生成的文件不等于自动触发。需要把它接到代理生命周期:
|
|
@@ -160,4 +162,4 @@ npx --no-install covgate run
|
|
|
160
162
|
- **没有弹出测试框架询问**:npm 11 默认阻止 install scripts。运行 `npx covgate setup`。
|
|
161
163
|
- **没有生成 CovGate 配置**:可能没有识别到源码目录或覆盖率命令。用 `--source`、`--test-command` 指定,或先安装框架。
|
|
162
164
|
- **Vitest 安装超时**:setup 对安装命令有 180 秒限制。检查网络/registry,手动安装 `vitest @vitest/coverage-v8` 后重跑 setup。
|
|
163
|
-
- **多 scope 项目**:setup 只生成一个;手工编辑或分目录 setup。
|
|
165
|
+
- **多 scope 项目**:setup 只生成一个;手工编辑或分目录 setup。
|
package/package.json
CHANGED
package/src/setup/agent.js
CHANGED
|
@@ -14,6 +14,7 @@ const AGENT_BLOCK = [
|
|
|
14
14
|
"- 禁止 `istanbul ignore`、`c8 ignore`、`v8 ignore`、`node:coverage ignore`。",
|
|
15
15
|
"- 确实不可达的代码使用 `covgate-ignore-next-line`、`covgate-ignore-start/end` 或 `covgate-ignore-file`,写具体原因、owner 和 expires。",
|
|
16
16
|
"- 禁止通过扩大 exclude、缩小 sources.include 或删除测试来让门禁通过;这类改动必须由人类评审。",
|
|
17
|
+
"- 行为变更必须新增或更新覆盖该行为的真实测试;不能把“门禁失败”当作完成理由,也不得用空报告、`--passWithNoTests`、ignore 或降低门槛绕过。",
|
|
17
18
|
"- 失败详情在 `.covgate/report.json` 的 scopes/issues/waivers 中;修复后重跑。",
|
|
18
19
|
"",
|
|
19
20
|
"也可运行 `npm run coverage:gate -- --json`。Agent Stop hook 可调用 `scripts/covgate-agent-gate.ps1`(Windows)或 `scripts/covgate-agent-gate.sh`。",
|
|
@@ -30,7 +31,8 @@ const CURSOR_RULE = [
|
|
|
30
31
|
"# CovGate 覆盖率门禁",
|
|
31
32
|
"",
|
|
32
33
|
"任何代码修改后运行 `npx --no-install covgate run --json`,退出码 0 才能结束。",
|
|
33
|
-
"客户端必须 100%,服务端至少 95
|
|
34
|
+
"客户端必须 100%,服务端至少 95%。行为变更必须补真实测试;门禁失败不能作为完成理由。",
|
|
35
|
+
"禁止原生 ignore;豁免必须写原因、owner、expires。",
|
|
34
36
|
""
|
|
35
37
|
].join("\n");
|
|
36
38
|
|
package/src/setup/config.js
CHANGED
|
@@ -44,45 +44,62 @@ async function pickExcludes(root, sourceGlob) {
|
|
|
44
44
|
/**
|
|
45
45
|
* @param {{
|
|
46
46
|
* root: string,
|
|
47
|
-
* profile: "client" | "server",
|
|
48
|
-
*
|
|
47
|
+
* scopes?: Array<{ name: string, profile: "client" | "server", label?: string, sourceGlob: string }>,
|
|
48
|
+
* profile?: "client" | "server",
|
|
49
|
+
* sourceGlob?: string,
|
|
49
50
|
* command: string,
|
|
50
51
|
* coveragePath: string
|
|
51
52
|
* }} options
|
|
52
53
|
*/
|
|
53
54
|
export async function buildStarterConfig({
|
|
54
55
|
root,
|
|
56
|
+
scopes,
|
|
55
57
|
profile,
|
|
56
58
|
sourceGlob,
|
|
57
59
|
command,
|
|
58
60
|
coveragePath
|
|
59
61
|
}) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
+
const specs =
|
|
63
|
+
scopes && scopes.length > 0
|
|
64
|
+
? scopes
|
|
65
|
+
: [
|
|
66
|
+
{
|
|
67
|
+
name: profile,
|
|
68
|
+
profile,
|
|
69
|
+
label: profile === "client" ? "客户端" : "服务端",
|
|
70
|
+
sourceGlob
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
const scopeConfigs = {};
|
|
74
|
+
for (const spec of specs) {
|
|
75
|
+
if (!spec.profile || !spec.sourceGlob || !spec.name) continue;
|
|
76
|
+
const label =
|
|
77
|
+
spec.label || (spec.profile === "client" ? "客户端" : "服务端");
|
|
78
|
+
const exclude = await pickExcludes(root, spec.sourceGlob);
|
|
79
|
+
scopeConfigs[spec.name] = {
|
|
80
|
+
profile: spec.profile,
|
|
81
|
+
label,
|
|
82
|
+
commands: [
|
|
83
|
+
{
|
|
84
|
+
name: label + "测试与覆盖率",
|
|
85
|
+
run: command,
|
|
86
|
+
timeoutSeconds: 900
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
coverage: {
|
|
90
|
+
format: "auto",
|
|
91
|
+
paths: [coveragePath]
|
|
92
|
+
},
|
|
93
|
+
sources: {
|
|
94
|
+
include: [spec.sourceGlob]
|
|
95
|
+
},
|
|
96
|
+
exclude
|
|
97
|
+
};
|
|
98
|
+
}
|
|
62
99
|
return {
|
|
63
100
|
$schema: "./node_modules/covgate/schema/covgate.schema.json",
|
|
64
101
|
version: 1,
|
|
65
|
-
scopes:
|
|
66
|
-
[profile]: {
|
|
67
|
-
profile,
|
|
68
|
-
label,
|
|
69
|
-
commands: [
|
|
70
|
-
{
|
|
71
|
-
name: label + "测试与覆盖率",
|
|
72
|
-
run: command,
|
|
73
|
-
timeoutSeconds: 900
|
|
74
|
-
}
|
|
75
|
-
],
|
|
76
|
-
coverage: {
|
|
77
|
-
format: "auto",
|
|
78
|
-
paths: [coveragePath]
|
|
79
|
-
},
|
|
80
|
-
sources: {
|
|
81
|
-
include: [sourceGlob]
|
|
82
|
-
},
|
|
83
|
-
exclude
|
|
84
|
-
}
|
|
85
|
-
},
|
|
102
|
+
scopes: scopeConfigs,
|
|
86
103
|
waivers: {
|
|
87
104
|
minReasonLength: 8,
|
|
88
105
|
requireOwner: false,
|
package/src/setup/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
detectFramework,
|
|
9
9
|
detectPackageManager,
|
|
10
10
|
detectProfile,
|
|
11
|
+
detectScopeSpecs,
|
|
11
12
|
detectSourceGlob,
|
|
12
13
|
displayPath,
|
|
13
14
|
installVitest,
|
|
@@ -108,23 +109,40 @@ export async function runSetup(options = {}) {
|
|
|
108
109
|
testCommand = "npm run test:coverage";
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
112
|
+
let scopeSpecs;
|
|
113
|
+
if (options.profile || options.source) {
|
|
114
|
+
const profile = options.profile || detectProfile(root, pkg);
|
|
115
|
+
if (!["client", "server"].includes(profile)) {
|
|
116
|
+
throw new SetupError("profile 必须是 client 或 server");
|
|
117
|
+
}
|
|
118
|
+
const sourceGlob = normalizeGlob(
|
|
119
|
+
root,
|
|
120
|
+
options.source || detectSourceGlob(root, profile) || ""
|
|
121
|
+
);
|
|
122
|
+
scopeSpecs = sourceGlob
|
|
123
|
+
? [
|
|
124
|
+
{
|
|
125
|
+
name: profile,
|
|
126
|
+
profile,
|
|
127
|
+
label: profile === "client" ? "客户端" : "服务端",
|
|
128
|
+
sourceGlob
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
: [];
|
|
132
|
+
} else {
|
|
133
|
+
scopeSpecs = detectScopeSpecs(root, pkg);
|
|
114
134
|
}
|
|
115
|
-
const sourceGlob = normalizeGlob(root, options.source || detectSourceGlob(root, profile) || "");
|
|
116
135
|
const coveragePath = normalizeGlob(root, options.coverage || DEFAULT_COVERAGE_PATH);
|
|
117
136
|
const configPath = path.join(root, "covgate.config.json");
|
|
118
137
|
let configWritten = false;
|
|
119
138
|
|
|
120
|
-
if (options.noConfig !== true && testCommand &&
|
|
139
|
+
if (options.noConfig !== true && testCommand && scopeSpecs.length > 0) {
|
|
121
140
|
if (existsSync(configPath) && options.force !== true) {
|
|
122
141
|
log("[covgate] " + displayPath(root, configPath) + " 已存在,保留现有配置");
|
|
123
142
|
} else {
|
|
124
143
|
const config = await buildStarterConfig({
|
|
125
144
|
root,
|
|
126
|
-
|
|
127
|
-
sourceGlob,
|
|
145
|
+
scopes: scopeSpecs,
|
|
128
146
|
command: testCommand,
|
|
129
147
|
coveragePath
|
|
130
148
|
});
|
|
@@ -137,9 +155,9 @@ export async function runSetup(options = {}) {
|
|
|
137
155
|
"[covgate] 生成 " +
|
|
138
156
|
displayPath(root, configPath) +
|
|
139
157
|
"(" +
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
scopeSpecs
|
|
159
|
+
.map((scope) => scope.profile + ":" + scope.sourceGlob)
|
|
160
|
+
.join(",") +
|
|
143
161
|
")"
|
|
144
162
|
);
|
|
145
163
|
if (framework.id && !coverageScriptFor(framework.id) && !options.testCommand) {
|
|
@@ -150,7 +168,7 @@ export async function runSetup(options = {}) {
|
|
|
150
168
|
if (!testCommand) {
|
|
151
169
|
log("[covgate] 未找到可用的覆盖率测试命令,跳过 covgate.config.json。");
|
|
152
170
|
}
|
|
153
|
-
if (
|
|
171
|
+
if (scopeSpecs.length === 0) {
|
|
154
172
|
log("[covgate] 未找到源码目录,跳过 covgate.config.json;请用 --source 指定。");
|
|
155
173
|
}
|
|
156
174
|
}
|
|
@@ -171,8 +189,9 @@ export async function runSetup(options = {}) {
|
|
|
171
189
|
packageManager,
|
|
172
190
|
framework: framework.id || null,
|
|
173
191
|
installedFramework,
|
|
174
|
-
profile,
|
|
175
|
-
sourceGlob: sourceGlob
|
|
192
|
+
profile: scopeSpecs.length === 1 ? scopeSpecs[0].profile : null,
|
|
193
|
+
sourceGlob: scopeSpecs.length === 1 ? scopeSpecs[0].sourceGlob : null,
|
|
194
|
+
scopes: scopeSpecs,
|
|
176
195
|
testCommand: testCommand || null,
|
|
177
196
|
coveragePath,
|
|
178
197
|
configPath: configWritten ? configPath : existsSync(configPath) ? configPath : null,
|
|
@@ -191,7 +210,16 @@ export async function runSetup(options = {}) {
|
|
|
191
210
|
if (summary.framework) {
|
|
192
211
|
log("[covgate] 测试框架: " + (framework.label || summary.framework));
|
|
193
212
|
}
|
|
213
|
+
if (scopeSpecs.length > 0) {
|
|
214
|
+
log(
|
|
215
|
+
"[covgate] Scope: " +
|
|
216
|
+
scopeSpecs
|
|
217
|
+
.map((scope) => scope.name + "/" + scope.profile + " (" + scope.sourceGlob + ")")
|
|
218
|
+
.join(", ")
|
|
219
|
+
);
|
|
220
|
+
}
|
|
194
221
|
log("[covgate] 运行门禁: " + (summary.dryRun ? "npx covgate run" : "npx --no-install covgate run"));
|
|
222
|
+
log("[covgate] 注意:CovGate 不会生成或补写测试;项目必须为行为变更补真实测试后才能通过门禁。");
|
|
195
223
|
if (!options.noAgent) {
|
|
196
224
|
log("[covgate] AI 接入: AGENTS.md 与 scripts/covgate-agent-gate.*");
|
|
197
225
|
}
|
package/src/setup/project.js
CHANGED
|
@@ -156,13 +156,27 @@ export function detectFramework(pkg) {
|
|
|
156
156
|
*/
|
|
157
157
|
export function detectProfile(root, pkg) {
|
|
158
158
|
const deps = directDependencyNames(pkg);
|
|
159
|
-
|
|
159
|
+
const htmlLocations = [
|
|
160
|
+
"index.html",
|
|
161
|
+
"src/index.html",
|
|
162
|
+
"app/index.html",
|
|
163
|
+
"renderer/index.html"
|
|
164
|
+
];
|
|
165
|
+
if (htmlLocations.some((file) => existsSync(path.join(root, file)))) return "client";
|
|
160
166
|
if (pkg?.browser) return "client";
|
|
167
|
+
if (deps.has("electron") || deps.has("electron-builder")) return "client";
|
|
161
168
|
if ([...BROWSER_DEPS].some((dependency) => deps.has(dependency))) return "client";
|
|
162
169
|
if ([...SERVER_DEPS].some((dependency) => deps.has(dependency))) return "server";
|
|
163
170
|
return "server";
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
/**
|
|
174
|
+
* @param {string} directory
|
|
175
|
+
*/
|
|
176
|
+
export function sourceGlobForDir(directory) {
|
|
177
|
+
return directory + "/**/*." + SOURCE_EXT;
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
/**
|
|
167
181
|
* @param {string} root
|
|
168
182
|
* @param {"client" | "server"} profile
|
|
@@ -170,33 +184,94 @@ export function detectProfile(root, pkg) {
|
|
|
170
184
|
*/
|
|
171
185
|
export function detectSourceGlob(root, profile) {
|
|
172
186
|
if (existsSync(path.join(root, "src"))) {
|
|
173
|
-
return "src
|
|
187
|
+
return sourceGlobForDir("src");
|
|
174
188
|
}
|
|
175
189
|
if (profile === "client" && existsSync(path.join(root, "app.js"))) {
|
|
176
190
|
return "app.js";
|
|
177
191
|
}
|
|
178
192
|
const candidate = profile === "client" ? "client" : "server";
|
|
179
193
|
if (existsSync(path.join(root, candidate))) {
|
|
180
|
-
return candidate
|
|
194
|
+
return sourceGlobForDir(candidate);
|
|
181
195
|
}
|
|
182
196
|
if (existsSync(path.join(root, "app"))) {
|
|
183
|
-
return "app
|
|
197
|
+
return sourceGlobForDir("app");
|
|
184
198
|
}
|
|
185
199
|
if (existsSync(path.join(root, "lib"))) {
|
|
186
|
-
return "lib
|
|
200
|
+
return sourceGlobForDir("lib");
|
|
187
201
|
}
|
|
188
202
|
return null;
|
|
189
203
|
}
|
|
190
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Electron apps usually have a renderer tree (src) and a main-process tree
|
|
207
|
+
* (desktop). Treating src as server (the old setup default) was wrong.
|
|
208
|
+
*
|
|
209
|
+
* @param {string} root
|
|
210
|
+
* @param {any} pkg
|
|
211
|
+
*/
|
|
212
|
+
export function detectElectronApp(root, pkg) {
|
|
213
|
+
const deps = directDependencyNames(pkg);
|
|
214
|
+
if (deps.has("electron") || deps.has("electron-builder") || deps.has("@electron/packager")) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
const main = typeof pkg?.main === "string" ? pkg.main.replaceAll("\\", "/") : "";
|
|
218
|
+
return main.startsWith("desktop/") && existsSync(path.join(root, "src"));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} root
|
|
223
|
+
* @param {any} pkg
|
|
224
|
+
* @returns {Array<{ name: string, profile: "client" | "server", label: string, sourceGlob: string }>}
|
|
225
|
+
*/
|
|
226
|
+
export function detectScopeSpecs(root, pkg) {
|
|
227
|
+
if (detectElectronApp(root, pkg)) {
|
|
228
|
+
const clientDir = ["src", "app", "renderer", "client"].find((directory) =>
|
|
229
|
+
existsSync(path.join(root, directory))
|
|
230
|
+
);
|
|
231
|
+
const serverDir = ["desktop", "electron", "main"].find((directory) =>
|
|
232
|
+
existsSync(path.join(root, directory))
|
|
233
|
+
);
|
|
234
|
+
const specs = [];
|
|
235
|
+
if (clientDir) {
|
|
236
|
+
specs.push({
|
|
237
|
+
name: "client",
|
|
238
|
+
profile: "client",
|
|
239
|
+
label: "桌面客户端",
|
|
240
|
+
sourceGlob: sourceGlobForDir(clientDir)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (serverDir) {
|
|
244
|
+
specs.push({
|
|
245
|
+
name: "server",
|
|
246
|
+
profile: "server",
|
|
247
|
+
label: "桌面主进程",
|
|
248
|
+
sourceGlob: sourceGlobForDir(serverDir)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (specs.length > 0) return specs;
|
|
252
|
+
}
|
|
253
|
+
const profile = detectProfile(root, pkg);
|
|
254
|
+
const sourceGlob = detectSourceGlob(root, profile);
|
|
255
|
+
if (!sourceGlob) return [];
|
|
256
|
+
return [
|
|
257
|
+
{
|
|
258
|
+
name: profile,
|
|
259
|
+
profile,
|
|
260
|
+
label: profile === "client" ? "客户端" : "服务端",
|
|
261
|
+
sourceGlob
|
|
262
|
+
}
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
|
|
191
266
|
/**
|
|
192
267
|
* @param {"vitest" | "jest" | string} frameworkId
|
|
193
268
|
*/
|
|
194
269
|
export function coverageScriptFor(frameworkId) {
|
|
195
270
|
if (frameworkId === "vitest") {
|
|
196
|
-
return "vitest run --coverage --coverage.reporter=json --coverage.reporter=lcov
|
|
271
|
+
return "vitest run --coverage --coverage.reporter=json --coverage.reporter=lcov";
|
|
197
272
|
}
|
|
198
273
|
if (frameworkId === "jest") {
|
|
199
|
-
return "jest --coverage --coverageReporters=json --coverageReporters=lcov
|
|
274
|
+
return "jest --coverage --coverageReporters=json --coverageReporters=lcov";
|
|
200
275
|
}
|
|
201
276
|
return null;
|
|
202
277
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
- 任何代码修改完成后,必须运行 `npx --no-install covgate run --json`;**退出码 0 才能结束任务**。
|
|
4
4
|
- 退出码 `1`:补测试或修实现;`2`:修配置;`3`:修测试命令/覆盖率报告。不要绕过。
|
|
5
5
|
- 禁止降低 `covgate.config.json` 中的门槛、扩大 exclude、删除豁免检查来让门禁通过。任何配置改动都必须由人类评审。
|
|
6
|
+
- **行为变更必须新增或更新覆盖该行为的真实测试**;不能把“门禁失败”当作完成理由,也不得用空报告、`--passWithNoTests`、ignore 或降低门槛绕过。
|
|
6
7
|
- 禁止 `istanbul ignore`、`c8 ignore`、`v8 ignore`、`node:coverage ignore`。
|
|
7
8
|
- 确实不可达的代码使用 `covgate-ignore-next-line`、`covgate-ignore-start/end` 或 `covgate-ignore-file`,并写具体原因、owner 和 expires。
|
|
8
9
|
- 新增或修改的源码必须出现在覆盖率报告中;`sources.include` 是范围事实来源。
|