@bovinphang/craft-common 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +12 -2
- package/dist/content/catalog.d.ts +4 -0
- package/dist/content/resources.d.ts +2 -0
- package/dist/content/types.d.ts +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +668 -0
- package/dist/install/identity.d.ts +5 -0
- package/dist/install/planner.d.ts +6 -0
- package/dist/install/transaction.d.ts +7 -0
- package/dist/install/types.d.ts +44 -0
- package/docs/consuming-common.md +33 -0
- package/localized/zh-CN/methods/debug.md +13 -0
- package/localized/zh-CN/methods/doc-sync.md +12 -0
- package/localized/zh-CN/methods/refactoring.md +16 -0
- package/localized/zh-CN/methods/source-driven-development.md +19 -0
- package/localized/zh-CN/methods/tdd.md +13 -0
- package/localized/zh-CN/skills/cc-debug-methodology/SKILL.md +8 -0
- package/localized/zh-CN/skills/cc-debug-methodology/references/method.md +13 -0
- package/localized/zh-CN/skills/cc-doc-sync-core/SKILL.md +8 -0
- package/localized/zh-CN/skills/cc-doc-sync-core/references/method.md +12 -0
- package/localized/zh-CN/skills/cc-drawio-studio/SKILL.md +73 -0
- package/localized/zh-CN/skills/cc-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
- package/localized/zh-CN/skills/cc-drawio-studio/data/brand-icons.json +878 -0
- package/localized/zh-CN/skills/cc-drawio-studio/data/shape-index.json +83570 -0
- package/localized/zh-CN/skills/cc-drawio-studio/references/data-residency.md +27 -0
- package/localized/zh-CN/skills/cc-drawio-studio/references/diagram-patterns.md +60 -0
- package/localized/zh-CN/skills/cc-drawio-studio/references/flowchart-quality.md +49 -0
- package/localized/zh-CN/skills/cc-drawio-studio/references/layout-and-export.md +72 -0
- package/localized/zh-CN/skills/cc-drawio-studio/references/troubleshooting.md +29 -0
- package/localized/zh-CN/skills/cc-drawio-studio/references/xml-and-mermaid.md +58 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/brand-symbols.mjs +99 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/diagram-lint.mjs +205 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/diagram-url.mjs +102 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/drawio-export.mjs +134 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/layout-graph.mjs +107 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/png-embed-fix.mjs +26 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-go-packages.mjs +36 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-js-modules.mjs +52 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-python-classes.mjs +30 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-python-modules.mjs +55 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/shape-query.mjs +121 -0
- package/localized/zh-CN/skills/cc-drawio-studio/scripts/studio-core.mjs +139 -0
- package/localized/zh-CN/skills/cc-drawio-studio/styles/built-in/corporate.json +49 -0
- package/localized/zh-CN/skills/cc-drawio-studio/styles/built-in/default.json +49 -0
- package/localized/zh-CN/skills/cc-drawio-studio/styles/built-in/handdrawn.json +49 -0
- package/localized/zh-CN/skills/cc-drawio-studio/styles/schema.json +112 -0
- package/localized/zh-CN/skills/cc-refactoring-core/SKILL.md +8 -0
- package/localized/zh-CN/skills/cc-refactoring-core/references/method.md +16 -0
- package/localized/zh-CN/skills/cc-source-driven-development/SKILL.md +8 -0
- package/localized/zh-CN/skills/cc-source-driven-development/references/method.md +19 -0
- package/localized/zh-CN/skills/cc-tdd-methodology/SKILL.md +8 -0
- package/localized/zh-CN/skills/cc-tdd-methodology/references/method.md +13 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/SKILL.md +34 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/interactive-diagram.html +371 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/README.md +14 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/architecture.json +121 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/invalid-explicit.json +41 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/sequence.json +53 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/sequence.mmd +21 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/workflow.json +98 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/workflow.mmd +7 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/references/diagram-workflows.md +81 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/references/html-technical-diagrams.md +243 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/references/png-qa-autofix.md +37 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/diagram-browser.mjs +153 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/diagram-layout.mjs +176 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/export-diagram.mjs +332 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/interactive-diagram-server.mjs +654 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/mermaid-render.mjs +64 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/png-qa.mjs +462 -0
- package/localized/zh-CN/skills/cc-technical-diagrams/scripts/tech-diagram-render.mjs +1537 -0
- package/localized/zh-CN/skills/eval_queries.json +160 -0
- package/localized/zh-CN/skills/metadata.json +223 -0
- package/localized/zh-CN/skills/relations.json +53 -0
- package/methods/debug.md +13 -0
- package/methods/doc-sync.md +12 -0
- package/methods/refactoring.md +16 -0
- package/methods/source-driven-development.md +19 -0
- package/methods/tdd.md +13 -0
- package/package.json +51 -4
- package/skills/cc-debug-methodology/SKILL.md +8 -0
- package/skills/cc-debug-methodology/references/method.md +13 -0
- package/skills/cc-doc-sync-core/SKILL.md +8 -0
- package/skills/cc-doc-sync-core/references/method.md +12 -0
- package/skills/cc-drawio-studio/SKILL.md +73 -0
- package/skills/cc-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
- package/skills/cc-drawio-studio/data/brand-icons.json +878 -0
- package/skills/cc-drawio-studio/data/shape-index.json +83570 -0
- package/skills/cc-drawio-studio/references/data-residency.md +27 -0
- package/skills/cc-drawio-studio/references/diagram-patterns.md +60 -0
- package/skills/cc-drawio-studio/references/flowchart-quality.md +49 -0
- package/skills/cc-drawio-studio/references/layout-and-export.md +72 -0
- package/skills/cc-drawio-studio/references/troubleshooting.md +29 -0
- package/skills/cc-drawio-studio/references/xml-and-mermaid.md +58 -0
- package/skills/cc-drawio-studio/scripts/brand-symbols.mjs +99 -0
- package/skills/cc-drawio-studio/scripts/diagram-lint.mjs +205 -0
- package/skills/cc-drawio-studio/scripts/diagram-url.mjs +102 -0
- package/skills/cc-drawio-studio/scripts/drawio-export.mjs +134 -0
- package/skills/cc-drawio-studio/scripts/layout-graph.mjs +107 -0
- package/skills/cc-drawio-studio/scripts/png-embed-fix.mjs +26 -0
- package/skills/cc-drawio-studio/scripts/scan-go-packages.mjs +36 -0
- package/skills/cc-drawio-studio/scripts/scan-js-modules.mjs +52 -0
- package/skills/cc-drawio-studio/scripts/scan-python-classes.mjs +30 -0
- package/skills/cc-drawio-studio/scripts/scan-python-modules.mjs +55 -0
- package/skills/cc-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
- package/skills/cc-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
- package/skills/cc-drawio-studio/scripts/shape-query.mjs +121 -0
- package/skills/cc-drawio-studio/scripts/studio-core.mjs +139 -0
- package/skills/cc-drawio-studio/styles/built-in/corporate.json +49 -0
- package/skills/cc-drawio-studio/styles/built-in/default.json +49 -0
- package/skills/cc-drawio-studio/styles/built-in/handdrawn.json +49 -0
- package/skills/cc-drawio-studio/styles/schema.json +112 -0
- package/skills/cc-refactoring-core/SKILL.md +8 -0
- package/skills/cc-refactoring-core/references/method.md +16 -0
- package/skills/cc-source-driven-development/SKILL.md +8 -0
- package/skills/cc-source-driven-development/references/method.md +19 -0
- package/skills/cc-tdd-methodology/SKILL.md +8 -0
- package/skills/cc-tdd-methodology/references/method.md +13 -0
- package/skills/cc-technical-diagrams/SKILL.md +34 -0
- package/skills/cc-technical-diagrams/assets/interactive-diagram.html +371 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/README.md +14 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/architecture.json +121 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/invalid-explicit.json +41 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/sequence.json +53 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/sequence.mmd +21 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/workflow.json +98 -0
- package/skills/cc-technical-diagrams/assets/quality-examples/workflow.mmd +7 -0
- package/skills/cc-technical-diagrams/references/diagram-workflows.md +81 -0
- package/skills/cc-technical-diagrams/references/html-technical-diagrams.md +243 -0
- package/skills/cc-technical-diagrams/references/png-qa-autofix.md +37 -0
- package/skills/cc-technical-diagrams/scripts/diagram-browser.mjs +153 -0
- package/skills/cc-technical-diagrams/scripts/diagram-layout.mjs +176 -0
- package/skills/cc-technical-diagrams/scripts/export-diagram.mjs +332 -0
- package/skills/cc-technical-diagrams/scripts/interactive-diagram-server.mjs +654 -0
- package/skills/cc-technical-diagrams/scripts/mermaid-render.mjs +64 -0
- package/skills/cc-technical-diagrams/scripts/png-qa.mjs +462 -0
- package/skills/cc-technical-diagrams/scripts/tech-diagram-render.mjs +1537 -0
- package/skills/eval_queries.json +160 -0
- package/skills/metadata.json +223 -0
- package/skills/relations.json +53 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"diagram_type": "workflow",
|
|
4
|
+
"meta": {
|
|
5
|
+
"title": "订单审批与异常回退"
|
|
6
|
+
},
|
|
7
|
+
"lanes": [
|
|
8
|
+
{
|
|
9
|
+
"id": "user",
|
|
10
|
+
"label": "用户提交"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "ops",
|
|
14
|
+
"label": "审批与处理"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "sys",
|
|
18
|
+
"label": "系统执行"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"nodes": [
|
|
22
|
+
{
|
|
23
|
+
"id": "start",
|
|
24
|
+
"lane": "user",
|
|
25
|
+
"col": 0,
|
|
26
|
+
"label": "提交订单申请",
|
|
27
|
+
"type": "start"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "check",
|
|
31
|
+
"lane": "ops",
|
|
32
|
+
"col": 1,
|
|
33
|
+
"label": "检查订单信息是否完整且符合审核条件",
|
|
34
|
+
"type": "decision"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "approve",
|
|
38
|
+
"lane": "ops",
|
|
39
|
+
"col": 2,
|
|
40
|
+
"label": "人工审批并记录处理意见",
|
|
41
|
+
"type": "backend"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "retry",
|
|
45
|
+
"lane": "user",
|
|
46
|
+
"col": 1,
|
|
47
|
+
"label": "补充缺失材料后重新提交",
|
|
48
|
+
"type": "waiting"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "save",
|
|
52
|
+
"lane": "sys",
|
|
53
|
+
"col": 3,
|
|
54
|
+
"label": "保存审批结果并异步通知申请人",
|
|
55
|
+
"type": "database"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "done",
|
|
59
|
+
"lane": "sys",
|
|
60
|
+
"col": 4,
|
|
61
|
+
"label": "处理完成",
|
|
62
|
+
"type": "success"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"edges": [
|
|
66
|
+
{
|
|
67
|
+
"from": "start",
|
|
68
|
+
"to": "check",
|
|
69
|
+
"label": "提交审核"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"from": "check",
|
|
73
|
+
"to": "approve",
|
|
74
|
+
"label": "信息完整"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"from": "check",
|
|
78
|
+
"to": "retry",
|
|
79
|
+
"label": "需要补充",
|
|
80
|
+
"variant": "dashed"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"from": "retry",
|
|
84
|
+
"to": "check",
|
|
85
|
+
"label": "重新审核"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"from": "approve",
|
|
89
|
+
"to": "save",
|
|
90
|
+
"label": "审批通过"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"from": "save",
|
|
94
|
+
"to": "done",
|
|
95
|
+
"label": "通知成功"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 图表工作流
|
|
2
|
+
|
|
3
|
+
## 事实源选择
|
|
4
|
+
|
|
5
|
+
| 图表类型 | 首选源 | QA 重点 |
|
|
6
|
+
| ------------ | ---------------- | -------- |
|
|
7
|
+
| ER 图 | Mermaid ER、DBML、Graphviz、SVG | 基数标签、表名、关键字段、交叉关系。 |
|
|
8
|
+
| UML 类图 | Mermaid class、PlantUML、Graphviz | 继承方向、方法/属性换行、包分组。 |
|
|
9
|
+
| 序列图 | Mermaid sequence、PlantUML | lifeline 顺序、activation 范围、异步/返回箭头、消息文本。 |
|
|
10
|
+
| 技术架构 | draw.io、Mermaid/C4、HTML `architecture` IR、SVG/HTML | 分层分组、信任边界、数据流方向、图例。 |
|
|
11
|
+
| Agent / memory 架构 | 带语义节点类型和 flow 连线的 HTML `architecture` IR、SVG/HTML、draw.io | read/write 路径分离、tool-call 回路、memory 分层、retrieval 标签、flow legend。 |
|
|
12
|
+
| ML / 深度学习 | SVG/HTML/canvas | tensor shape、层顺序、分支汇合、重复块、注释。 |
|
|
13
|
+
| 流程图 / 流程工作流 | Mermaid flowchart、HTML `workflow` IR、SVG、draw.io | 决策标签、终止状态、回环可读性、连接线间距、异常路径清晰度。 |
|
|
14
|
+
| 会话实时草图 | 本地交互式浏览器服务 | 增量呈现、session 隔离、拖拽后的标签可读性、导出交接。 |
|
|
15
|
+
|
|
16
|
+
## 交互式实时图表
|
|
17
|
+
|
|
18
|
+
当图表本身是对话的一部分,并且用户能从“边生成边看见结构出现”中受益时,使用实时路线。它适合流程草图、架构初稿、简短思维导图式拆解,以及需要先拖拽或改标签再导出的快速讲解图。
|
|
19
|
+
|
|
20
|
+
启动本地服务:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node skills/cc-technical-diagrams/scripts/interactive-diagram-server.mjs --port 6100
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
打开 `http://127.0.0.1:6100/?s=meaningful-session-id`,再向 `/cmd?s=meaningful-session-id` 发送命令。每张图都必须使用唯一 session id。先发送 `init`,再添加节点,最后添加连线;每条命令尽量小,让浏览器能持续增量更新。
|
|
27
|
+
|
|
28
|
+
支持的命令包括 `init`、`node`、`edge`、`container`、`remove`、`clear`、`layout`、`title` 和 `export`。支持的节点类型包括 `terminal`、`terminal-end`、`process`、`decision`、`service`、`database`、`success`、`error` 和 `container`。
|
|
29
|
+
|
|
30
|
+
服务端导出可用 `/export?s=session-id&format=json`、`svg` 或 `drawio`。PNG 导出使用浏览器工具栏。如果本地服务无法启动,不要阻塞用户,改用 Mermaid、SVG 或 HTML technical diagrams。
|
|
31
|
+
|
|
32
|
+
## 最小源清单
|
|
33
|
+
|
|
34
|
+
渲染路径允许时,在 PNG 旁保存布局 manifest:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"canvas": { "width": 1280, "height": 720 },
|
|
39
|
+
"boxes": [
|
|
40
|
+
{ "id": "api", "x": 80, "y": 120, "width": 220, "height": 96, "label": "API Gateway" }
|
|
41
|
+
],
|
|
42
|
+
"connectors": [
|
|
43
|
+
{ "id": "api-db", "from": "api", "to": "db", "points": [[300, 168], [560, 168]] }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
坐标是导出后的 PNG 像素坐标。boxes 应覆盖可见标签/节点边界,而不仅是中心点。
|
|
49
|
+
|
|
50
|
+
## 图表修复手册
|
|
51
|
+
|
|
52
|
+
- **节点重叠:** 增加 rank/列间距、拆分簇,或将低优先级注释移入图例。
|
|
53
|
+
- **标签截断:** 加宽节点、添加手动换行、保留完整文字并调整源布局。
|
|
54
|
+
- **连接线穿过标签:** 添加 waypoints、沿框体周围正交布线,或将标签移到线段上方。
|
|
55
|
+
- **连接线堆叠:** 用不同 waypoints 分离平行边,或用带共享标签的 bus 汇总。
|
|
56
|
+
- **边缘裁剪:** 增加外边距,或扩大导出 viewBox/canvas 尺寸。
|
|
57
|
+
- **密集图不可读:** 拆成总览图加细节图,不要整体缩小。
|
|
58
|
+
- **箭头含义不清:** 添加 `flow` 和简短标签,不要只依赖颜色。
|
|
59
|
+
|
|
60
|
+
## 架构图
|
|
61
|
+
|
|
62
|
+
用户需要浏览器可打开的单文件、显式系统拓扑、分组、信任边界、数据流和 PNG QA 时,优先使用 HTML `architecture` IR。需要官方厂商图形、diagrams.net 编辑或长期人工维护时,使用 draw.io。希望图主要留在 Markdown 或纯文本技术文档中时,使用 Mermaid/C4。
|
|
63
|
+
|
|
64
|
+
Agent、memory、RAG、tool-call 或 multi-agent 图中,如果形状能帮助解释系统,优先使用语义 architecture 节点类型:`agent`、`model`、`memory`、`vectorstore`、`graphdb`、`tool`、`document`、`queue`、`browser`、`user`、`gateway`。使用 `flow` 值 `control`、`data`、`read`、`write`、`async`、`feedback` 区分读取路径、写入路径、事件和推理回路。先把结构画清楚,再添加 `visual.style`。
|
|
65
|
+
|
|
66
|
+
UML 覆盖优先走确定性源:class、ER、state-machine、activity 和 sequence 需求可使用 Mermaid、PlantUML、HTML technical diagrams、SVG 或 draw.io。需要后续人工编辑或官方记法库时优先 draw.io;需要浏览器可打开交付和 PNG QA 时优先 HTML/SVG。
|
|
67
|
+
|
|
68
|
+
## 流程工作流
|
|
69
|
+
|
|
70
|
+
用户需要浏览器可打开的流程地图、审批流、自动化 runbook、事故流程或循环运营流程,并且需要泳道、步骤编号、参与者、决策、异常路径和摘要卡片时,优先使用 HTML `workflow` IR。流程必须作为 `.drawio` 文件长期维护、需要 diagrams.net 编辑、官方形状库、手工泳道维护或交付后由利益相关方继续修改时,改用 `cc-drawio-studio`。
|
|
71
|
+
|
|
72
|
+
## 导出指引
|
|
73
|
+
|
|
74
|
+
位图 PNG 优先以 2x 倍率导出;只有在文字仍清晰时才下采样。凡是未来可能修改图表的地方,都应提交或附上可编辑源文件。
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## 质量交付约定
|
|
78
|
+
|
|
79
|
+
标准序列/流程图优先本地 Mermaid;架构图需要手工编辑时复用 cc-drawio-studio,大型拓扑可选 Graphviz。无本地工具时使用兼容 JSON/HTML,不自动安装或下载。HTML 预览、独立 SVG 和 PNG 使用同源主题样式;导出指定 `--theme light|dark`。PNG 导出等待字体并测量文字,使用 `--manifest source.layout.json --output-manifest image.actual.json` 写出缩放后的像素坐标。QA 必须读取 actual manifest,不能把估算坐标直接用于 2x PNG。
|
|
80
|
+
|
|
81
|
+
扩展 manifest 保留 canvas/boxes/connectors,增加 coordinateSpace、scale、measurement、groups、labels 与 issues。分组包含节点不算节点重叠。新增 node-text-overflow、label-content-loss、label-out-of-bounds、edge-label-collision、text-overlap 与 degenerate-connector 检查。旧 manifest 保留估算检测。显式尺寸或 waypoints 不满足约束时定位问题,不删字、不改关系、不静默移动。提高导出分辨率不能修复源级溢出。最终 PNG 必须视觉审阅;记录工具可用性、版本、主题、比例、修复轮次和残留问题,未解决时标记部分验收。
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# HTML 技术图
|
|
2
|
+
|
|
3
|
+
当技术图需要单文件浏览器打开、便于分享、支持暗/亮主题,并且导出 PNG 后要做视觉 QA 时,使用这一路线。
|
|
4
|
+
|
|
5
|
+
这一路线补充但不替代其他图表源:
|
|
6
|
+
|
|
7
|
+
| 需求 | 优先选择 |
|
|
8
|
+
| ---- | -------- |
|
|
9
|
+
| 小型 Markdown 维护图 | Mermaid、Graphviz 或 SVG |
|
|
10
|
+
| 后续人工编辑的图源 | `cc-drawio-studio` 和 `.drawio` |
|
|
11
|
+
| 浏览器可打开的主题化技术图 | HTML 技术图路线 |
|
|
12
|
+
| 位图优先的视觉概念、海报、产品图或图片编辑 | 图片生成或编辑路线 |
|
|
13
|
+
|
|
14
|
+
## 支持的图表意图
|
|
15
|
+
|
|
16
|
+
- `architecture`:系统拓扑、系统蓝图、部署拓扑、云区域、信任边界、客户端、网关、服务、数据存储、事件总线、认证流、Agent runtime 图、memory/recall 总览、before/after 架构对比和基础设施总览。
|
|
17
|
+
- `workflow`:职责泳道、流程地图、审批门禁、自动化运行、异常路径、循环流程、runbook、CI/CD、事故响应、工具调用和请求生命周期。
|
|
18
|
+
- `sequence`:参与者随时间交互、API 调用链、缓存回退、鉴权检查、异步链路和返回消息。
|
|
19
|
+
- `dataflow`:来源、采集、处理、存储、消费者、PII 边界、治理、分析和血缘。
|
|
20
|
+
- `lifecycle`:对象状态、终止状态、重试、等待、取消、超时、部署或订单状态流转。
|
|
21
|
+
|
|
22
|
+
浏览器可交付的架构总览优先用 `architecture` 模式。需要 `.drawio` 源、官方厂商图标、diagrams.net 手工编辑或复杂自动布局时,改用 `cc-drawio-studio`。
|
|
23
|
+
|
|
24
|
+
## 主题化语义图
|
|
25
|
+
|
|
26
|
+
同一份 JSON 源需要更明确的交付语气时,可以使用 `visual.style`:`default`、`terminal`、`blueprint`、`minimal`、`glass`、`warm`、`openai` 或 `editorial-dark`。
|
|
27
|
+
|
|
28
|
+
Agent、memory 和 AI 工作流图优先在 architecture 节点上使用语义 `type`:
|
|
29
|
+
|
|
30
|
+
- 参与者和入口:`user`、`browser`、`gateway`。
|
|
31
|
+
- 智能与编排:`agent`、`model`、`tool`。
|
|
32
|
+
- 记忆与知识:`memory`、`vectorstore`、`graphdb`、`document`。
|
|
33
|
+
- 运行时流动:`queue`、`messagebus`、`backend`、`external`。
|
|
34
|
+
|
|
35
|
+
连线不仅表达方向,还需要表达含义时使用 `flow`:
|
|
36
|
+
|
|
37
|
+
- `control`:编排、策略、触发或命令。
|
|
38
|
+
- `data`:请求载荷、文档、embedding、事实或响应。
|
|
39
|
+
- `read`:从 memory 或存储中读取。
|
|
40
|
+
- `write`:持久化、索引、笔记或 memory 更新。
|
|
41
|
+
- `async`:事件或队列工作。
|
|
42
|
+
- `feedback`:循环、重试、细化或推理回路。
|
|
43
|
+
|
|
44
|
+
出现两种以上 flow 时,渲染器会自动输出 flow legend。连线标签保持简短,密集分组、标题标签和节点内部区域用 `waypoints` 绕开。
|
|
45
|
+
|
|
46
|
+
## 系统蓝图工作流
|
|
47
|
+
|
|
48
|
+
生成展示级系统蓝图、部署拓扑、集成关系图、Agent runtime 视图、memory/recall 图或 before/after 架构对比时,使用 architecture 路线。先抽取紧凑的系统模型,再绘图:
|
|
49
|
+
|
|
50
|
+
- 入口层:浏览器、移动端、CLI、webhook、调度器或 agent/session 入口。
|
|
51
|
+
- 应用层:网关、前端应用、后端服务、worker、编排层、运行时宿主和工具适配器。
|
|
52
|
+
- 数据层:数据库、对象存储、缓存、向量库、队列、事件总线、日志和分析存储。
|
|
53
|
+
- 外部依赖:身份提供方、AI 模型、支付/邮件服务、SaaS API、云服务、设计工具或运行时连接器。
|
|
54
|
+
- 身份与信任边界:认证、权限、PII 区域、租户边界、私有网络、密钥存储和策略检查。
|
|
55
|
+
- 关键流:请求/控制流、数据流、事件流、部署流、recall/memory flow 或 before/after 迁移流。
|
|
56
|
+
|
|
57
|
+
第一版蓝图保持可读:
|
|
58
|
+
|
|
59
|
+
- 展示 4-10 个主节点,把低价值文件、类或端点折叠成分组节点。
|
|
60
|
+
- 使用 1-3 个分组边界表达层级、信任区、运行环境或所有权域。
|
|
61
|
+
- 标题和副标题要足够清楚,文件脱离原会话也能分享。
|
|
62
|
+
- 用图例或最多 3 张摘要卡片说明 runtime、data、trust、risks、inputs 或 outcomes。
|
|
63
|
+
- 优先使用语义配色,不做装饰性堆砌;当连线或标签变密时拆成多张图。
|
|
64
|
+
|
|
65
|
+
面向 README 或干系人交付时,保留 JSON IR 和 HTML 作为可编辑事实源,再导出 SVG/PNG/JPG 产物:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
node skills/cc-technical-diagrams/scripts/export-diagram.mjs --input docs/architecture/system-blueprint.html --format svg
|
|
69
|
+
node skills/cc-technical-diagrams/scripts/export-diagram.mjs --input docs/architecture/system-blueprint.html --format png --scale 2
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
位图导出会优先使用本机已有的 Chromium 系浏览器。环境无法栅格化 SVG 时,交付生成的 HTML/SVG,并说明 PNG/JPG 导出需要本地浏览器。
|
|
73
|
+
|
|
74
|
+
## JSON IR
|
|
75
|
+
|
|
76
|
+
所有模式都需要:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"schema_version": 1,
|
|
81
|
+
"diagram_type": "architecture",
|
|
82
|
+
"meta": {
|
|
83
|
+
"title": "SaaS Architecture",
|
|
84
|
+
"subtitle": "Browser-ready infrastructure overview"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
各模式最小字段:
|
|
90
|
+
|
|
91
|
+
- `architecture`:`nodes`、`connections`;可选 `groups`、`legend`、`summary`。
|
|
92
|
+
- `workflow`:`lanes`、`nodes`、`edges`;可选 `summary`。
|
|
93
|
+
- `sequence`:`participants`、`messages`。
|
|
94
|
+
- `dataflow`:`stages`、`nodes`、`flows`。
|
|
95
|
+
- `lifecycle`:`lanes`、`states`、`transitions`。
|
|
96
|
+
|
|
97
|
+
ID 必须以字母开头,只使用字母、数字、`_` 和 `-`。节点标签保持短小;较长说明放到图外文字或摘要卡片。
|
|
98
|
+
|
|
99
|
+
## 流程工作流 IR
|
|
100
|
+
|
|
101
|
+
`workflow` 模式适合有明确顺序的流程:它更关注职责、决策、分支和回环,而不是精确的基础设施拓扑。用 `lanes` 表示参与者或系统,用 `nodes` 表示步骤,用 `edges` 表示流转结果。节点仍使用显式 `col` 控制阅读顺序,不依赖隐藏的自动布局引擎。
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"schema_version": 1,
|
|
106
|
+
"diagram_type": "workflow",
|
|
107
|
+
"meta": {
|
|
108
|
+
"title": "Procurement Approval",
|
|
109
|
+
"subtitle": "Request to payment"
|
|
110
|
+
},
|
|
111
|
+
"lanes": [
|
|
112
|
+
{ "id": "requester", "label": "Requester" },
|
|
113
|
+
{ "id": "system", "label": "System" },
|
|
114
|
+
{ "id": "manager", "label": "Manager" }
|
|
115
|
+
],
|
|
116
|
+
"nodes": [
|
|
117
|
+
{ "id": "start", "lane": "requester", "col": 0, "type": "start", "label": "Submit Request", "actor": "Employee" },
|
|
118
|
+
{ "id": "classify", "lane": "system", "col": 1, "type": "active", "label": "Classify Spend", "actor": "Policy Engine", "sublabel": "auto rules" },
|
|
119
|
+
{ "id": "review", "lane": "manager", "col": 2, "type": "decision", "label": "Approved?", "step": "A" },
|
|
120
|
+
{ "id": "pay", "lane": "system", "col": 3, "type": "success", "label": "Issue Payment" },
|
|
121
|
+
{ "id": "reject", "lane": "requester", "col": 3, "type": "failure", "label": "Return Request" }
|
|
122
|
+
],
|
|
123
|
+
"edges": [
|
|
124
|
+
{ "from": "start", "to": "classify", "label": "intake", "variant": "emphasis" },
|
|
125
|
+
{ "from": "classify", "to": "review", "label": "policy result" },
|
|
126
|
+
{ "from": "review", "to": "pay", "label": "yes", "variant": "emphasis" },
|
|
127
|
+
{ "from": "review", "to": "reject", "label": "no", "variant": "return", "waypoints": [[650, 396], [520, 396]] }
|
|
128
|
+
],
|
|
129
|
+
"summary": [
|
|
130
|
+
{ "title": "Inputs", "type": "active", "items": ["Purchase request", "Policy threshold"] },
|
|
131
|
+
{ "title": "Outcomes", "type": "success", "items": ["Payment issued", "Requester notified"] }
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Workflow 节点字段:
|
|
137
|
+
|
|
138
|
+
- `type`:流程语义使用 `start`、`active`、`waiting`、`decision`、`success`、`failure` 或 `external`。
|
|
139
|
+
- `actor`:可选,显示在节点上方的简短负责人或系统标签。
|
|
140
|
+
- `step`:可选,显示的步骤标记。未提供时,workflow 节点会自动获得顺序编号。
|
|
141
|
+
- `sublabel`、`width`、`height` 和 `yOffset`:可选,用于提升密集泳道中的可读性。
|
|
142
|
+
|
|
143
|
+
Workflow 连线字段:
|
|
144
|
+
|
|
145
|
+
- `variant`:主路径使用 `emphasis`,回环和返工使用 `return` 或 `dashed`,只有信任或策略检查才使用 `security`。
|
|
146
|
+
- `waypoints`:可选 `[x, y]` 点,用于异常路径、跨行连接或需要避开标签和节点的回环。
|
|
147
|
+
|
|
148
|
+
`summary` 用于简短记录前置条件、输入/输出或工具等流程元信息。长说明放在 SVG 外,避免节点标签不可读。
|
|
149
|
+
|
|
150
|
+
## Architecture IR
|
|
151
|
+
|
|
152
|
+
架构图使用显式坐标,因为系统图通常需要刻意安排位置、边界和信任区域:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"schema_version": 1,
|
|
157
|
+
"diagram_type": "architecture",
|
|
158
|
+
"meta": {
|
|
159
|
+
"title": "SaaS Architecture",
|
|
160
|
+
"subtitle": "Browser-ready infrastructure overview"
|
|
161
|
+
},
|
|
162
|
+
"visual": {
|
|
163
|
+
"style": "blueprint"
|
|
164
|
+
},
|
|
165
|
+
"groups": [
|
|
166
|
+
{ "id": "cloud", "label": "Cloud region", "type": "cloud", "x": 170, "y": 50, "width": 620, "height": 280 }
|
|
167
|
+
],
|
|
168
|
+
"nodes": [
|
|
169
|
+
{ "id": "web", "label": "Web App", "type": "frontend", "x": 48, "y": 160, "sublabel": "React" },
|
|
170
|
+
{ "id": "api", "label": "API Gateway", "type": "cloud", "x": 230, "y": 160, "group": "cloud" },
|
|
171
|
+
{ "id": "svc", "label": "App Service", "type": "backend", "x": 420, "y": 160, "group": "cloud" },
|
|
172
|
+
{ "id": "db", "label": "PostgreSQL", "type": "database", "x": 620, "y": 160, "group": "cloud" }
|
|
173
|
+
],
|
|
174
|
+
"connections": [
|
|
175
|
+
{ "from": "web", "to": "api", "label": "HTTPS", "variant": "emphasis" },
|
|
176
|
+
{ "from": "api", "to": "svc", "label": "REST" },
|
|
177
|
+
{ "from": "svc", "to": "db", "label": "SQL" }
|
|
178
|
+
],
|
|
179
|
+
"summary": [
|
|
180
|
+
{ "title": "Runtime", "type": "backend", "items": ["API and worker services", "Managed relational data"] }
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
节点和分组类型支持 `frontend`、`backend`、`database`、`cloud`、`security`、`messagebus`、`external`、`agent`、`model`、`memory`、`vectorstore`、`graphdb`、`tool`、`document`、`queue`、`browser`、`user` 和 `gateway`。连接支持 `default`、`emphasis`、`security`、`dashed` 和 `return`;architecture 连接还可以添加 `flow`。当连线需要绕开边界或标签时,添加 `waypoints`,格式为 `[[x, y], ...]`。
|
|
186
|
+
|
|
187
|
+
架构图默认根据节点类型生成图例;设置 `legend: false` 可关闭。自定义图例使用 `{ "label": "...", "type": "..." }`。`summary` 最多渲染 3 张图下摘要卡片,不进入 layout manifest,PNG QA 聚焦主图。
|
|
188
|
+
|
|
189
|
+
## 架构图布局规则
|
|
190
|
+
|
|
191
|
+
- 分组用于表达云区域、集群、信任边界或安全域,不作为装饰面板。
|
|
192
|
+
- 图例放在所有边界之外或下方;空间不足时增大坐标和画布,不压缩文字。
|
|
193
|
+
- 事件总线用 `messagebus` 节点,放在服务之间的空隙。
|
|
194
|
+
- 鉴权、令牌、策略或信任边界流使用 `security` 连接。
|
|
195
|
+
- 网关扇出到多个服务时,优先用 `waypoints` 控制路径,避免密集交叉线。
|
|
196
|
+
- 如果总览图需要多个密集簇,或必须缩小文字才放得下,应拆成总览图加细节图。
|
|
197
|
+
|
|
198
|
+
## 渲染命令
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
node skills/cc-technical-diagrams/scripts/tech-diagram-render.mjs \
|
|
202
|
+
--input diagram.json \
|
|
203
|
+
--output diagram.html \
|
|
204
|
+
--type architecture \
|
|
205
|
+
--manifest diagram.layout.json
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
生成的 HTML 是自包含文件,包含:
|
|
209
|
+
|
|
210
|
+
- Inline SVG。
|
|
211
|
+
- CSS 变量暗/亮主题。
|
|
212
|
+
- 浏览器内主题切换、SVG 下载、打印/PDF 控制。
|
|
213
|
+
- 可选 layout manifest,用于 PNG QA。
|
|
214
|
+
|
|
215
|
+
## QA 和修复
|
|
216
|
+
|
|
217
|
+
导出 PNG 后运行:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
node skills/cc-technical-diagrams/scripts/png-qa.mjs \
|
|
221
|
+
--png diagram.png \
|
|
222
|
+
--manifest diagram.layout.json \
|
|
223
|
+
--format markdown
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
修复应回到 JSON 源或渲染尺寸:
|
|
227
|
+
|
|
228
|
+
- 重叠:拆分密集分组,或增加节点/分组间距。
|
|
229
|
+
- 标签截断:对完整标签换行、保留文字或加宽节点。
|
|
230
|
+
- 连线穿过标签:减少边标签,添加 `waypoints`,或移动节点。
|
|
231
|
+
- 流向过密:拆成总览图和细节图。
|
|
232
|
+
- 边缘裁剪:增加输出空间或简化路径。
|
|
233
|
+
|
|
234
|
+
## 交付说明
|
|
235
|
+
|
|
236
|
+
交付 `.html` 源和用户要求的 SVG/PNG/JPG。说明使用的路线、源文件、导出文件、QA 结果,以及仍需人工确认的命名或领域假设。
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## 质量交付约定
|
|
240
|
+
|
|
241
|
+
标准序列/流程图优先本地 Mermaid;架构图需要手工编辑时复用 cc-drawio-studio,大型拓扑可选 Graphviz。无本地工具时使用兼容 JSON/HTML,不自动安装或下载。HTML 预览、独立 SVG 和 PNG 使用同源主题样式;导出指定 `--theme light|dark`。PNG 导出等待字体并测量文字,使用 `--manifest source.layout.json --output-manifest image.actual.json` 写出缩放后的像素坐标。QA 必须读取 actual manifest,不能把估算坐标直接用于 2x PNG。
|
|
242
|
+
|
|
243
|
+
扩展 manifest 保留 canvas/boxes/connectors,增加 coordinateSpace、scale、measurement、groups、labels 与 issues。分组包含节点不算节点重叠。新增 node-text-overflow、label-content-loss、label-out-of-bounds、edge-label-collision、text-overlap 与 degenerate-connector 检查。旧 manifest 保留估算检测。显式尺寸或 waypoints 不满足约束时定位问题,不删字、不改关系、不静默移动。提高导出分辨率不能修复源级溢出。最终 PNG 必须视觉审阅;记录工具可用性、版本、主题、比例、修复轮次和残留问题,未解决时标记部分验收。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# PNG 质检与自动修复
|
|
2
|
+
|
|
3
|
+
## 循环
|
|
4
|
+
|
|
5
|
+
1. 从可编辑源文件或图像工作流导出 PNG。
|
|
6
|
+
2. 目视检查 PNG。
|
|
7
|
+
3. 有 manifest 时,带 manifest 运行 `png-qa.mjs`。
|
|
8
|
+
4. 将每个问题转化为源文件、提示词或布局变更。
|
|
9
|
+
5. 默认重复 2 轮;用户要求更多修复尝试时最多 5 轮。
|
|
10
|
+
|
|
11
|
+
## 问题映射
|
|
12
|
+
|
|
13
|
+
| 问题 | 修复方式 |
|
|
14
|
+
| ----- | ------ |
|
|
15
|
+
| `blank-image` | 检查导出目标、渲染器错误、隐藏图层、透明填充或图像生成失败。 |
|
|
16
|
+
| `edge-clipping` | 增加画布留白、扩大 SVG viewBox、降低主体缩放,或导出更大的画幅。 |
|
|
17
|
+
| `box-overlap` | 增加间距、重排为网格/图层、拆分簇,或对完整标签换行。 |
|
|
18
|
+
| `box-out-of-bounds` | 将节点移入画布,或增大画布尺寸。 |
|
|
19
|
+
| `label-overflow` | 文本换行、加宽框体、保留完整文案,并同时增大字体/容器尺寸。 |
|
|
20
|
+
| `connector-through-label` | 在框体周围添加 waypoints、移动标签,或使用正交布线。 |
|
|
21
|
+
| `connector-stacking` | 偏移平行连接线,或用带标签的分组路径替代重复线条。 |
|
|
22
|
+
| `manifest-canvas-mismatch` | 根据最终导出尺寸重新生成 manifest。 |
|
|
23
|
+
|
|
24
|
+
## Manifest 质量
|
|
25
|
+
|
|
26
|
+
只有当 manifest 坐标与导出的 PNG 匹配时,辅助脚本才能判断重叠和连接线问题。若 manifest 过期,先修复 manifest,再重新运行 QA。
|
|
27
|
+
|
|
28
|
+
## 报告要求
|
|
29
|
+
|
|
30
|
+
报告 QA 轮数和最终问题列表。若由于源渲染器无法暴露布局坐标而仍有遗留问题,说明哪些检查只能基于图像,哪些需要人工视觉复核。
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## 质量交付约定
|
|
34
|
+
|
|
35
|
+
标准序列/流程图优先本地 Mermaid;架构图需要手工编辑时复用 cc-drawio-studio,大型拓扑可选 Graphviz。无本地工具时使用兼容 JSON/HTML,不自动安装或下载。HTML 预览、独立 SVG 和 PNG 使用同源主题样式;导出指定 `--theme light|dark`。PNG 导出等待字体并测量文字,使用 `--manifest source.layout.json --output-manifest image.actual.json` 写出缩放后的像素坐标。QA 必须读取 actual manifest,不能把估算坐标直接用于 2x PNG。
|
|
36
|
+
|
|
37
|
+
扩展 manifest 保留 canvas/boxes/connectors,增加 coordinateSpace、scale、measurement、groups、labels 与 issues。分组包含节点不算节点重叠。新增 node-text-overflow、label-content-loss、label-out-of-bounds、edge-label-collision、text-overlap 与 degenerate-connector 检查。旧 manifest 保留估算检测。显式尺寸或 waypoints 不满足约束时定位问题,不删字、不改关系、不静默移动。提高导出分辨率不能修复源级溢出。最终 PNG 必须视觉审阅;记录工具可用性、版本、主题、比例、修复轮次和残留问题,未解决时标记部分验收。
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
export function findBrowser() {
|
|
9
|
+
const candidates = [process.env.FEC_BROWSER_PATH,
|
|
10
|
+
"C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
|
|
11
|
+
"C:/Program Files/Microsoft/Edge/Application/msedge.exe",
|
|
12
|
+
"C:/Program Files/Google/Chrome/Application/chrome.exe",
|
|
13
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
14
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
15
|
+
"google-chrome", "chromium", "chromium-browser", "msedge", "brave-browser", "vivaldi"];
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
if (!candidate) continue;
|
|
18
|
+
if (path.isAbsolute(candidate) ? fs.existsSync(candidate) : spawnSync(candidate, ["--version"], { timeout: 5000, windowsHide: true, stdio: "ignore" }).status === 0) return candidate;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @typedef {{evaluate:(expression:string)=>Promise<unknown>,screenshot:(width:number,height:number,scale?:number)=>Promise<Buffer>}} DiagramSession */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Export failures must stay within the wording callers match on, so CDP details are wrapped here.
|
|
27
|
+
* @param {string} detail @returns {Error}
|
|
28
|
+
*/
|
|
29
|
+
const exportFailure = (detail) => new Error(`Browser raster export failed: ${detail}`);
|
|
30
|
+
|
|
31
|
+
/** @param {()=>string} read @param {number} deadline @returns {Promise<number>} */
|
|
32
|
+
export async function waitForDebuggingPort(read, deadline) {
|
|
33
|
+
while (Date.now() < deadline) {
|
|
34
|
+
try {
|
|
35
|
+
const value = read().split("\n")[0].trim();
|
|
36
|
+
const port = Number(value);
|
|
37
|
+
if (/^\d+$/.test(value) && Number.isInteger(port) && port > 0 && port <= 65535) return port;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
40
|
+
if (!["EBUSY", "ENOENT", "EPERM"].includes(code)) throw exportFailure(error instanceof Error ? error.message : String(error));
|
|
41
|
+
}
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(50, Math.max(0, deadline - Date.now()))));
|
|
43
|
+
}
|
|
44
|
+
throw exportFailure("browser debugging port did not become readable before the initialization deadline.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The target URL can be advertised before its execution context has finished navigating.
|
|
49
|
+
* Retry only that initialization window; action failures must propagate without replaying exports.
|
|
50
|
+
* @param {DiagramSession['evaluate']} evaluate @param {string} url @param {number} deadline
|
|
51
|
+
*/
|
|
52
|
+
export async function waitForDiagramReady(evaluate, url, deadline) {
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
try {
|
|
55
|
+
const ready = await evaluate(`(async()=>{if(location.href!==${JSON.stringify(url)})return false;if(document.readyState!=="complete")await new Promise(r=>window.addEventListener("load",r,{once:true}));await document.fonts.ready;await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));return true;})()`);
|
|
56
|
+
if (ready === true) return;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!(error instanceof Error) || !/^Browser raster export failed: (?:Execution context was destroyed|Cannot find default execution context)\.?$/.test(error.message)) throw error;
|
|
59
|
+
}
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(50, Math.max(0, deadline - Date.now()))));
|
|
61
|
+
}
|
|
62
|
+
throw exportFailure("diagram page did not become ready before the initialization deadline.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Chromium keeps profile files locked until its whole process tree is gone, so cleanup waits for
|
|
67
|
+
* the process instead of sleeping a fixed amount. @param {ReturnType<typeof spawn>} child @param {number} ms @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
const waitForExit = async (child, ms) => {
|
|
70
|
+
if (child.exitCode !== null) return;
|
|
71
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
72
|
+
let timer;
|
|
73
|
+
await new Promise((resolve) => {
|
|
74
|
+
const finish = () => { if (timer) clearTimeout(timer); child.off("exit", finish); resolve(undefined); };
|
|
75
|
+
timer = setTimeout(finish, ms);
|
|
76
|
+
child.once("exit", finish);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** @template T @param {string} html @param {{theme:'light'|'dark',timeoutMs:number}} options @param {(session:DiagramSession)=>Promise<T>} action @returns {Promise<T>} */
|
|
81
|
+
export async function withDiagramPage(html, options, action) {
|
|
82
|
+
const browser = findBrowser();
|
|
83
|
+
if (!browser) throw new Error("PNG/JPG export requires a local Chromium browser. Export SVG instead, or install a Chromium browser and rerun this command.");
|
|
84
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fec-diagram-browser-"));
|
|
85
|
+
const page = path.join(dir, "diagram.html"), profile = path.join(dir, "profile");
|
|
86
|
+
fs.writeFileSync(page, html);
|
|
87
|
+
const child = spawn(browser, ["--headless=new", "--disable-gpu", "--no-first-run", "--no-default-browser-check", "--hide-scrollbars", "--remote-debugging-port=0", "--remote-allow-origins=http://localhost", `--user-data-dir=${profile}`, pathToFileURL(page).href], { windowsHide: true, stdio: "ignore" });
|
|
88
|
+
let childError = "";
|
|
89
|
+
child.on("error", (error) => { childError = error.message; });
|
|
90
|
+
/** @type {WebSocket | undefined} */
|
|
91
|
+
let socket;
|
|
92
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
93
|
+
let timer;
|
|
94
|
+
try {
|
|
95
|
+
const work = async () => {
|
|
96
|
+
const deadline = Date.now() + Math.min(15000, options.timeoutMs);
|
|
97
|
+
const portFile = path.join(profile, "DevToolsActivePort");
|
|
98
|
+
while (!fs.existsSync(portFile) && Date.now() < deadline && (child.exitCode === null || child.exitCode === 0) && !childError) await new Promise((resolve) => setTimeout(resolve, 50));
|
|
99
|
+
if (!fs.existsSync(portFile)) throw exportFailure(`browser did not start a debugging session${childError ? ` (${childError})` : ""}.`);
|
|
100
|
+
const port = await waitForDebuggingPort(() => fs.readFileSync(portFile, "utf8"), deadline);
|
|
101
|
+
// Select the intended target first; the readiness handshake below also waits for its context.
|
|
102
|
+
/** @type {{webSocketDebuggerUrl?:string}|undefined} */
|
|
103
|
+
let target;
|
|
104
|
+
while (Date.now() < deadline) {
|
|
105
|
+
const pages = await (await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(Math.min(5000, options.timeoutMs)) })).json();
|
|
106
|
+
target = pages.find(/** @param {{type:string,url:string}} p */ (p) => p.type === "page" && p.url === pathToFileURL(page).href);
|
|
107
|
+
if (target) break;
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
109
|
+
}
|
|
110
|
+
if (!target?.webSocketDebuggerUrl) throw exportFailure("browser did not create a diagram page.");
|
|
111
|
+
socket = new WebSocket(target.webSocketDebuggerUrl);
|
|
112
|
+
await new Promise((resolve, reject) => { socket?.addEventListener("open", resolve, { once: true }); socket?.addEventListener("error", reject, { once: true }); });
|
|
113
|
+
/** @type {Map<number,{resolve:(value:Record<string,unknown>)=>void,reject:(reason:Error)=>void}>} */
|
|
114
|
+
const pending = new Map();
|
|
115
|
+
socket.addEventListener("close", () => { for (const entry of pending.values()) entry.reject(exportFailure("session closed before export completed.")); pending.clear(); });
|
|
116
|
+
let serial = 0;
|
|
117
|
+
socket.addEventListener("message", (event) => {
|
|
118
|
+
const message = JSON.parse(String(event.data));
|
|
119
|
+
const entry = pending.get(message.id);
|
|
120
|
+
if (!entry) return;
|
|
121
|
+
pending.delete(message.id);
|
|
122
|
+
if (message.error) entry.reject(exportFailure(message.error.message)); else entry.resolve(message.result ?? {});
|
|
123
|
+
});
|
|
124
|
+
/** @param {string} method @param {Record<string,unknown>} [params] @returns {Promise<Record<string,unknown>>} */
|
|
125
|
+
const call = (method, params = {}) => new Promise((resolve, reject) => { const id = ++serial; pending.set(id, { resolve, reject }); socket?.send(JSON.stringify({ id, method, params })); });
|
|
126
|
+
/** @param {string} expression */
|
|
127
|
+
const evaluate = async (expression) => {
|
|
128
|
+
const result = await call("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true });
|
|
129
|
+
if (result.exceptionDetails) throw new Error(`Diagram evaluation failed: ${JSON.stringify(result.exceptionDetails)}`);
|
|
130
|
+
return /** @type {{value?:unknown}} */ (result.result).value;
|
|
131
|
+
};
|
|
132
|
+
/** @type {DiagramSession} */
|
|
133
|
+
const session = { evaluate, screenshot: async (width, height, scale = 1) => {
|
|
134
|
+
await call("Emulation.setDeviceMetricsOverride", { width: Math.ceil(width), height: Math.ceil(height), deviceScaleFactor: scale, mobile: false });
|
|
135
|
+
const result = await call("Page.captureScreenshot", { format: "png", captureBeyondViewport: true, clip: { x: 0, y: 0, width, height, scale: 1 } });
|
|
136
|
+
return Buffer.from(String(result.data), "base64");
|
|
137
|
+
} };
|
|
138
|
+
await waitForDiagramReady(evaluate, pathToFileURL(page).href, deadline);
|
|
139
|
+
return await action(session);
|
|
140
|
+
};
|
|
141
|
+
return await Promise.race([work(), new Promise(/** @param {(reason:Error)=>void} reject */ (_, reject) => { timer = setTimeout(() => reject(exportFailure("export timed out.")), options.timeoutMs); })]);
|
|
142
|
+
} finally {
|
|
143
|
+
if (timer) clearTimeout(timer);
|
|
144
|
+
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ id: 999999, method: "Browser.close" }));
|
|
145
|
+
// Browser.close is graceful, so wait for the process rather than guessing with a fixed sleep:
|
|
146
|
+
// Chromium's child processes keep profile files locked until the tree is gone.
|
|
147
|
+
await waitForExit(child, 2000);
|
|
148
|
+
socket?.close();
|
|
149
|
+
if (child.exitCode === null) child.kill();
|
|
150
|
+
// The Chromium profile is owned by this invocation only. Windows may release locks after exit.
|
|
151
|
+
try { fs.rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }); } catch { /* A locked temporary profile can be removed by OS cleanup. */ }
|
|
152
|
+
}
|
|
153
|
+
}
|