Files
diagrams-drawio/references/maintenance.md

8.4 KiB

diagrams-drawio — Maintenance Guide

This document is for developers maintaining or extending the drawio-tools CLI.
For agent usage instructions, see SKILL.md.

The CLI lives under the skill's scripts/ directory (scripts/src/ → scripts/dist/), per the skill-manager structure conventions. All commands below run from scripts/.


Tech stack

Layer Library / Tool Version
Language TypeScript ESM (NodeNext) —
Package manager pnpm v11.9+
XML model @maxgraph/core v0.23
DOM polyfill jsdom —
Deflate decode pako —
YAML output js-yaml —
Build tsc —
Dev runner tsx —

pnpm-workspace.yaml must include:

allowBuilds:
  esbuild: true

Install and deploy

The git repository is the source of truth. Install dependencies and build inside scripts/:

cd <skill>/scripts
pnpm install
task build          # or: pnpm run build

Deploy with skill-manager (creates absolute-path symlinks in $HOME/.agents/skills and $HOME/.claude/skills, so the repo working copy stays the single source of truth):

cd <skill-manager>/scripts
task deploy -- --skill-dir="/absolute/path/to/diagrams-drawio"

Verify the CLI works after installation:

cd <skill>/scripts
node dist/cli/commands.js -f <diagram.drawio> -a summary

Run the CLI

cd <skill>/scripts
task run -- --file="<diagram.drawio>" --action=<action>
# or directly:
node dist/cli/commands.js -f <diagram.drawio> -a <action>
# or build-and-run during development:
pnpm run cli -f <diagram.drawio> -a <action>

Note: with pnpm run cli, pass arguments directly after cli — do not use -- separator.


Build (development only)

Build only (no run):

cd <skill>/scripts
task build          # tsc → dist/

Source structure

src/
├── cli/
│   └── commands.ts                      # parseArgs dispatcher → dynamic action imports
├── authoring/
│   └── ir-to-drawio.ts                  # deterministic IR v1/v2 serializer + preservation envelopes
├── model/
│   └── diagram-ir.ts                    # canonical types, normalization, runtime validation
├── services/
│   ├── drawio-parser/
│   │   ├── parser.ts                   # parseAllPages() / parseDiagram() — Shape, Edge, ParsedPage
│   │   └── page-summary.ts            # buildPageSummary() — shared per-page serialisation helper
│   ├── hierarchy-builder/
│   │   └── hierarchy-builder.ts        # buildHierarchy() — shared BFS depth map + containment tree
│   └── semantic-lifecycle/              # import, edit, views/query/policy/what-if, sync, story, atomic I/O
└── actions/
    ├── build|import|edit|views|query|test|what-if|sync|story|doctor/
    │                                      # authoring and semantic lifecycle actions
    ├── summary/                         # all pages inventory
    ├── page-summary/                    # single page inventory (uses --page)
    ├── page-hierarchy/                  # containment tree from parentId
    ├── page-connectors-summary/         # connector stats
    ├── page-connectors-validation/      # overlap + crossing detection
    ├── page-labels-validation/          # label quality checks
    ├── page-shape-bbox-validation/      # bounding box overlap detection
    ├── page-orphans/                    # isolated shapes + dangling connectors
    ├── page-recommendations/           # page size recommendation
    ├── page-hierarchy-full/            # nesting levels with full shape geometry (x, y, width, height)
    └── validate/                       # MANDATORY final gate — XML well-formedness + maxGraph compile + sanity check

Each action exports run(filePath, pageIndex?, outputPath?, options?): Record<string, unknown>. Analysis actions may ignore lifecycle-only parameters. File-producing actions must require --output and use the shared atomic writer.

Key modules

src/services/drawio-parser/parser.ts

  • parseAllPages(filePath) — parses all <diagram> tabs in an mxfile, returns ParsedPage[]
  • parseDiagram(filePath) — backward-compat wrapper, returns first page only
  • Multi-page support: regex extracts all <diagram> blocks; each decoded separately (base64 + pako inflateRaw)
  • Uses @maxgraph/core ModelXmlSerializer + GraphDataModel with a jsdom DOM polyfill

src/services/drawio-parser/page-summary.ts

  • buildPageSummary(page: ParsedPage): PageSummaryResult — shared helper used by both summary and page-summary actions

src/services/hierarchy-builder/hierarchy-builder.ts

  • buildHierarchy(page: ParsedPage): HierarchyResult — shared BFS depth map + containment tree
  • Returns: tree, allNodes, depthMap, childrenOf, maxDepth, totalLevels (= maxDepth + 1), depthCounts
  • Used by page-hierarchy (tree output) and page-hierarchy-full (geometry per nesting level)

src/cli/commands.ts

  • parseArgs dispatcher → dynamic imports of action modules
  • Common options: --file / -f, --action / -a, --page / -p, --output / -o, --help / -h
  • Lifecycle options: --spec, --base, --strict, --prune, --dry-run, --fail, --views, --kind, repeatable --property, --from, and --to
  • doctor is the only action that does not require --file
  • All output serialised to YAML on stdout; exit 0 success, 1 error

Adding a new action

  1. Create src/actions/<name>/action.ts exporting:
    export async function run(filePath: string, pageIndex?: number): Promise<Record<string, unknown>>
    
  2. Register it in src/cli/commands.ts under ACTIONS:
    "my-action": () => import("../actions/my-action/action.js"),
    
  3. Use parseDiagram(filePath) (first page) or parseAllPages(filePath) (all pages) from the parser
  4. Optionally import buildPageSummary(page) from page-summary.ts for standard shape/edge serialisation
  5. Return a plain object — the CLI serialises it to YAML automatically
  6. Run pnpm run build to compile and verify no TypeScript errors

Naming convention

Actions follow {object}-{action} naming:

  • page-* — operates on a single diagram page (uses --page, default 0)
  • summary — operates on all pages

CLI action registration map

const ACTIONS: Record<string, () => Promise<ActionModule>> = {
  "build":                      () => import("../actions/build/action.js"),
  "summary":                    () => import("../actions/summary/action.js"),
  "page-summary":               () => import("../actions/page-summary/action.js"),
  "page-hierarchy":             () => import("../actions/page-hierarchy/action.js"),
  "page-connectors-summary":    () => import("../actions/page-connectors-summary/action.js"),
  "page-connectors-validation": () => import("../actions/page-connectors-validation/action.js"),
  "page-labels-validation":     () => import("../actions/page-labels-validation/action.js"),
  "page-shape-bbox-validation": () => import("../actions/page-shape-bbox-validation/action.js"),
  "page-orphans":               () => import("../actions/page-orphans/action.js"),
  "page-recommendations":       () => import("../actions/page-recommendations/action.js"),
  "page-hierarchy-full":        () => import("../actions/page-hierarchy-full/action.js"),
  "page-negative-space-summary":() => import("../actions/page-negative-space-summary/action.js"),
  "validate":                   () => import("../actions/validate/action.js"),
  "quality":                    () => import("../actions/quality/action.js"),
  "import":                     () => import("../actions/import/action.js"),
  "edit":                       () => import("../actions/edit/action.js"),
  "views":                      () => import("../actions/views/action.js"),
  "query":                      () => import("../actions/query/action.js"),
  "test":                       () => import("../actions/test/action.js"),
  "what-if":                    () => import("../actions/what-if/action.js"),
  "sync":                       () => import("../actions/sync/action.js"),
  "story":                      () => import("../actions/story/action.js"),
  "doctor":                     () => import("../actions/doctor/action.js"),
};