@bovinphang/craft-common 0.0.0-stage → 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/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,27 @@
|
|
|
1
|
+
# Data Residency
|
|
2
|
+
|
|
3
|
+
Use this checklist when a diagram contains private architecture, unreleased product details, customer data or regulated information.
|
|
4
|
+
|
|
5
|
+
## Local First
|
|
6
|
+
|
|
7
|
+
- A `.drawio` file written in the workspace is the safest default artifact because it can be inspected, versioned and opened locally.
|
|
8
|
+
- draw.io Desktop export runs on the local machine when the CLI is installed. If the CLI is missing, keep the source file and offer a URL fallback instead of sending the diagram to a cloud converter.
|
|
9
|
+
- `diagram-lint.mjs`, `diagram-url.mjs`, `layout-graph.mjs`, `png-embed-fix.mjs` and code scanners run locally. Graphviz is optional and local when installed.
|
|
10
|
+
|
|
11
|
+
## URL Modes
|
|
12
|
+
|
|
13
|
+
- Viewer/editor `#R` URLs and `#create=` URLs place compressed diagram content in the URL fragment.
|
|
14
|
+
- URL fragments are not sent to ordinary HTTP servers as request paths, but the browser still loads application code from the chosen diagrams.net host.
|
|
15
|
+
- Very large diagrams can exceed practical browser URL limits. Fall back to a local `.drawio` file when the URL becomes unwieldy.
|
|
16
|
+
- On Windows, open long diagram URLs through a `.url` shortcut file or use the JSON `windowsShortcut` output from `diagram-url.mjs`; passing the URL directly through `cmd start` can lose the fragment.
|
|
17
|
+
|
|
18
|
+
## External Requests
|
|
19
|
+
|
|
20
|
+
- Brand CDN symbols require network access when rendered unless embedded as data URIs.
|
|
21
|
+
- diagrams.net web editor/viewer loads editor code and assets from the configured host.
|
|
22
|
+
- A self-hosted diagrams.net base URL can be passed with `--base-url` when the environment requires a controlled endpoint.
|
|
23
|
+
- Existing draw.io MCP App or Tool servers may be useful as preview surfaces, but they are optional and governed by the user's configured host and model provider.
|
|
24
|
+
|
|
25
|
+
## Handoff Rule
|
|
26
|
+
|
|
27
|
+
For sensitive diagrams, deliver the local `.drawio` source plus lint results first. Add PNG/SVG/PDF or URL outputs only when the user explicitly needs them and the data boundary is acceptable.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Diagram Patterns
|
|
2
|
+
|
|
3
|
+
Use these conventions when a user asks for a named technical diagram.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
- Group nodes by tier: client, edge, service, data, external.
|
|
8
|
+
- Use swimlanes or grouped containers when boundaries matter.
|
|
9
|
+
- For layered block architecture, prefer horizontal tiers with left-side tier labels, content-driven containers, and optional cross-cutting sidebars for security, monitoring or governance.
|
|
10
|
+
- Put bus or event-stream nodes near the center of publisher/subscriber rows.
|
|
11
|
+
- Use cylinders for databases, rounded rectangles for services, dashed outlines for external systems.
|
|
12
|
+
- Use arrows only when data flow or dependency direction matters; use spatial grouping for pure capability stacks.
|
|
13
|
+
|
|
14
|
+
## ERD
|
|
15
|
+
|
|
16
|
+
- Model tables as containers with rows for columns.
|
|
17
|
+
- Mark primary keys and foreign keys in labels.
|
|
18
|
+
- Use dashed relationship edges when cardinality is not fully known.
|
|
19
|
+
- Prefer top-to-bottom layout for schemas with multiple relation clusters.
|
|
20
|
+
- Use stable table widths and row heights so long column names wrap instead of resizing neighboring tables.
|
|
21
|
+
|
|
22
|
+
## UML Class
|
|
23
|
+
|
|
24
|
+
- Use a class box split into name, fields and methods.
|
|
25
|
+
- Use hollow triangle arrows for inheritance, dashed hollow triangle for interface implementation.
|
|
26
|
+
- Keep interfaces or abstract bases above concrete classes.
|
|
27
|
+
- Keep associations orthogonal and label multiplicity near the edge segment it describes.
|
|
28
|
+
|
|
29
|
+
## Sequence
|
|
30
|
+
|
|
31
|
+
- Lay participants left-to-right and time top-to-bottom.
|
|
32
|
+
- Keep request, response and async message styles visually distinct.
|
|
33
|
+
- Add activation boxes only when they clarify nested work.
|
|
34
|
+
- Leave enough horizontal spacing for message labels; if labels collide, wrap text or split the interaction into phases.
|
|
35
|
+
|
|
36
|
+
## ML / Deep Learning
|
|
37
|
+
|
|
38
|
+
- Show tensor shape on a second line when it matters.
|
|
39
|
+
- Group encoder, decoder, embedding, attention and output stages.
|
|
40
|
+
- Route skip connections around the primary flow.
|
|
41
|
+
|
|
42
|
+
## Flowchart
|
|
43
|
+
|
|
44
|
+
- Use ovals for start/end, rectangles for process, diamonds for decisions.
|
|
45
|
+
- Label decision branches.
|
|
46
|
+
- Merge branches back to a central path when possible.
|
|
47
|
+
- Keep the primary path vertical. Route alternate or error branches sideways and back to the main path with orthogonal edges.
|
|
48
|
+
- For editable draw.io flowcharts, follow [flowchart-quality.md](flowchart-quality.md) before final delivery.
|
|
49
|
+
|
|
50
|
+
## Mindmap
|
|
51
|
+
|
|
52
|
+
- Put the central concept in the middle and distribute first-level branches across quadrants.
|
|
53
|
+
- Use curved edges without arrowheads and keep each branch color consistent from parent to leaves.
|
|
54
|
+
- Fan leaves away from the center; do not let branches cross through the central label.
|
|
55
|
+
|
|
56
|
+
## Network Topology
|
|
57
|
+
|
|
58
|
+
- Use hierarchical rows such as internet, edge, switching/routing and devices.
|
|
59
|
+
- Distinguish wired and wireless links visually, usually solid for wired and dashed for wireless.
|
|
60
|
+
- Keep endpoint devices aligned in rows or columns so connection paths remain scannable.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Flowchart Quality
|
|
2
|
+
|
|
3
|
+
Use this reference when a draw.io / diagrams.net deliverable is a process, workflow, approval path, release path, incident response path, onboarding flow or other flowchart that must remain readable after opening in diagrams.net.
|
|
4
|
+
|
|
5
|
+
## Readability Contract
|
|
6
|
+
|
|
7
|
+
- Keep manually authored flowcharts narrow. Prefer `pageWidth` between 600 and 800; do not exceed 800 unless the diagram is intentionally a wide cross-functional table.
|
|
8
|
+
- Set `dx` and `dy` close to the page dimensions so diagrams.net does not initially zoom the drawing until labels become tiny.
|
|
9
|
+
- Put the primary path top-to-bottom. Use side branches only for alternatives, failures, retries and exceptions, then return them to the main path with orthogonal connectors.
|
|
10
|
+
- Split more than five dense stages into multiple diagrams or pages instead of making one oversized canvas.
|
|
11
|
+
|
|
12
|
+
## Stage Grouping
|
|
13
|
+
|
|
14
|
+
- Use `swimlane` containers for real stages, actors, phases or bounded groups; avoid loose rectangles when a title and containment relationship matter.
|
|
15
|
+
- Keep stage containers on a 10px grid, normally with a 20px outer page margin and 30-40px vertical breathing room between stages.
|
|
16
|
+
- Give container headers a readable hierarchy: stage labels around 14-16px, bold, with a clear border color.
|
|
17
|
+
- Set stage containers to `connectable="0"` and `collapsible=0` so connectors target intentional nodes and the diagram does not gain collapse controls.
|
|
18
|
+
- Size containers to fit their children with 15px or more bottom padding. A container with large one-sided blank space usually needs a layout pass.
|
|
19
|
+
|
|
20
|
+
## Labels
|
|
21
|
+
|
|
22
|
+
- Keep node text centered for process boxes unless a table-like artifact genuinely needs aligned rows.
|
|
23
|
+
- Use `whiteSpace=wrap;html=1` on process nodes, and keep normal body text at 12px or larger.
|
|
24
|
+
- Put the node title first, visually stronger than the body. Use concise labels and move long implementation detail into notes or metadata.
|
|
25
|
+
- Use the same language as the user's request, and escape XML attribute content before saving.
|
|
26
|
+
|
|
27
|
+
## Connectors
|
|
28
|
+
|
|
29
|
+
- Use `edgeStyle=orthogonalEdgeStyle` for normal flowchart connectors.
|
|
30
|
+
- Make the main stage-to-stage path visually stronger than secondary edges, for example with a thicker stroke and a consistent accent color.
|
|
31
|
+
- Label decision branches and exception paths close to the edge segment they describe.
|
|
32
|
+
- For fan-out from one node to several peers, use explicit `entryX`, `entryY`, `exitX` and `exitY` anchors so branches do not cross.
|
|
33
|
+
- Route retry, rollback and loop connectors around the left or right margin with explicit waypoints instead of diagonal shortcuts through content.
|
|
34
|
+
|
|
35
|
+
## Visual System
|
|
36
|
+
|
|
37
|
+
- Use a small palette with one color per stage type or actor group. Keep process node fills neutral and let borders or headers carry the stage color.
|
|
38
|
+
- Reserve warning, failure, pending and future states for exceptional nodes; do not use status colors as decoration.
|
|
39
|
+
- Keep container corners square when the stage boundary is structural; rounded corners are fine for ordinary process nodes.
|
|
40
|
+
|
|
41
|
+
## Final Review
|
|
42
|
+
|
|
43
|
+
Before delivery, run:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node skills/cc-drawio-studio/scripts/diagram-lint.mjs diagram.drawio --format markdown --strict
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then manually check that the opened diagram has no crossed main-path connectors, no clipped labels, no collapsed-looking stages, and no unreadably small text at the default diagrams.net zoom.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Layout And Export
|
|
2
|
+
|
|
3
|
+
## Auto Layout
|
|
4
|
+
|
|
5
|
+
Use `layout-graph.mjs` for dependency graphs, code structure maps and diagrams with more than about 15 nodes. For smaller diagrams, manual XML is acceptable when the coordinates, containers and edge anchors can be reviewed directly.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node skills/cc-drawio-studio/scripts/layout-graph.mjs graph.json --output diagram.drawio --manifest layout.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Input graph:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"direction": "TB",
|
|
16
|
+
"nodes": [
|
|
17
|
+
{ "id": "client", "label": "Web Client", "group": "Client" },
|
|
18
|
+
{ "id": "api", "label": "API", "group": "Service" }
|
|
19
|
+
],
|
|
20
|
+
"edges": [
|
|
21
|
+
{ "source": "client", "target": "api", "label": "HTTPS" }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Graphviz `dot` is optional. If it is missing, hand-place the diagram or deliver XML plus a diagrams.net URL.
|
|
27
|
+
|
|
28
|
+
## Manual Layout
|
|
29
|
+
|
|
30
|
+
- Align visible vertex `x` and `y` values to the 10px grid.
|
|
31
|
+
- Keep at least 60px outer page margin and enough space that connectors do not cross labels.
|
|
32
|
+
- Use equal sizes and equal gaps for sibling nodes in the same row or column unless content requires a different size.
|
|
33
|
+
- Encode explicit label breaks as `
`; do not put literal `\n` in draw.io `value` attributes.
|
|
34
|
+
- Include `whiteSpace=wrap;html=1` on labels and use `fontSize=14` by default, with 12 as the smallest readable exception.
|
|
35
|
+
- Size containers, sidebars and swimlanes to their content plus balanced padding. If a final row is incomplete, center it or split the group instead of leaving a large empty tail.
|
|
36
|
+
|
|
37
|
+
## Export
|
|
38
|
+
|
|
39
|
+
Preview:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node skills/cc-drawio-studio/scripts/drawio-export.mjs diagram.drawio --format png --output diagram.png --width 2000
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Final editable PNG:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
node skills/cc-drawio-studio/scripts/drawio-export.mjs diagram.drawio --format png --output diagram.drawio.png --scale 2 --embed
|
|
49
|
+
node skills/cc-drawio-studio/scripts/png-embed-fix.mjs diagram.drawio.png
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Preview export can be checked without draw.io side effects:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
node skills/cc-drawio-studio/scripts/drawio-export.mjs diagram.drawio --format png --output diagram.png --scale 2 --width 2000 --dry-run --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Fallback URL:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node skills/cc-drawio-studio/scripts/diagram-url.mjs diagram.drawio --edit
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Create/import URL for XML, Mermaid or CSV:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node skills/cc-drawio-studio/scripts/diagram-url.mjs diagram.drawio --create --type xml --json
|
|
68
|
+
node skills/cc-drawio-studio/scripts/diagram-url.mjs flow.mmd --create --type mermaid
|
|
69
|
+
node skills/cc-drawio-studio/scripts/diagram-url.mjs org.csv --create --type csv --lightbox
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `--base-url` for a self-hosted diagrams.net endpoint. Use `--shortcut` or the JSON `windowsShortcut` field when opening the URL through Windows shell tooling.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## draw.io CLI Missing
|
|
4
|
+
|
|
5
|
+
Generate the `.drawio` file and use `diagram-url.mjs --edit` to open diagrams.net in the browser.
|
|
6
|
+
|
|
7
|
+
## Graphviz Missing
|
|
8
|
+
|
|
9
|
+
`layout-graph.mjs` exits with a clear Graphviz message. Keep the graph JSON and either install Graphviz or hand-place a smaller diagram.
|
|
10
|
+
|
|
11
|
+
## Blank Vendor Shape
|
|
12
|
+
|
|
13
|
+
Run `shape-query.mjs` and use the returned style string. A guessed `shape=mxgraph.*` often renders as a generic box.
|
|
14
|
+
|
|
15
|
+
## Brand Icon Does Not Render
|
|
16
|
+
|
|
17
|
+
CDN-referenced symbols require network access during render. Use `brand-symbols.mjs --embed` for a self-contained data URI when portability matters.
|
|
18
|
+
|
|
19
|
+
## Embedded PNG Looks Corrupt
|
|
20
|
+
|
|
21
|
+
Run `png-embed-fix.mjs` after final `drawio -e` PNG export. The command is idempotent.
|
|
22
|
+
|
|
23
|
+
## Browser URL Opens Blank On Windows
|
|
24
|
+
|
|
25
|
+
Use `diagram-url.mjs --json` and open the emitted `windowsShortcut` content as a `.url` file, or run `diagram-url.mjs --shortcut`. This preserves the `#R` or `#create=` fragment that carries the compressed diagram.
|
|
26
|
+
|
|
27
|
+
## Mermaid Or CSV URL Fails
|
|
28
|
+
|
|
29
|
+
Mermaid and CSV must use `diagram-url.mjs --create --type mermaid|csv`. The older viewer/editor `#R` mode is XML-only.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# XML And Mermaid
|
|
2
|
+
|
|
3
|
+
Use this reference to choose the lightest editable diagram source that still meets the user's maintenance and fidelity needs.
|
|
4
|
+
|
|
5
|
+
## Route Choice
|
|
6
|
+
|
|
7
|
+
- Use Mermaid for common flowcharts, sequence diagrams, class diagrams, state diagrams, ERDs, timelines, mind maps and other text-first diagrams that will live in Markdown.
|
|
8
|
+
- Use draw.io XML when the diagram needs exact shapes, vendor symbols, nested containers, layers, tags, metadata, custom colors or a durable `.drawio` source.
|
|
9
|
+
- Use CSV only as a short import path for tabular structures such as org charts or simple relationship maps; save the resulting editable source when possible.
|
|
10
|
+
- Use URL creation for preview and handoff, not as the only artifact for important diagrams.
|
|
11
|
+
|
|
12
|
+
## XML Structure
|
|
13
|
+
|
|
14
|
+
- Include root cells `id="0"` and `id="1"` in every diagram.
|
|
15
|
+
- Give every visible vertex and edge a stable unique id.
|
|
16
|
+
- Put child nodes inside their visual container by setting `parent` to the container id and using coordinates relative to that container.
|
|
17
|
+
- Put edges that cross container boundaries at `parent="1"` so they are not clipped by a lane or group.
|
|
18
|
+
- Every edge must contain `<mxGeometry relative="1" as="geometry"/>`; do not use self-closing edge cells.
|
|
19
|
+
- Use `
` for forced line breaks in `value` attributes. Literal `\n` renders as text in draw.io and should be treated as a quality issue.
|
|
20
|
+
- Keep manually placed vertices on a 10px grid, within `pageWidth` / `pageHeight`, and with stable dimensions so hover, labels and exports do not shift the layout.
|
|
21
|
+
- For manually authored flowcharts, keep `pageWidth` at or below 800 unless the diagram is deliberately a wide matrix or cross-functional table.
|
|
22
|
+
|
|
23
|
+
## Containers And Process Layout
|
|
24
|
+
|
|
25
|
+
- Use titled swimlanes for actor, tier or bounded-context groupings.
|
|
26
|
+
- Use nested swimlanes for real hierarchy such as region, VPC, availability zone and workload.
|
|
27
|
+
- Use a table layout for cross-functional flows where both actor rows and phase columns matter.
|
|
28
|
+
- Use plain groups only when a visual boundary is unnecessary and the group should not capture connectors.
|
|
29
|
+
|
|
30
|
+
## Layers, Tags And Metadata
|
|
31
|
+
|
|
32
|
+
- Use layers for major visibility sets, such as logical view, physical view, annotations or migration phases.
|
|
33
|
+
- Use tags for cross-cutting filters such as critical, deprecated, external, v2 or owner names.
|
|
34
|
+
- Use object metadata and placeholders when a shape represents a record with fields such as owner, status, IP, service tier or version.
|
|
35
|
+
- Keep visible labels concise; put secondary facts in metadata or notes when the diagram would otherwise become hard to scan.
|
|
36
|
+
|
|
37
|
+
## Labels And Dark Mode
|
|
38
|
+
|
|
39
|
+
- Match diagram label language to the user's language.
|
|
40
|
+
- Add `html=1` whenever labels contain HTML markup or escaped `<br>` formatting.
|
|
41
|
+
- Include `whiteSpace=wrap;html=1` on normal shape labels so long text wraps consistently.
|
|
42
|
+
- Escape XML attribute values: `&`, `<`, `>`, and `"` must be entities.
|
|
43
|
+
- Prefer adaptive colors and simple palettes; specify dark-mode overrides only when automatic inversion makes labels or shapes unclear.
|
|
44
|
+
|
|
45
|
+
## Mermaid Import Notes
|
|
46
|
+
|
|
47
|
+
- Pick the Mermaid header deliberately: `flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram-v2`, `erDiagram`, `timeline`, `mindmap`, `gantt`, `C4Context` and similar headers select different parsers.
|
|
48
|
+
- Quote labels that include punctuation, spaces, parentheses or non-ASCII text.
|
|
49
|
+
- Keep node ids simple and put display text in labels.
|
|
50
|
+
- For large, highly styled or vendor-specific diagrams, move to XML before final delivery.
|
|
51
|
+
|
|
52
|
+
## XML Well-Formedness
|
|
53
|
+
|
|
54
|
+
- Do not include XML comments.
|
|
55
|
+
- Do not leave raw ampersands or angle brackets inside attribute values.
|
|
56
|
+
- Do not use duplicate ids.
|
|
57
|
+
- Do not leave edges pointing to missing source or target cells.
|
|
58
|
+
- Run `diagram-lint.mjs` before final handoff. Use `--strict` when the exported artifact is a final deliverable so warnings such as overlaps, literal line breaks, missing wrapping styles, tiny fonts, grid drift and page overflow block the handoff.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseArgs, skillDir } from "./studio-core.mjs";
|
|
5
|
+
|
|
6
|
+
const MANIFEST = path.join(skillDir, "data", "brand-icons.json");
|
|
7
|
+
const STYLE_PREFIX = "shape=image;html=1;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=";
|
|
8
|
+
const SIMPLE = new Map([
|
|
9
|
+
["qdrant", "qdrant"], ["milvus", "milvus"], ["supabase", "supabase"], ["redis", "redis"],
|
|
10
|
+
["postgresql", "postgresql"], ["postgres", "postgresql"], ["mongodb", "mongodb"],
|
|
11
|
+
["elasticsearch", "elasticsearch"], ["neo4j", "neo4j"], ["kafka", "apachekafka"],
|
|
12
|
+
["clickhouse", "clickhouse"], ["duckdb", "duckdb"], ["mysql", "mysql"], ["sqlite", "sqlite"],
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function loadFamilies() {
|
|
16
|
+
const manifest = JSON.parse(fs.readFileSync(MANIFEST, "utf8"));
|
|
17
|
+
const families = new Map();
|
|
18
|
+
for (const icon of manifest.icons) {
|
|
19
|
+
const base = icon.replace(/-(color|text)$/, "");
|
|
20
|
+
if (!families.has(base)) families.set(base, new Set());
|
|
21
|
+
families.get(base).add(icon);
|
|
22
|
+
}
|
|
23
|
+
return { cdn: manifest.cdn, families };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function queryBrand(query, options = {}) {
|
|
27
|
+
const { cdn, families } = loadFamilies();
|
|
28
|
+
const hits = rankBrands(families, query).slice(0, Number(options.limit ?? 8));
|
|
29
|
+
const size = Number(options.size ?? 48);
|
|
30
|
+
const variant = options.variant ?? "color";
|
|
31
|
+
const results = [];
|
|
32
|
+
for (const brand of hits) {
|
|
33
|
+
const file = pickVariant(brand, families.get(brand), variant);
|
|
34
|
+
const url = `${cdn}${file}.svg`;
|
|
35
|
+
results.push({ brand, file, w: size, h: size, style: STYLE_PREFIX + (options.embed ? await dataUri(url) : url) });
|
|
36
|
+
}
|
|
37
|
+
if (results.length === 0) {
|
|
38
|
+
const slug = SIMPLE.get(squish(query));
|
|
39
|
+
if (slug) {
|
|
40
|
+
const url = `https://cdn.simpleicons.org/${slug}`;
|
|
41
|
+
results.push({ brand: squish(query), file: `simpleicons:${slug}`, w: size, h: size, style: STYLE_PREFIX + (options.embed ? await dataUri(url) : url) });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rankBrands(families, query) {
|
|
48
|
+
const needle = squish(query);
|
|
49
|
+
const tokens = String(query).toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
50
|
+
const scored = [];
|
|
51
|
+
for (const brand of families.keys()) {
|
|
52
|
+
const base = squish(brand);
|
|
53
|
+
let score = 0;
|
|
54
|
+
if (base === needle) score = 100;
|
|
55
|
+
else if (base.startsWith(needle)) score = 70;
|
|
56
|
+
else if (base.includes(needle) || needle.includes(base)) score = 45;
|
|
57
|
+
for (const token of tokens) {
|
|
58
|
+
if (base === token) score = Math.max(score, 90);
|
|
59
|
+
else if (token.length >= 3 && base.includes(token)) score = Math.max(score, 35);
|
|
60
|
+
}
|
|
61
|
+
if (score > 0) scored.push([brand, score]);
|
|
62
|
+
}
|
|
63
|
+
return scored.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([brand]) => brand);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function pickVariant(base, variants, preference) {
|
|
67
|
+
const order = preference === "mono" ? ["", "-color", "-text"] : preference === "text" ? ["-text", "-color", ""] : ["-color", "", "-text"];
|
|
68
|
+
for (const suffix of order) if (variants.has(base + suffix)) return base + suffix;
|
|
69
|
+
return [...variants].sort()[0];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function squish(value) {
|
|
73
|
+
return String(value ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function dataUri(url) {
|
|
77
|
+
const response = await fetch(url);
|
|
78
|
+
if (!response.ok) throw new Error(`failed to fetch ${url}: ${response.status}`);
|
|
79
|
+
const svg = Buffer.from(await response.arrayBuffer()).toString("base64");
|
|
80
|
+
return `data:image/svg+xml;base64,${svg}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const args = parseArgs(process.argv.slice(2));
|
|
84
|
+
const query = args._.join(" ");
|
|
85
|
+
if (!query && !args.list) {
|
|
86
|
+
console.error("usage: node brand-symbols.mjs <brand> [--variant color|mono|text] [--embed] [--json]");
|
|
87
|
+
process.exit(2);
|
|
88
|
+
}
|
|
89
|
+
if (args.list) {
|
|
90
|
+
process.stdout.write([...loadFamilies().families.keys()].sort().join("\n") + "\n");
|
|
91
|
+
} else {
|
|
92
|
+
const results = await queryBrand(query, args);
|
|
93
|
+
if (results.length === 0) {
|
|
94
|
+
console.error(`no brand symbol matched ${JSON.stringify(query)}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
if (args.json) process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
|
|
98
|
+
else for (const item of results) process.stdout.write(`${item.brand} (${item.file}, ${item.w}x${item.h})\n ${item.style.slice(0, 180)}${item.style.length > 180 ? "..." : ""}\n`);
|
|
99
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { formatResult, parseArgs, readText } from "./studio-core.mjs";
|
|
3
|
+
|
|
4
|
+
export function lintDrawio(xml) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
const warnings = [];
|
|
7
|
+
if (/<!--/.test(xml) || /-->/.test(xml)) errors.push("XML comments are not allowed in draw.io diagram sources");
|
|
8
|
+
if (!/<mx(Cell|file|GraphModel)\b/.test(xml)) warnings.push("No draw.io graph markers found");
|
|
9
|
+
if (/<mxCell\b[^>]*\bedge="1"[^>]*\/>/.test(xml)) errors.push("edge mxCell must not be self-closing; add mxGeometry relative=\"1\"");
|
|
10
|
+
|
|
11
|
+
const graphModel = attrsOf(xml.match(/<mxGraphModel\b([^>]*)>/)?.[1] ?? "");
|
|
12
|
+
const pageWidth = Number(graphModel.pageWidth);
|
|
13
|
+
const pageHeight = Number(graphModel.pageHeight);
|
|
14
|
+
const viewportWidth = Number(graphModel.dx);
|
|
15
|
+
const viewportHeight = Number(graphModel.dy);
|
|
16
|
+
if (Number.isFinite(pageWidth) && pageWidth > 800) warnings.push(`pageWidth ${pageWidth} is above the readable manual-flowchart maximum of 800`);
|
|
17
|
+
if (Number.isFinite(pageWidth) && Number.isFinite(viewportWidth) && viewportWidth < pageWidth * 0.75) {
|
|
18
|
+
warnings.push(`dx ${viewportWidth} is much smaller than pageWidth ${pageWidth}; diagrams.net may initially zoom labels too small`);
|
|
19
|
+
}
|
|
20
|
+
if (Number.isFinite(pageHeight) && Number.isFinite(viewportHeight) && viewportHeight < pageHeight * 0.5) {
|
|
21
|
+
warnings.push(`dy ${viewportHeight} is much smaller than pageHeight ${pageHeight}; diagrams.net may initially zoom labels too small`);
|
|
22
|
+
}
|
|
23
|
+
const cellRe = /<mxCell\b([^>]*?)(?:\/>|>([\s\S]*?)<\/mxCell>)/g;
|
|
24
|
+
const objectByCellIndex = objectsByCellIndex(xml);
|
|
25
|
+
const cells = [];
|
|
26
|
+
const ids = new Map();
|
|
27
|
+
for (const match of xml.matchAll(cellRe)) {
|
|
28
|
+
const objectAttrs = objectByCellIndex.get(match.index) ?? {};
|
|
29
|
+
const attrs = attrsOf(match[1]);
|
|
30
|
+
if (!attrs.id && objectAttrs.id) attrs.id = objectAttrs.id;
|
|
31
|
+
if (!attrs.value && objectAttrs.label) attrs.value = objectAttrs.label;
|
|
32
|
+
const body = match[2] ?? "";
|
|
33
|
+
const id = attrs.id;
|
|
34
|
+
if (!id) {
|
|
35
|
+
errors.push("mxCell without id");
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ids.has(id)) errors.push(`duplicate id ${id}`);
|
|
39
|
+
ids.set(id, attrs);
|
|
40
|
+
for (const [name, value] of rawAttrsOf(match[1])) {
|
|
41
|
+
if (hasUnescapedAmpersand(value)) errors.push(`cell ${id} attribute ${name} contains an unescaped ampersand`);
|
|
42
|
+
if (/[<>]/.test(value)) errors.push(`cell ${id} attribute ${name} contains an unescaped angle bracket`);
|
|
43
|
+
if (name === "value" && /\\n/.test(value)) warnings.push(`cell ${id} value contains literal \\n; use 
 for draw.io line breaks`);
|
|
44
|
+
}
|
|
45
|
+
const geom = body.match(/<mxGeometry\b([^>]*?)(?:\/>|>)/)?.[1];
|
|
46
|
+
cells.push({ id, attrs, body, geom: geom ? attrsOf(geom) : null });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (cells.length > 0) {
|
|
50
|
+
if (!ids.has("0")) errors.push("missing root cell id 0");
|
|
51
|
+
if (!ids.has("1")) errors.push("missing default layer cell id 1");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const cell of cells) {
|
|
55
|
+
const { attrs, id, geom } = cell;
|
|
56
|
+
if ((attrs.vertex === "1" || attrs.edge === "1") && (id === "0" || id === "1")) {
|
|
57
|
+
errors.push(`reserved id used by graph cell ${id}`);
|
|
58
|
+
}
|
|
59
|
+
if (attrs.parent && !ids.has(attrs.parent)) errors.push(`cell ${id} parent ${attrs.parent} does not exist`);
|
|
60
|
+
for (const end of ["source", "target"]) {
|
|
61
|
+
if (attrs[end] && !ids.has(attrs[end])) errors.push(`edge ${id} ${end} ${attrs[end]} does not exist`);
|
|
62
|
+
}
|
|
63
|
+
if (attrs.edge === "1" && !geom) errors.push(`edge ${id} is missing mxGeometry`);
|
|
64
|
+
if (attrs.edge === "1" && (!attrs.source || !attrs.target)) warnings.push(`edge ${id} has no source or target`);
|
|
65
|
+
if (attrs.edge === "1" && attrs.style && !styleHas(attrs.style, "edgeStyle", "orthogonalEdgeStyle")) {
|
|
66
|
+
warnings.push(`edge ${id} should use orthogonalEdgeStyle for readable technical diagram routing`);
|
|
67
|
+
}
|
|
68
|
+
if (attrs.vertex === "1") {
|
|
69
|
+
if (looksLikeHtml(attrs.value) && !styleHas(attrs.style, "html", "1")) warnings.push(`cell ${id} label contains HTML but style does not include html=1`);
|
|
70
|
+
if (!styleHas(attrs.style, "whiteSpace", "wrap")) warnings.push(`cell ${id} style should include whiteSpace=wrap for label wrapping`);
|
|
71
|
+
if (!styleHas(attrs.style, "html", "1")) warnings.push(`cell ${id} style should include html=1 for predictable label rendering`);
|
|
72
|
+
const fontSize = styleNumber(attrs.style, "fontSize");
|
|
73
|
+
if (Number.isFinite(fontSize) && fontSize < 12) warnings.push(`cell ${id} fontSize ${fontSize} is below the readable minimum of 12`);
|
|
74
|
+
if (isSwimlane(attrs.style)) {
|
|
75
|
+
if (attrs.connectable !== "0") warnings.push(`swimlane ${id} should set connectable="0" so connectors target intentional nodes`);
|
|
76
|
+
if (styleValue(attrs.style, "collapsible") !== "0") warnings.push(`swimlane ${id} should set collapsible=0 to avoid collapse controls in deliverables`);
|
|
77
|
+
if (styleValue(attrs.style, "rounded") !== "0") warnings.push(`swimlane ${id} should use rounded=0 for clear structural stage boundaries`);
|
|
78
|
+
if (Number.isFinite(fontSize) && fontSize < 14) warnings.push(`swimlane ${id} title fontSize ${fontSize} is below the recommended flowchart stage range of 14-16`);
|
|
79
|
+
} else if (isLikelyProcessNode(attrs.style)) {
|
|
80
|
+
const align = styleValue(attrs.style, "align");
|
|
81
|
+
if (align && align !== "center") warnings.push(`process node ${id} should use align=center for flowchart readability`);
|
|
82
|
+
}
|
|
83
|
+
if (!geom) errors.push(`vertex ${id} is missing mxGeometry`);
|
|
84
|
+
else {
|
|
85
|
+
const rect = rectOf(geom);
|
|
86
|
+
if (!Number.isFinite(rect.w) || !Number.isFinite(rect.h)) errors.push(`vertex ${id} has invalid size`);
|
|
87
|
+
else if (rect.w <= 0 || rect.h <= 0) warnings.push(`vertex ${id} has non-positive size`);
|
|
88
|
+
if (rect.x < 0 || rect.y < 0) warnings.push(`vertex ${id} has negative position`);
|
|
89
|
+
if (!onGrid(rect.x) || !onGrid(rect.y)) warnings.push(`vertex ${id} position should align to the 10px grid`);
|
|
90
|
+
if (Number.isFinite(pageWidth) && rect.x + rect.w > pageWidth) warnings.push(`vertex ${id} extends beyond pageWidth ${pageWidth}`);
|
|
91
|
+
if (Number.isFinite(pageHeight) && rect.y + rect.h > pageHeight) warnings.push(`vertex ${id} extends beyond pageHeight ${pageHeight}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const leafVertices = cells
|
|
97
|
+
.filter((cell) => cell.attrs.vertex === "1" && cell.geom)
|
|
98
|
+
.filter((cell) => !cells.some((candidate) => candidate.attrs.parent === cell.id))
|
|
99
|
+
.map((cell) => ({ id: cell.id, parent: cell.attrs.parent ?? "1", rect: rectOf(cell.geom) }))
|
|
100
|
+
.filter((item) => Number.isFinite(item.rect.w) && Number.isFinite(item.rect.h));
|
|
101
|
+
for (let i = 0; i < leafVertices.length; i += 1) {
|
|
102
|
+
for (let j = i + 1; j < leafVertices.length; j += 1) {
|
|
103
|
+
if (leafVertices[i].parent === leafVertices[j].parent && overlaps(leafVertices[i].rect, leafVertices[j].rect)) {
|
|
104
|
+
warnings.push(`vertices ${leafVertices[i].id} and ${leafVertices[j].id} overlap`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (cells.filter((cell) => cell.attrs.vertex === "1").length === 0) warnings.push("diagram has no vertex cells");
|
|
109
|
+
return { title: "Diagram Lint", errors, warnings, ok: errors.length === 0 };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function objectsByCellIndex(xml) {
|
|
113
|
+
const out = new Map();
|
|
114
|
+
const objectRe = /<object\b([^>]*)>([\s\S]*?)<\/object>/g;
|
|
115
|
+
for (const match of xml.matchAll(objectRe)) {
|
|
116
|
+
const inner = match[2] ?? "";
|
|
117
|
+
const cellOffset = inner.search(/<mxCell\b/);
|
|
118
|
+
if (cellOffset < 0) continue;
|
|
119
|
+
const index = match.index + match[0].indexOf("<mxCell");
|
|
120
|
+
out.set(index, attrsOf(match[1]));
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function attrsOf(raw) {
|
|
126
|
+
const attrs = {};
|
|
127
|
+
for (const match of raw.matchAll(/([A-Za-z_:][-A-Za-z0-9_:.]*)="([^"]*)"/g)) attrs[match[1]] = decode(match[2]);
|
|
128
|
+
return attrs;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rawAttrsOf(raw) {
|
|
132
|
+
return [...raw.matchAll(/([A-Za-z_:][-A-Za-z0-9_:.]*)="([^"]*)"/g)].map((match) => [match[1], match[2]]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function decode(value) {
|
|
136
|
+
return value.replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hasUnescapedAmpersand(value) {
|
|
140
|
+
return /&(?!amp;|lt;|gt;|quot;|apos;|#[0-9]+;|#x[0-9a-fA-F]+;)/.test(value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function looksLikeHtml(value = "") {
|
|
144
|
+
return /<\/?[A-Za-z][^>]*>/.test(value);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function styleHas(style = "", key, expected) {
|
|
148
|
+
return style.split(";").some((part) => {
|
|
149
|
+
const [partKey, value] = part.split("=", 2);
|
|
150
|
+
return partKey === key && value === expected;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function styleNumber(style = "", key) {
|
|
155
|
+
for (const part of style.split(";")) {
|
|
156
|
+
const [partKey, value] = part.split("=", 2);
|
|
157
|
+
if (partKey === key) return Number(value);
|
|
158
|
+
}
|
|
159
|
+
return NaN;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function styleValue(style = "", key) {
|
|
163
|
+
for (const part of style.split(";")) {
|
|
164
|
+
const [partKey, value] = part.split("=", 2);
|
|
165
|
+
if (partKey === key) return value ?? "";
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isSwimlane(style = "") {
|
|
171
|
+
return style.split(";").some((part) => part === "swimlane" || part === "shape=swimlane");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isLikelyProcessNode(style = "") {
|
|
175
|
+
const shape = styleValue(style, "shape");
|
|
176
|
+
if (shape) return ["process", "rectangle", "rhombus", "terminator"].includes(shape);
|
|
177
|
+
return style.includes("rounded=1") || style.includes("whiteSpace=wrap");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function rectOf(geom) {
|
|
181
|
+
return {
|
|
182
|
+
x: Number(geom.x ?? 0),
|
|
183
|
+
y: Number(geom.y ?? 0),
|
|
184
|
+
w: Number(geom.width),
|
|
185
|
+
h: Number(geom.height),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function onGrid(value, grid = 10) {
|
|
190
|
+
return Number.isFinite(value) && Math.abs(value / grid - Math.round(value / grid)) < 1e-9;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function overlaps(a, b) {
|
|
194
|
+
return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const args = parseArgs(process.argv.slice(2));
|
|
198
|
+
const input = args._[0];
|
|
199
|
+
if (!input) {
|
|
200
|
+
console.error("usage: node diagram-lint.mjs <file.drawio> [--format markdown|json] [--strict]");
|
|
201
|
+
process.exit(2);
|
|
202
|
+
}
|
|
203
|
+
const result = lintDrawio(readText(input));
|
|
204
|
+
process.stdout.write(formatResult(result, args.format ?? (args.json ? "json" : "markdown")));
|
|
205
|
+
if (!result.ok || (args.strict && result.warnings.length > 0)) process.exit(1);
|