covgate 0.3.2 → 0.3.3

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 CHANGED
@@ -29,6 +29,7 @@ npx --no-install covgate ui --root D:/Projects/my-app --port 4319 --no-open
29
29
  - 指定客户端、服务端源码路径,路径有自动检测的建议列表;
30
30
  - 从项目 `package.json` 脚本中**下拉选择测试命令**,不需要手敲;
31
31
  - 单独指定客户端/服务端命令,自动生成临时配置并运行;
32
+ - 客户端、服务端可以分别设置覆盖率报告路径,适配前后端分开产出的项目;
32
33
  - 工具内嵌覆盖率报告:左侧文件列表,中间源码,绿色已覆盖、白色未覆盖;
33
34
  - 运行前检查测试文件;没有测试时禁用运行,并提供可复制的 **AI 补测任务**,避免生成空报告;
34
35
  - 测试命令失败或没有生成报告时,直接显示日志和原因,不展示空结果页;
@@ -80,7 +81,7 @@ covgate run
80
81
 
81
82
  ### 从 npm registry 安装(推荐)
82
83
 
83
- 包已发布到 npm:[covgate](https://www.npmjs.com/package/covgate),当前 latest 为 **0.3.2**。
84
+ 包已发布到 npm:[covgate](https://www.npmjs.com/package/covgate),当前 latest 为 **0.3.3**。
84
85
 
85
86
  ```bash
86
87
  npm install --save-dev covgate
@@ -511,7 +512,7 @@ macOS 用 `open .covgate/report.html`,Linux 用 `xdg-open .covgate/report.html
511
512
  文本输出示例:
512
513
 
513
514
  ```text
514
- CovGate · 覆盖率门禁 v0.3.2
515
+ CovGate · 覆盖率门禁 v0.3.3
515
516
  模式 执行测试并检查 · 配置 covgate.config.json · 用时 12.4s
516
517
 
517
518
  ✗ 客户端 [client/client] 未通过
package/docs/setup.md CHANGED
@@ -9,7 +9,7 @@ npm install --save-dev covgate
9
9
  npx --no-install covgate setup
10
10
  ```
11
11
 
12
- 包页面:[covgate](https://www.npmjs.com/package/covgate)。当前 latest 是 **0.3.2**。
12
+ 包页面:[covgate](https://www.npmjs.com/package/covgate)。当前 latest 是 **0.3.3**。
13
13
 
14
14
  > `0.0.0-stage` 是 npm 暂存发布产生的占位版本,不会被 `npm install covgate` 选中,可以忽略。
15
15
 
@@ -38,7 +38,7 @@ npm 11.19+ 默认不执行依赖的 `postinstall`,会显示类似:
38
38
 
39
39
  ```text
40
40
  npm warn install-scripts 1 package has install scripts not yet covered by allowScripts
41
- npm warn install-scripts covgate@0.3.2 (postinstall: node bin/covgate-postinstall.js)
41
+ npm warn install-scripts covgate@0.3.3 (postinstall: node bin/covgate-postinstall.js)
42
42
  ```
43
43
 
44
44
  因此:
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "covgate",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "覆盖率门禁:客户端 100%,服务端至少 95%,安装时自动检测/配置测试框架与 AI 编码代理接入。",
5
5
  "type": "module",
6
6
  "bin": {
package/src/ui/page.html CHANGED
@@ -201,7 +201,8 @@
201
201
  <div id="healthCard" class="health" hidden>
202
202
  <div class="health-row"><span>测试框架</span><b id="healthFramework">—</b></div>
203
203
  <div class="health-row"><span>测试文件</span><b id="healthTests">—</b></div>
204
- <div class="health-row"><span>覆盖率报告</span><b id="healthCoverage">—</b></div>
204
+ <div class="health-row"><span>客户端报告</span><b id="healthClientCoverage">—</b></div>
205
+ <div class="health-row"><span>服务端报告</span><b id="healthServerCoverage">—</b></div>
205
206
  </div>
206
207
  <div id="healthWarn" class="health-warn" hidden>
207
208
  没有检测到测试文件。CovGate 不会生成测试,请先让项目/AI 补真实测试。
@@ -243,8 +244,12 @@
243
244
  <span class="hint">自动检测</span>
244
245
  </div>
245
246
  <div class="field">
246
- <label for="coveragePath">报告文件</label>
247
- <input id="coveragePath" list="coverageOptions" spellcheck="false" placeholder="coverage/coverage-final.json">
247
+ <label for="clientCoveragePath">客户端报告</label>
248
+ <input id="clientCoveragePath" list="coverageOptions" spellcheck="false" placeholder="coverage/client/coverage-final.json">
249
+ </div>
250
+ <div class="field">
251
+ <label for="serverCoveragePath">服务端报告</label>
252
+ <input id="serverCoveragePath" list="coverageOptions" spellcheck="false" placeholder="coverage/server/coverage-final.json">
248
253
  </div>
249
254
  <div id="configChip" class="config-chip temp">使用临时配置</div>
250
255
  </section>
@@ -322,7 +327,8 @@
322
327
  var server = $("serverPath").value.trim() || "(未指定)";
323
328
  var clientCommand = $("clientCommand").value || "(未选择)";
324
329
  var serverCommand = $("serverCommand").value || "(未选择)";
325
- var coverage = $("coveragePath").value.trim() || "(未指定)";
330
+ var clientCoverage = $("clientCoveragePath").value.trim() || "(未指定)";
331
+ var serverCoverage = $("serverCoveragePath").value.trim() || "(未指定)";
326
332
  var framework = state && state.frameworkLabel ? state.frameworkLabel : "项目现有测试框架";
327
333
  return [
328
334
  "你正在为以下项目补真实测试,目标是让 CovGate 真正通过。",
@@ -333,7 +339,8 @@
333
339
  "服务端源码:" + server + "(目标 95%)",
334
340
  "客户端测试命令:" + clientCommand,
335
341
  "服务端测试命令:" + serverCommand,
336
- "覆盖率报告:" + coverage,
342
+ "客户端覆盖率报告:" + clientCoverage,
343
+ "服务端覆盖率报告:" + serverCoverage,
337
344
  "",
338
345
  "当前测试文件数量:" + ((state && state.testCount) || 0) + "。请先建立/完善测试基础设施,再补真实测试。",
339
346
  "",
@@ -359,6 +366,14 @@
359
366
  area.remove();
360
367
  return Promise.resolve();
361
368
  }
369
+ function syncCoverageHealth() {
370
+ var client = $("clientCoveragePath").value.trim() || "未指定";
371
+ var server = $("serverCoveragePath").value.trim() || "未指定";
372
+ $("healthClientCoverage").textContent = client;
373
+ $("healthClientCoverage").title = client;
374
+ $("healthServerCoverage").textContent = server;
375
+ $("healthServerCoverage").title = server;
376
+ }
362
377
  function updateHealth(data) {
363
378
  var hasTests = (data.testCount || 0) > 0;
364
379
  $("healthCard").hidden = false;
@@ -366,7 +381,7 @@
366
381
  $("healthFramework").className = data.framework ? "good" : "bad";
367
382
  $("healthTests").textContent = hasTests ? data.testCount + " 个" : "0 个 · 需要补测试";
368
383
  $("healthTests").className = hasTests ? "good" : "bad";
369
- $("healthCoverage").textContent = data.coveragePath || "未指定";
384
+ syncCoverageHealth();
370
385
  $("healthWarn").hidden = hasTests;
371
386
  $("emptyMark").textContent = hasTests ? "⌁" : "!";
372
387
  $("emptyTitle").textContent = hasTests ? "把覆盖率跑起来" : "项目还没有测试";
@@ -374,6 +389,7 @@
374
389
  ? "在左侧选择源码路径和测试命令,点击“运行覆盖率”。<br>完成后会在这里显示文件列表和逐行覆盖情况。"
375
390
  : "CovGate 不会生成测试。先让项目或编码代理补真实测试,再运行覆盖率。<br>可以复制下面的补测任务作为 AI 的输入。";
376
391
  $("emptyActions").hidden = hasTests;
392
+ syncCoverageHealth();
377
393
  setRunning(false);
378
394
  }
379
395
  function showView(name) {
@@ -422,12 +438,15 @@
422
438
  $("root").value = data.root;
423
439
  $("clientPath").value = data.clientPath || "";
424
440
  $("serverPath").value = data.serverPath || "";
425
- $("coveragePath").value = data.coveragePath || "coverage/coverage-final.json";
441
+ $("clientCoveragePath").value = data.clientCoveragePath || data.coveragePath || "coverage/coverage-final.json";
442
+ $("serverCoveragePath").value = data.serverCoveragePath || data.coveragePath || "coverage/coverage-final.json";
426
443
  fillCommands($("clientCommand"), data.commands || [], data.clientCommand);
427
444
  fillCommands($("serverCommand"), data.commands || [], data.serverCommand);
428
445
  fillDatalist("sourceDirs", data.sourceDirs || []);
429
446
  var coverageOptions = (data.coverageFiles || []).slice();
430
- if (data.coveragePath && coverageOptions.indexOf(data.coveragePath) === -1) coverageOptions.unshift(data.coveragePath);
447
+ [data.clientCoveragePath, data.serverCoveragePath, data.coveragePath].forEach(function (value) {
448
+ if (value && coverageOptions.indexOf(value) === -1) coverageOptions.unshift(value);
449
+ });
431
450
  fillDatalist("coverageOptions", coverageOptions);
432
451
  var chip = $("configChip");
433
452
  chip.textContent = data.existingConfig ? "已加载现有 covgate.config.json" : "使用临时配置";
@@ -578,7 +597,8 @@
578
597
  serverPath: serverPath,
579
598
  clientCommand: clientCommand,
580
599
  serverCommand: serverCommand,
581
- coveragePath: $("coveragePath").value.trim(),
600
+ clientCoveragePath: $("clientCoveragePath").value.trim(),
601
+ serverCoveragePath: $("serverCoveragePath").value.trim(),
582
602
  allowNoTests: allowNoTests
583
603
  })
584
604
  });
@@ -598,6 +618,8 @@
598
618
  $("detectBtn").addEventListener("click", detect);
599
619
  $("root").addEventListener("keydown", function (event) { if (event.key === "Enter") detect(); });
600
620
  $("runBtn").addEventListener("click", run);
621
+ $("clientCoveragePath").addEventListener("input", syncCoverageHealth);
622
+ $("serverCoveragePath").addEventListener("input", syncCoverageHealth);
601
623
  $("copyPromptBtn").addEventListener("click", function () {
602
624
  copyText(buildPrompt()).then(function () {
603
625
  toast("补测任务已复制");
package/src/ui/server.js CHANGED
@@ -178,8 +178,17 @@ async function inspectProject(root) {
178
178
  for (const candidate of COVERAGE_CANDIDATES) {
179
179
  if (existsSync(path.join(projectRoot, candidate))) coverageFiles.push(candidate);
180
180
  }
181
- const configuredCoverage = scopes.client?.coverage?.paths?.[0] || scopes.server?.coverage?.paths?.[0];
181
+ const configuredClientCoverage = scopes.client?.coverage?.paths?.[0];
182
+ const configuredServerCoverage = scopes.server?.coverage?.paths?.[0];
183
+ const configuredCoverage = configuredClientCoverage || configuredServerCoverage;
182
184
  const coveragePath = configuredCoverage || coverageFiles[0] || COVERAGE_CANDIDATES[0];
185
+ const clientCoveragePath = configuredClientCoverage || coveragePath;
186
+ const serverCoveragePath = configuredServerCoverage || coveragePath;
187
+ const coverageSuggestions = [...new Set([
188
+ clientCoveragePath,
189
+ serverCoveragePath,
190
+ ...coverageFiles
191
+ ].filter(Boolean))];
183
192
 
184
193
  const pickExisting = (names) => names.find((name) => existsSync(path.join(projectRoot, name))) || "";
185
194
  const clientPath =
@@ -200,8 +209,10 @@ async function inspectProject(root) {
200
209
  frameworkLabel: framework.label,
201
210
  commands,
202
211
  sourceDirs,
203
- coverageFiles,
212
+ coverageFiles: coverageSuggestions,
204
213
  coveragePath,
214
+ clientCoveragePath,
215
+ serverCoveragePath,
205
216
  clientPath,
206
217
  serverPath,
207
218
  clientCommand,
@@ -216,10 +227,21 @@ async function inspectProject(root) {
216
227
  async function buildConfig(state, input) {
217
228
  const clientPath = relativeInput(state.root, input.clientPath);
218
229
  const serverPath = relativeInput(state.root, input.serverPath);
219
- const coveragePath = relativeInput(state.root, input.coveragePath || state.coveragePath);
230
+ const fallbackCoveragePath = relativeInput(
231
+ state.root,
232
+ input.coveragePath || state.coveragePath
233
+ );
234
+ const clientCoveragePath = relativeInput(
235
+ state.root,
236
+ input.clientCoveragePath || state.clientCoveragePath || fallbackCoveragePath
237
+ );
238
+ const serverCoveragePath = relativeInput(
239
+ state.root,
240
+ input.serverCoveragePath || state.serverCoveragePath || fallbackCoveragePath
241
+ );
220
242
  const existing = state.existing || {};
221
243
  const scopes = {};
222
- const makeScope = async (name, profile, label, sourcePath, command) => {
244
+ const makeScope = async (name, profile, label, sourcePath, command, coveragePath) => {
223
245
  if (!sourcePath) return;
224
246
  const old = existing.scopes?.[name] || {};
225
247
  const oldExclude = Array.isArray(old.exclude) ? old.exclude : [];
@@ -247,8 +269,8 @@ async function buildConfig(state, input) {
247
269
  exclude
248
270
  };
249
271
  };
250
- await makeScope("client", "client", "客户端", clientPath, input.clientCommand || state.clientCommand);
251
- await makeScope("server", "server", "服务端", serverPath, input.serverCommand || state.serverCommand);
272
+ await makeScope("client", "client", "客户端", clientPath, input.clientCommand || state.clientCommand, clientCoveragePath);
273
+ await makeScope("server", "server", "服务端", serverPath, input.serverCommand || state.serverCommand, serverCoveragePath);
252
274
  if (Object.keys(scopes).length === 0) {
253
275
  throw new HttpError(400, "至少需要指定客户端或服务端源码路径。");
254
276
  }
@@ -379,7 +401,10 @@ export async function startUiServer(options = {}) {
379
401
  serverPath: body.serverPath || "",
380
402
  clientCommand: body.clientCommand || state.clientCommand || "",
381
403
  serverCommand: body.serverCommand || state.serverCommand || "",
382
- coveragePath: body.coveragePath || state.coveragePath || ""
404
+ clientCoveragePath:
405
+ body.clientCoveragePath || state.clientCoveragePath || body.coveragePath || "",
406
+ serverCoveragePath:
407
+ body.serverCoveragePath || state.serverCoveragePath || body.coveragePath || ""
383
408
  }
384
409
  };
385
410
  jobs.set(id, job);