@mcptoolshop/venvkit 0.2.1 → 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.es.md +274 -0
- package/README.fr.md +274 -0
- package/README.hi.md +274 -0
- package/README.it.md +274 -0
- package/README.ja.md +274 -0
- package/README.md +27 -2
- package/README.pt-BR.md +274 -0
- package/README.zh.md +274 -0
- package/dist/integration.test.d.ts +2 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +245 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/mapRender.test.js +101 -0
- package/dist/mapRender.test.js.map +1 -1
- package/dist/map_cli.test.d.ts +2 -0
- package/dist/map_cli.test.d.ts.map +1 -0
- package/dist/map_cli.test.js +320 -0
- package/dist/map_cli.test.js.map +1 -0
- package/dist/runLog.test.js +167 -0
- package/dist/runLog.test.js.map +1 -1
- package/dist/scanEnvPaths.d.ts.map +1 -1
- package/dist/scanEnvPaths.js +7 -4
- package/dist/scanEnvPaths.js.map +1 -1
- package/dist/scanEnvPaths.test.d.ts +2 -0
- package/dist/scanEnvPaths.test.d.ts.map +1 -0
- package/dist/scanEnvPaths.test.js +250 -0
- package/dist/scanEnvPaths.test.js.map +1 -0
- package/dist/taskCluster.test.js +84 -0
- package/dist/taskCluster.test.js.map +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +18 -0
- package/dist/vitest.config.js.map +1 -1
- package/dist/windows.test.d.ts +6 -0
- package/dist/windows.test.d.ts.map +1 -0
- package/dist/windows.test.js +121 -0
- package/dist/windows.test.js.map +1 -0
- package/package.json +11 -6
package/README.zh.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="README.ja.md">日本語</a> | <a href="README.md">English</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/venvkit/readme.png" alt="venvkit" width="400">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
# venvkit
|
|
10
|
+
|
|
11
|
+
> [MCP Tool Shop](https://mcptoolshop.com) 的一部分
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://github.com/mcp-tool-shop-org/venvkit/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/venvkit/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
15
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT"></a>
|
|
16
|
+
<a href="https://mcp-tool-shop-org.github.io/venvkit/"><img src="https://img.shields.io/badge/Landing_Page-live-blue?style=flat-square" alt="Landing Page"></a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@mcptoolshop/venvkit"><img src="https://img.shields.io/npm/v/@mcptoolshop/venvkit?style=flat-square&color=cb3837" alt="npm version"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
**适用于 Windows 机器学习工作流程的 Python 虚拟环境诊断工具包。**
|
|
21
|
+
|
|
22
|
+
该工具会扫描您的系统中的 Python 环境,诊断健康问题(SSL、DLL、ABI 不匹配、路径泄露),跟踪任务执行历史记录,检测不可靠的任务,并生成一个生态系统地图。
|
|
23
|
+
|
|
24
|
+
## 30 秒快速入门
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/mcp-tool-shop-org/venvkit && cd venvkit
|
|
28
|
+
npm install && npm run build
|
|
29
|
+
node dist/map_cli.js --root C:\projects --httpsProbe
|
|
30
|
+
# Open .venvkit/venv-map.html in your browser
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 功能
|
|
34
|
+
|
|
35
|
+
- **doctorLite** - 快速检查任何 Python 解释器的健康状况
|
|
36
|
+
- SSL/TLS 验证
|
|
37
|
+
- DLL 加载失败(常见于 PyTorch/CUDA)
|
|
38
|
+
- ABI 不匹配(ARM 与 x86)
|
|
39
|
+
- pip 检查
|
|
40
|
+
- 检测用户站点和 PYTHONPATH 泄露
|
|
41
|
+
|
|
42
|
+
- **scanEnvPaths** - 发现系统中的所有 Python 环境
|
|
43
|
+
- 查找 venvs、conda 环境、pyenv 版本、基本解释器
|
|
44
|
+
- 可配置的深度和过滤
|
|
45
|
+
|
|
46
|
+
- **mapRender** - 可视化您的 Python 生态系统
|
|
47
|
+
- 图形 JSON 输出,用于程序化使用
|
|
48
|
+
- Mermaid 图表,用于文档
|
|
49
|
+
- 基本解释器分组,并进行影响范围分析
|
|
50
|
+
- 任务路由可视化
|
|
51
|
+
|
|
52
|
+
- **runLog** - 跟踪任务执行历史记录
|
|
53
|
+
- 仅追加的 JSONL 格式
|
|
54
|
+
- 记录哪个环境运行了哪个任务
|
|
55
|
+
- 记录成功/失败,并进行错误分类
|
|
56
|
+
|
|
57
|
+
- **taskCluster** - 按签名聚合任务运行
|
|
58
|
+
- 检测不可靠的任务(一致性不通过)
|
|
59
|
+
- 检测与环境相关的不可靠性
|
|
60
|
+
- 识别失败热点
|
|
61
|
+
- 传染分析(共享的根本原因)
|
|
62
|
+
|
|
63
|
+
## 安装
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install
|
|
67
|
+
npm run build
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 命令行用法
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Scan current directory and generate ecosystem map
|
|
74
|
+
node dist/map_cli.js
|
|
75
|
+
|
|
76
|
+
# Scan specific directories
|
|
77
|
+
node dist/map_cli.js --root C:\projects --root D:\ml-experiments
|
|
78
|
+
|
|
79
|
+
# Include task run history
|
|
80
|
+
node dist/map_cli.js --runlog .venvkit/runs.jsonl
|
|
81
|
+
|
|
82
|
+
# Output options
|
|
83
|
+
node dist/map_cli.js --out ./output --minScore 50 --strict --httpsProbe
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 命令行选项
|
|
87
|
+
|
|
88
|
+
| 标志 | 描述 |
|
|
89
|
+
|------|-------------|
|
|
90
|
+
| `--root, -r` | 要扫描的目录(可以指定多个) |
|
|
91
|
+
| `--out` | 输出目录(默认为:`.venvkit`) |
|
|
92
|
+
| `--maxDepth` | 要扫描的最大目录深度(默认为:5) |
|
|
93
|
+
| `--strict` | 启用严格模式检查 |
|
|
94
|
+
| `--httpsProbe` | 测试 HTTPS 连接 |
|
|
95
|
+
| `--minScore` | 过滤健康评分低于此值的环境 |
|
|
96
|
+
| `--concurrency` | 并行检查(默认为:CPU 数量) |
|
|
97
|
+
| `--runlog` | 任务运行日志的路径(JSONL) |
|
|
98
|
+
| `--no-tasks` | 跳过任务可视化 |
|
|
99
|
+
|
|
100
|
+
### 输出
|
|
101
|
+
|
|
102
|
+
| 文件 | 描述 |
|
|
103
|
+
|------|-------------|
|
|
104
|
+
| `venv-map.json` | 完整的图形数据(节点、边、摘要) |
|
|
105
|
+
| `venv-map.mmd` | Mermaid 图表源代码 |
|
|
106
|
+
| `venv-map.html` | 交互式查看器 |
|
|
107
|
+
| `reports.json` | 原始的 doctorLite 报告 |
|
|
108
|
+
| `insights.json` | 可操作的建议 |
|
|
109
|
+
|
|
110
|
+
## 程序化用法
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { doctorLite, scanEnvPaths, mapRender, readRunLog } from 'venvkit';
|
|
114
|
+
|
|
115
|
+
// Check a specific Python
|
|
116
|
+
const report = await doctorLite({
|
|
117
|
+
pythonPath: 'C:\\project\\.venv\\Scripts\\python.exe',
|
|
118
|
+
requiredModules: ['torch', 'transformers'],
|
|
119
|
+
httpsProbe: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
console.log(report.status); // 'good' | 'warn' | 'bad'
|
|
123
|
+
console.log(report.score); // 0-100
|
|
124
|
+
console.log(report.findings); // Array of issues
|
|
125
|
+
|
|
126
|
+
// Scan for all Python environments
|
|
127
|
+
const scan = await scanEnvPaths({
|
|
128
|
+
roots: ['C:\\projects'],
|
|
129
|
+
maxDepth: 5,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Run doctorLite on all found environments
|
|
133
|
+
const reports = await Promise.all(
|
|
134
|
+
scan.pythonPaths.map(p => doctorLite({ pythonPath: p }))
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Load task execution history
|
|
138
|
+
const runs = await readRunLog('.venvkit/runs.jsonl');
|
|
139
|
+
|
|
140
|
+
// Generate ecosystem visualization
|
|
141
|
+
const { graph, mermaid, insights } = mapRender(reports, runs, {
|
|
142
|
+
taskMode: 'clustered', // 'none' | 'runs' | 'clustered'
|
|
143
|
+
includeHotEdgeLabels: true,
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 运行日志模式
|
|
148
|
+
|
|
149
|
+
通过将事件追加到 JSONL 文件来跟踪任务执行:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { appendRunLog, newRunId } from 'venvkit';
|
|
153
|
+
|
|
154
|
+
await appendRunLog('.venvkit/runs.jsonl', {
|
|
155
|
+
version: '1.0',
|
|
156
|
+
runId: newRunId(),
|
|
157
|
+
at: new Date().toISOString(),
|
|
158
|
+
task: {
|
|
159
|
+
name: 'train',
|
|
160
|
+
command: 'python train.py --epochs 10',
|
|
161
|
+
requirements: { packages: ['torch', 'transformers'] },
|
|
162
|
+
},
|
|
163
|
+
selected: {
|
|
164
|
+
pythonPath: 'C:\\project\\.venv\\Scripts\\python.exe',
|
|
165
|
+
score: 95,
|
|
166
|
+
status: 'good',
|
|
167
|
+
},
|
|
168
|
+
outcome: {
|
|
169
|
+
ok: true,
|
|
170
|
+
exitCode: 0,
|
|
171
|
+
durationMs: 45000,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 任务聚类
|
|
177
|
+
|
|
178
|
+
当您有许多任务运行时,venvkit 会根据签名对它们进行聚类:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { clusterRuns, isFlaky, getFailingEnvs } from 'venvkit';
|
|
182
|
+
|
|
183
|
+
const clusters = clusterRuns(runs);
|
|
184
|
+
|
|
185
|
+
for (const c of clusters) {
|
|
186
|
+
console.log(`${c.sig.name}: ${c.ok}/${c.runs} (${(c.successRate * 100).toFixed(0)}%)`);
|
|
187
|
+
|
|
188
|
+
if (isFlaky(c)) {
|
|
189
|
+
console.log(` WARNING: Flaky task!`);
|
|
190
|
+
const badEnvs = getFailingEnvs(c, 3);
|
|
191
|
+
console.log(` Failing most on: ${badEnvs.map(e => e.pythonPath).join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 图形模式
|
|
197
|
+
|
|
198
|
+
`mapRender` 输出遵循稳定的 JSON 模式:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
type GraphJSONv1 = {
|
|
202
|
+
version: '1.0';
|
|
203
|
+
generatedAt: string;
|
|
204
|
+
host: { os: string; arch: string; hostname: string };
|
|
205
|
+
summary: {
|
|
206
|
+
envCount: number;
|
|
207
|
+
baseCount: number;
|
|
208
|
+
taskCount: number;
|
|
209
|
+
healthy: number;
|
|
210
|
+
warning: number;
|
|
211
|
+
broken: number;
|
|
212
|
+
runsPassed: number;
|
|
213
|
+
runsFailed: number;
|
|
214
|
+
topIssues: Array<{ code: string; count: number; hint: string }>;
|
|
215
|
+
};
|
|
216
|
+
nodes: GraphNode[];
|
|
217
|
+
edges: GraphEdge[];
|
|
218
|
+
};
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 节点类型
|
|
222
|
+
|
|
223
|
+
| 类型 | 描述 |
|
|
224
|
+
|------|-------------|
|
|
225
|
+
| `base` | 基本 Python 解释器(例如:`C:\Python311`) |
|
|
226
|
+
| `venv` | 虚拟环境 |
|
|
227
|
+
| `task` | 任务签名(聚类运行) |
|
|
228
|
+
|
|
229
|
+
### 边类型
|
|
230
|
+
|
|
231
|
+
| 类型 | 描述 |
|
|
232
|
+
|------|-------------|
|
|
233
|
+
| `USES_BASE` | venv → 基本关系 |
|
|
234
|
+
| `ROUTES_TASK_TO` | 任务 → 环境 路由 |
|
|
235
|
+
| `FAILED_RUN` | 任务 → 环境 失败(Mermaid 中为虚线) |
|
|
236
|
+
|
|
237
|
+
## 错误代码
|
|
238
|
+
|
|
239
|
+
| 代码 | 严重性 | 描述 |
|
|
240
|
+
|------|----------|-------------|
|
|
241
|
+
| `SSL_BROKEN` | bad | SSL 模块导入失败 |
|
|
242
|
+
| `CERT_STORE_FAIL` | warn | HTTPS 证书验证失败 |
|
|
243
|
+
| `DLL_LOAD_FAIL` | bad | 本机扩展 DLL 加载失败 |
|
|
244
|
+
| `ABI_MISMATCH` | bad | 二进制不兼容(ARM/x86) |
|
|
245
|
+
| `PIP_MISSING` | warn | pip 不可用 |
|
|
246
|
+
| `PIP_CHECK_FAIL` | warn | 检测到依赖项冲突 |
|
|
247
|
+
| `USER_SITE_LEAK` | warn | venv 中启用了用户站点包 |
|
|
248
|
+
| `PYTHONPATH_INJECTED` | warn | 设置了 PYTHONPATH 环境变量 |
|
|
249
|
+
| `ARCH_MISMATCH` | bad | 当需要 64 位的 Python 时,使用了 32 位的 Python |
|
|
250
|
+
| `PYVENV_CFG_INVALID` | warn | pyvenv.cfg 文件损坏或缺失 |
|
|
251
|
+
|
|
252
|
+
## 开发
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npm install
|
|
256
|
+
npm run typecheck # Type check
|
|
257
|
+
npm run test # Run tests
|
|
258
|
+
npm run build # Build to dist/
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## 安全与数据范围
|
|
262
|
+
|
|
263
|
+
- **只读扫描:** Python 可执行文件和 pyvenv.cfg 文件会被读取,但绝不会被修改。
|
|
264
|
+
- **子进程:** 使用受控参数启动 `python` 进程,不涉及 shell 执行。
|
|
265
|
+
- **网络:** 可选的 `--httpsProbe` 参数用于测试 SSL 证书,不会发送其他类型的网络请求。
|
|
266
|
+
- **不收集或发送任何遥测数据**,详细的安全策略请参见 [SECURITY.md](SECURITY.md) 文件。
|
|
267
|
+
|
|
268
|
+
## 许可证
|
|
269
|
+
|
|
270
|
+
MIT
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
由 [MCP Tool Shop](https://mcp-tool-shop.github.io/) 构建。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// integration.test.ts
|
|
2
|
+
// End-to-end integration tests for venvkit workflow
|
|
3
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import { scanEnvPaths } from "./scanEnvPaths.js";
|
|
8
|
+
import { mapRender } from "./mapRender.js";
|
|
9
|
+
import { appendRunLog, readRunLog, summarizeRuns } from "./runLog.js";
|
|
10
|
+
import { clusterRuns, summarizeClusters } from "./taskCluster.js";
|
|
11
|
+
describe("integration", () => {
|
|
12
|
+
let tempDir;
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "venvkit-integration-"));
|
|
15
|
+
});
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
// Helper to create a mock venv structure
|
|
20
|
+
async function createMockVenv(venvPath) {
|
|
21
|
+
await fs.mkdir(venvPath, { recursive: true });
|
|
22
|
+
await fs.writeFile(path.join(venvPath, "pyvenv.cfg"), "home = C:\\Python311\nversion = 3.11.5\n");
|
|
23
|
+
const binDir = os.platform() === "win32" ? "Scripts" : "bin";
|
|
24
|
+
const pyExe = os.platform() === "win32" ? "python.exe" : "python";
|
|
25
|
+
await fs.mkdir(path.join(venvPath, binDir), { recursive: true });
|
|
26
|
+
await fs.writeFile(path.join(venvPath, binDir, pyExe), "fake python");
|
|
27
|
+
return path.join(venvPath, binDir, pyExe);
|
|
28
|
+
}
|
|
29
|
+
// Helper to create mock doctor report
|
|
30
|
+
function makeReport(pythonPath, opts = {}) {
|
|
31
|
+
return {
|
|
32
|
+
pythonPath,
|
|
33
|
+
ranAt: new Date().toISOString(),
|
|
34
|
+
status: opts.status ?? "good",
|
|
35
|
+
score: opts.score ?? 100,
|
|
36
|
+
summary: "Test report",
|
|
37
|
+
facts: {
|
|
38
|
+
version: "3.11.5 (main)",
|
|
39
|
+
version_info: [3, 11, 5],
|
|
40
|
+
executable: pythonPath,
|
|
41
|
+
prefix: pythonPath.replace(/[/\\](Scripts|bin)[/\\]python(\.exe)?$/, ""),
|
|
42
|
+
base_prefix: opts.basePrefix ?? "C:\\Python311",
|
|
43
|
+
bits: 64,
|
|
44
|
+
machine: "AMD64",
|
|
45
|
+
os: "windows",
|
|
46
|
+
py_path: [],
|
|
47
|
+
enable_user_site: false,
|
|
48
|
+
user_site: "",
|
|
49
|
+
},
|
|
50
|
+
findings: opts.findings ?? [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Helper to create run event
|
|
54
|
+
function makeRunEvent(overrides = {}) {
|
|
55
|
+
return {
|
|
56
|
+
version: "1.0",
|
|
57
|
+
runId: `run_${Math.random().toString(36).slice(2, 10)}`,
|
|
58
|
+
at: overrides.at ?? new Date().toISOString(),
|
|
59
|
+
cwd: tempDir,
|
|
60
|
+
task: overrides.task ?? {
|
|
61
|
+
name: overrides.taskName ?? "pytest",
|
|
62
|
+
command: "pytest tests/",
|
|
63
|
+
args: ["tests/"],
|
|
64
|
+
requirements: {
|
|
65
|
+
packages: ["pytest"],
|
|
66
|
+
tags: ["test"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
selected: overrides.selected ?? {
|
|
70
|
+
pythonPath: overrides.pythonPath ?? "C:\\repo\\.venv\\Scripts\\python.exe",
|
|
71
|
+
score: 95,
|
|
72
|
+
status: "good",
|
|
73
|
+
},
|
|
74
|
+
outcome: overrides.outcome ?? {
|
|
75
|
+
ok: overrides.ok ?? true,
|
|
76
|
+
exitCode: overrides.ok === false ? 1 : 0,
|
|
77
|
+
durationMs: 1500,
|
|
78
|
+
errorClass: overrides.ok === false ? (overrides.errorClass ?? "RUNTIME_ERROR") : undefined,
|
|
79
|
+
},
|
|
80
|
+
doctor: overrides.doctor,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
describe("scan and doctor workflow", () => {
|
|
84
|
+
it("scans for venvs and creates doctor reports", async () => {
|
|
85
|
+
// Step 1: Create venvs
|
|
86
|
+
const py1 = await createMockVenv(path.join(tempDir, "project1", ".venv"));
|
|
87
|
+
const py2 = await createMockVenv(path.join(tempDir, "project2", ".venv"));
|
|
88
|
+
// Step 2: Scan for venvs
|
|
89
|
+
const scanResult = await scanEnvPaths({
|
|
90
|
+
roots: [tempDir],
|
|
91
|
+
maxDepth: 3,
|
|
92
|
+
includeUserHomeCache: false,
|
|
93
|
+
});
|
|
94
|
+
// Should find our mock venvs
|
|
95
|
+
expect(scanResult.meta.foundVenvs).toBeGreaterThanOrEqual(2);
|
|
96
|
+
// Step 3: Create mock doctor reports (in real usage, would run doctorLite)
|
|
97
|
+
const reports = [
|
|
98
|
+
makeReport(py1, { status: "good", score: 100 }),
|
|
99
|
+
makeReport(py2, { status: "warn", score: 70 }),
|
|
100
|
+
];
|
|
101
|
+
// Step 4: Render graph
|
|
102
|
+
const renderResult = mapRender(reports, [], { format: "both" });
|
|
103
|
+
expect(renderResult.graph.summary.envCount).toBe(2);
|
|
104
|
+
expect(renderResult.graph.summary.healthy).toBe(1);
|
|
105
|
+
expect(renderResult.graph.summary.warning).toBe(1);
|
|
106
|
+
expect(renderResult.mermaid).toContain("graph TD");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe("task clustering workflow", () => {
|
|
110
|
+
it("logs tasks, clusters them, and generates insights", async () => {
|
|
111
|
+
const logPath = path.join(tempDir, "runs.jsonl");
|
|
112
|
+
// Step 1: Log multiple task runs
|
|
113
|
+
const events = [
|
|
114
|
+
makeRunEvent({ taskName: "pytest", ok: true }),
|
|
115
|
+
makeRunEvent({ taskName: "pytest", ok: true }),
|
|
116
|
+
makeRunEvent({ taskName: "pytest", ok: false, errorClass: "IMPORT_ERROR" }),
|
|
117
|
+
makeRunEvent({ taskName: "lint", ok: true }),
|
|
118
|
+
makeRunEvent({ taskName: "lint", ok: true }),
|
|
119
|
+
makeRunEvent({ taskName: "build", ok: false, errorClass: "SSL_BROKEN" }),
|
|
120
|
+
];
|
|
121
|
+
for (const evt of events) {
|
|
122
|
+
await appendRunLog(logPath, evt);
|
|
123
|
+
}
|
|
124
|
+
// Step 2: Read run log
|
|
125
|
+
const runs = await readRunLog(logPath);
|
|
126
|
+
expect(runs).toHaveLength(6);
|
|
127
|
+
// Step 3: Cluster runs
|
|
128
|
+
const clusters = clusterRuns(runs);
|
|
129
|
+
expect(clusters.length).toBe(3); // pytest, lint, build
|
|
130
|
+
const pytestCluster = clusters.find((c) => c.sig.name === "pytest");
|
|
131
|
+
expect(pytestCluster?.runs).toBe(3);
|
|
132
|
+
expect(pytestCluster?.ok).toBe(2);
|
|
133
|
+
expect(pytestCluster?.fail).toBe(1);
|
|
134
|
+
// Step 4: Get summary
|
|
135
|
+
const summary = summarizeClusters(clusters);
|
|
136
|
+
expect(summary.totalTasks).toBe(3);
|
|
137
|
+
expect(summary.totalRuns).toBe(6);
|
|
138
|
+
expect(summary.totalOk).toBe(4);
|
|
139
|
+
expect(summary.totalFail).toBe(2);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe("full graph rendering workflow", () => {
|
|
143
|
+
it("combines doctor reports and task runs into unified graph", async () => {
|
|
144
|
+
const logPath = path.join(tempDir, "runs.jsonl");
|
|
145
|
+
// Step 1: Create mock envs and doctor reports
|
|
146
|
+
const py1 = "C:\\project1\\.venv\\Scripts\\python.exe";
|
|
147
|
+
const py2 = "C:\\project2\\.venv\\Scripts\\python.exe";
|
|
148
|
+
const reports = [
|
|
149
|
+
makeReport(py1, { status: "good", score: 95, basePrefix: "C:\\Python311" }),
|
|
150
|
+
makeReport(py2, { status: "bad", score: 30, basePrefix: "C:\\Python311", findings: [
|
|
151
|
+
{ code: "SSL_BROKEN", severity: "bad", penalty: 40, what: "ssl", why: "why", fix: [] },
|
|
152
|
+
] }),
|
|
153
|
+
];
|
|
154
|
+
// Step 2: Log task runs against these envs
|
|
155
|
+
const events = [
|
|
156
|
+
makeRunEvent({ taskName: "pytest", pythonPath: py1, ok: true }),
|
|
157
|
+
makeRunEvent({ taskName: "pytest", pythonPath: py1, ok: true }),
|
|
158
|
+
makeRunEvent({ taskName: "pytest", pythonPath: py2, ok: false, errorClass: "SSL_BROKEN" }),
|
|
159
|
+
makeRunEvent({ taskName: "pytest", pythonPath: py2, ok: false, errorClass: "SSL_BROKEN" }),
|
|
160
|
+
];
|
|
161
|
+
for (const evt of events) {
|
|
162
|
+
await appendRunLog(logPath, evt);
|
|
163
|
+
}
|
|
164
|
+
const runs = await readRunLog(logPath);
|
|
165
|
+
// Step 3: Render combined graph
|
|
166
|
+
const result = mapRender(reports, runs, {
|
|
167
|
+
format: "both",
|
|
168
|
+
taskMode: "clustered",
|
|
169
|
+
includeHotEdgeLabels: true,
|
|
170
|
+
});
|
|
171
|
+
// Verify graph structure
|
|
172
|
+
expect(result.graph.summary.envCount).toBe(2);
|
|
173
|
+
expect(result.graph.summary.taskCount).toBe(1); // 1 clustered pytest task
|
|
174
|
+
// Verify insights
|
|
175
|
+
expect(result.insights.length).toBeGreaterThan(0);
|
|
176
|
+
// Verify mermaid output
|
|
177
|
+
expect(result.mermaid).toContain("graph TD");
|
|
178
|
+
expect(result.mermaid).toContain("pytest");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe("workflow with logging", () => {
|
|
182
|
+
it("tracks task execution lifecycle through logging", async () => {
|
|
183
|
+
const logPath = path.join(tempDir, "lifecycle.jsonl");
|
|
184
|
+
// Simulate a development session with multiple task runs
|
|
185
|
+
// Session 1: Initial development
|
|
186
|
+
const session1Events = [
|
|
187
|
+
makeRunEvent({ taskName: "lint", ok: true, at: "2024-01-01T10:00:00Z" }),
|
|
188
|
+
makeRunEvent({ taskName: "test", ok: true, at: "2024-01-01T10:01:00Z" }),
|
|
189
|
+
makeRunEvent({ taskName: "build", ok: true, at: "2024-01-01T10:02:00Z" }),
|
|
190
|
+
];
|
|
191
|
+
for (const evt of session1Events) {
|
|
192
|
+
await appendRunLog(logPath, evt);
|
|
193
|
+
}
|
|
194
|
+
// Session 2: Bug introduced
|
|
195
|
+
const session2Events = [
|
|
196
|
+
makeRunEvent({ taskName: "lint", ok: true, at: "2024-01-02T10:00:00Z" }),
|
|
197
|
+
makeRunEvent({ taskName: "test", ok: false, errorClass: "RUNTIME_ERROR", at: "2024-01-02T10:01:00Z" }),
|
|
198
|
+
makeRunEvent({ taskName: "build", ok: false, errorClass: "BUILD_FAIL", at: "2024-01-02T10:02:00Z" }),
|
|
199
|
+
];
|
|
200
|
+
for (const evt of session2Events) {
|
|
201
|
+
await appendRunLog(logPath, evt);
|
|
202
|
+
}
|
|
203
|
+
// Read all logs
|
|
204
|
+
const runs = await readRunLog(logPath);
|
|
205
|
+
expect(runs).toHaveLength(6);
|
|
206
|
+
// Summarize
|
|
207
|
+
const summary = summarizeRuns(runs);
|
|
208
|
+
expect(summary.total).toBe(6);
|
|
209
|
+
expect(summary.passed).toBe(4);
|
|
210
|
+
expect(summary.failed).toBe(2);
|
|
211
|
+
// Task-level summary
|
|
212
|
+
expect(summary.byTask.get("lint")).toEqual({ passed: 2, failed: 0 });
|
|
213
|
+
expect(summary.byTask.get("test")).toEqual({ passed: 1, failed: 1 });
|
|
214
|
+
expect(summary.byTask.get("build")).toEqual({ passed: 1, failed: 1 });
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("CLI integration", () => {
|
|
218
|
+
it("produces all expected output files", async () => {
|
|
219
|
+
// This test verifies the expected output structure from CLI
|
|
220
|
+
// without actually running the CLI (which would require real Python envs)
|
|
221
|
+
const outDir = path.join(tempDir, "output");
|
|
222
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
223
|
+
// Create mock outputs as CLI would
|
|
224
|
+
const reports = [makeReport("C:\\project\\.venv\\Scripts\\python.exe")];
|
|
225
|
+
const result = mapRender(reports, [], { format: "both" });
|
|
226
|
+
// Write outputs as CLI would
|
|
227
|
+
await fs.writeFile(path.join(outDir, "venv-map.json"), JSON.stringify(result.graph, null, 2));
|
|
228
|
+
await fs.writeFile(path.join(outDir, "venv-map.mmd"), result.mermaid ?? "");
|
|
229
|
+
await fs.writeFile(path.join(outDir, "reports.json"), JSON.stringify(reports, null, 2));
|
|
230
|
+
await fs.writeFile(path.join(outDir, "insights.json"), JSON.stringify(result.insights, null, 2));
|
|
231
|
+
// Verify all files exist
|
|
232
|
+
const files = await fs.readdir(outDir);
|
|
233
|
+
expect(files).toContain("venv-map.json");
|
|
234
|
+
expect(files).toContain("venv-map.mmd");
|
|
235
|
+
expect(files).toContain("reports.json");
|
|
236
|
+
expect(files).toContain("insights.json");
|
|
237
|
+
// Verify JSON is valid
|
|
238
|
+
const graphJson = JSON.parse(await fs.readFile(path.join(outDir, "venv-map.json"), "utf8"));
|
|
239
|
+
expect(graphJson.summary).toBeDefined();
|
|
240
|
+
expect(graphJson.nodes).toBeDefined();
|
|
241
|
+
expect(graphJson.edges).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
//# sourceMappingURL=integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.test.js","sourceRoot":"","sources":["../integration.test.ts"],"names":[],"mappings":"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"}
|
package/dist/mapRender.test.js
CHANGED
|
@@ -466,5 +466,106 @@ describe("mapRender", () => {
|
|
|
466
466
|
expect(contagionInsight?.text).toContain("SSL_BROKEN");
|
|
467
467
|
});
|
|
468
468
|
});
|
|
469
|
+
describe("empty and edge cases", () => {
|
|
470
|
+
it("renders empty graph with no reports", () => {
|
|
471
|
+
const result = mapRender([], []);
|
|
472
|
+
expect(result.graph.summary.envCount).toBe(0);
|
|
473
|
+
expect(result.graph.summary.baseCount).toBe(0);
|
|
474
|
+
expect(result.graph.nodes).toHaveLength(0);
|
|
475
|
+
expect(result.graph.edges).toHaveLength(0);
|
|
476
|
+
});
|
|
477
|
+
it("renders single node graph", () => {
|
|
478
|
+
const reports = [makeReport("C:\\single\\.venv\\Scripts\\python.exe")];
|
|
479
|
+
const result = mapRender(reports, []);
|
|
480
|
+
expect(result.graph.summary.envCount).toBe(1);
|
|
481
|
+
// Should have venv node and base node
|
|
482
|
+
expect(result.graph.nodes.filter((n) => n.type === "venv")).toHaveLength(1);
|
|
483
|
+
expect(result.graph.nodes.filter((n) => n.type === "base")).toHaveLength(1);
|
|
484
|
+
});
|
|
485
|
+
it("handles invalid or missing facts gracefully", () => {
|
|
486
|
+
const report = {
|
|
487
|
+
pythonPath: "C:\\broken\\python.exe",
|
|
488
|
+
ranAt: new Date().toISOString(),
|
|
489
|
+
status: "bad",
|
|
490
|
+
score: 0,
|
|
491
|
+
summary: "Missing facts",
|
|
492
|
+
facts: undefined, // Missing facts
|
|
493
|
+
findings: [],
|
|
494
|
+
};
|
|
495
|
+
// Should not throw
|
|
496
|
+
expect(() => mapRender([report], [])).not.toThrow();
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
describe("output format control", () => {
|
|
500
|
+
it("returns json only when format is json", () => {
|
|
501
|
+
const reports = [makeReport("C:\\project\\.venv\\Scripts\\python.exe")];
|
|
502
|
+
const result = mapRender(reports, [], { format: "json" });
|
|
503
|
+
expect(result.graph).toBeDefined();
|
|
504
|
+
expect(result.mermaid).toBeUndefined();
|
|
505
|
+
});
|
|
506
|
+
it("returns mermaid only when format is mermaid", () => {
|
|
507
|
+
const reports = [makeReport("C:\\project\\.venv\\Scripts\\python.exe")];
|
|
508
|
+
const result = mapRender(reports, [], { format: "mermaid" });
|
|
509
|
+
expect(result.mermaid).toBeDefined();
|
|
510
|
+
// graph is still returned for internal consistency
|
|
511
|
+
});
|
|
512
|
+
it("returns both when format is both", () => {
|
|
513
|
+
const reports = [makeReport("C:\\project\\.venv\\Scripts\\python.exe")];
|
|
514
|
+
const result = mapRender(reports, [], { format: "both" });
|
|
515
|
+
expect(result.graph).toBeDefined();
|
|
516
|
+
expect(result.mermaid).toBeDefined();
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
describe("complex graph rendering", () => {
|
|
520
|
+
it("renders complex graph with multiple bases and venvs", () => {
|
|
521
|
+
const reports = [
|
|
522
|
+
makeReport("C:\\p1\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python311" }),
|
|
523
|
+
makeReport("C:\\p2\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python311" }),
|
|
524
|
+
makeReport("C:\\p3\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python310" }),
|
|
525
|
+
makeReport("C:\\p4\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python310" }),
|
|
526
|
+
makeReport("C:\\p5\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python39" }),
|
|
527
|
+
];
|
|
528
|
+
const result = mapRender(reports, []);
|
|
529
|
+
expect(result.graph.summary.envCount).toBe(5);
|
|
530
|
+
expect(result.graph.summary.baseCount).toBe(3);
|
|
531
|
+
// Should have edges from each base to its venvs
|
|
532
|
+
const usesBaseEdges = result.graph.edges.filter((e) => e.type === "USES_BASE");
|
|
533
|
+
expect(usesBaseEdges.length).toBe(5);
|
|
534
|
+
});
|
|
535
|
+
it("renders with clusters (base subgraphs)", () => {
|
|
536
|
+
const reports = [
|
|
537
|
+
makeReport("C:\\p1\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python311" }),
|
|
538
|
+
makeReport("C:\\p2\\.venv\\Scripts\\python.exe", { basePrefix: "C:\\Python311" }),
|
|
539
|
+
];
|
|
540
|
+
const result = mapRender(reports, [], { includeBaseSubgraphs: true, format: "both" });
|
|
541
|
+
// Mermaid should include subgraph syntax
|
|
542
|
+
expect(result.mermaid).toContain("subgraph");
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe("render validation", () => {
|
|
546
|
+
it("validates all nodes have unique IDs", () => {
|
|
547
|
+
const reports = [
|
|
548
|
+
makeReport("C:\\p1\\.venv\\Scripts\\python.exe"),
|
|
549
|
+
makeReport("C:\\p2\\.venv\\Scripts\\python.exe"),
|
|
550
|
+
makeReport("C:\\p3\\.venv\\Scripts\\python.exe"),
|
|
551
|
+
];
|
|
552
|
+
const result = mapRender(reports, []);
|
|
553
|
+
const nodeIds = result.graph.nodes.map((n) => n.id);
|
|
554
|
+
const uniqueIds = new Set(nodeIds);
|
|
555
|
+
expect(uniqueIds.size).toBe(nodeIds.length);
|
|
556
|
+
});
|
|
557
|
+
it("validates all edges reference existing nodes", () => {
|
|
558
|
+
const reports = [
|
|
559
|
+
makeReport("C:\\p1\\.venv\\Scripts\\python.exe"),
|
|
560
|
+
makeReport("C:\\p2\\.venv\\Scripts\\python.exe"),
|
|
561
|
+
];
|
|
562
|
+
const result = mapRender(reports, []);
|
|
563
|
+
const nodeIds = new Set(result.graph.nodes.map((n) => n.id));
|
|
564
|
+
for (const edge of result.graph.edges) {
|
|
565
|
+
expect(nodeIds.has(edge.from)).toBe(true);
|
|
566
|
+
expect(nodeIds.has(edge.to)).toBe(true);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
});
|
|
469
570
|
});
|
|
470
571
|
//# sourceMappingURL=mapRender.test.js.map
|