{"kind":"Skill","metadata":{"namespace":"community","name":"drawio","version":"0.1.0"},"spec":{"description":"Generate draw.io diagrams as .drawio files and export to PNG/SVG/PDF with embedded XML","files":{"SKILL.md":"---\nname: drawio\ndescription: Generate draw.io diagrams as .drawio files and export to PNG/SVG/PDF with embedded XML\n---\n\n# Draw.io Diagram Skill\n\nGenerate draw.io diagrams as native `.drawio` files and export them to PNG images that can be embedded in Word documents.\n\n## How to Create a Diagram\n\n1. **Generate draw.io XML** in `mxGraphModel` format for the requested diagram\n2. **Write the XML** to a `.drawio` file using the create/edit file tool\n3. **Export to PNG** using the bundled export script\n\n## Bundled Export Script\n\nThis skill includes `drawio-to-png.mjs`, a Node.js export script with two rendering backends:\n\n1. **draw.io CLI** (pixel-perfect, fastest) — used automatically if draw.io desktop is installed\n2. **Official draw.io viewer in headless browser** (pixel-perfect, needs Chromium/Edge) — fallback when CLI is unavailable\n\n### Usage\n\n```bash\n# Install dependencies (one-time, from the scripts folder)\ncd skills/drawio/scripts \u0026\u0026 npm install\n\n# Export a single diagram\nnode skills/drawio/scripts/drawio-to-png.mjs \u003cinput.drawio\u003e [output.png]\n\n# Export all .drawio files in a directory\nnode skills/drawio/scripts/drawio-to-png.mjs --dir \u003cdirectory\u003e\n\n# Force a specific renderer\nnode skills/drawio/scripts/drawio-to-png.mjs --renderer=cli|viewer|auto \u003cinput.drawio\u003e\n```\n\n### Skill Folder Contents\n\n| File | Purpose |\n|------|---------|\n| `SKILL.md` | This instruction file |\n| `scripts/drawio-to-png.mjs` | Node.js export script (CLI + browser fallback) |\n| `scripts/package.json` | Dependencies (`puppeteer-core`) |\n\n## Supported Export Formats\n\n| Format | Embed XML | Notes |\n|--------|-----------|-------|\n| `png` | Yes | Viewable everywhere, editable in draw.io |\n| `svg` | Yes | Scalable, editable in draw.io |\n| `pdf` | Yes | Printable, editable in draw.io |\n\n## Draw.io XML Style Conventions\n\nUse these styles for consistent, professional diagrams:\n\n```xml\n\u003c!-- Primary service (highlighted) --\u003e\n\u003cmxCell style=\"rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=2;arcSize=12;shadow=1;\" /\u003e\n\n\u003c!-- External system --\u003e\n\u003cmxCell style=\"rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;\" /\u003e\n\n\u003c!-- Success/processing stage --\u003e\n\u003cmxCell style=\"rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" /\u003e\n\n\u003c!-- Warning/quality gate --\u003e\n\u003cmxCell style=\"rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;\" /\u003e\n\n\u003c!-- Error/failure path --\u003e\n\u003cmxCell style=\"rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;\" /\u003e\n\n\u003c!-- Data store (cylinder) --\u003e\n\u003cmxCell style=\"shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;\" /\u003e\n\n\u003c!-- Arrow --\u003e\n\u003cmxCell style=\"edgeStyle=orthogonalEdgeStyle;rounded=1;strokeColor=#6c8ebf;strokeWidth=2;\" /\u003e\n```\n\n## Locating the draw.io CLI\n\nTry `drawio` first (works if on PATH), then fall back:\n\n- **Windows**: `\"C:\\Program Files\\draw.io\\draw.io.exe\"`\n- **macOS**: `/Applications/draw.io.app/Contents/MacOS/draw.io`\n- **Linux**: `drawio` (via snap/apt/flatpak)\n\n### CLI Export Command\n\n```bash\ndrawio -x -f png -e -b 10 -o \u003coutput.png\u003e \u003cinput.drawio\u003e\n```\n\nFlags: `-x` (export), `-f` (format), `-e` (embed diagram XML), `-b` (border), `-o` (output path).\n","scripts/drawio-to-png.mjs":"/**\n * drawio-to-png.mjs - Convert .drawio files to PNG with accurate rendering.\n *\n * Rendering priority:\n *   1. draw.io CLI (if installed) — pixel-perfect, fastest\n *   2. Official draw.io viewer JS in headless browser — pixel-perfect, needs network\n *\n * Usage: node drawio-to-png.mjs \u003cinput.drawio\u003e [output.png]\n *        node drawio-to-png.mjs --dir \u003cdirectory\u003e   (converts all .drawio files in directory)\n *        node drawio-to-png.mjs --renderer=cli|viewer|auto \u003cinput.drawio\u003e [output.png]\n */\n\nimport { readFileSync, writeFileSync, readdirSync, statSync } from \"fs\";\nimport { join, basename, dirname, resolve } from \"path\";\nimport { spawnSync } from \"child_process\";\nimport { inflateRawSync } from \"zlib\";\nimport puppeteer from \"puppeteer-core\";\n\n// --- Build HTML that uses the official draw.io viewer for rendering ---\nfunction buildViewerHtml(rawFileContent) {\n  // Escape for embedding in a JS template literal\n  const escaped = rawFileContent\n    .replace(/\\\\/g, \"\\\\\\\\\")\n    .replace(/`/g, \"\\\\`\")\n    .replace(/\\$/g, \"\\\\$\");\n\n  // The official draw.io viewer (viewer-static.min.js) contains the full mxGraph\n  // rendering engine — it handles orthogonal edge routing, all shape types,\n  // container layouts, and compressed/uncompressed diagram formats.\n  return `\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"utf-8\"\u003e\n  \u003cstyle\u003e\n    * { margin: 0; padding: 0; }\n    body { background: white; }\n  \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n  \u003cdiv id=\"diagram-host\"\u003e\u003c/div\u003e\n  \u003cscript\u003e\n    // Prepare diagram XML and set up the viewer target div\n    (function() {\n      var raw = \\`${escaped}\\`;\n\n      // Wrap raw mxGraphModel in mxfile if needed (viewer expects mxfile format)\n      var xmlStr = raw.trim();\n      if (xmlStr.startsWith('\u003cmxGraphModel')) {\n        xmlStr = '\u003cmxfile\u003e\u003cdiagram name=\"Page-1\"\u003e' + xmlStr + '\u003c/diagram\u003e\u003c/mxfile\u003e';\n      } else if (!xmlStr.startsWith('\u003cmxfile')) {\n        // Assume it's already an mxfile or a diagram element\n        if (xmlStr.startsWith('\u003cdiagram')) {\n          xmlStr = '\u003cmxfile\u003e' + xmlStr + '\u003c/mxfile\u003e';\n        }\n      }\n\n      var config = {\n        xml: xmlStr,\n        highlight: \"none\",\n        nav: false,\n        resize: true,\n        toolbar: null,\n        \"toolbar-nohide\": true,\n        edit: null,\n        lightbox: false,\n        \"auto-fit\": true,\n        \"check-visible-state\": false\n      };\n\n      var div = document.createElement('div');\n      div.className = 'mxgraph';\n      div.setAttribute('data-mxgraph', JSON.stringify(config));\n      document.getElementById('diagram-host').appendChild(div);\n    })();\n\n    // Poll until the viewer renders the diagram (viewer script loaded separately)\n    window.__pollStarted = false;\n    window.__startPoll = function() {\n      if (window.__pollStarted) return;\n      window.__pollStarted = true;\n      // Explicitly trigger viewer processing\n      if (typeof GraphViewer !== 'undefined' \u0026\u0026 GraphViewer.processElements) {\n        GraphViewer.processElements();\n      }\n      (function poll() {\n        // Viewer places SVG directly inside .mxgraph div\n        var mxDiv = document.querySelector('.mxgraph');\n        if (mxDiv) {\n          var svg = mxDiv.querySelector('svg');\n          if (svg) {\n            var rect = mxDiv.getBoundingClientRect();\n            if (rect.width \u003e 10 \u0026\u0026 rect.height \u003e 10) {\n              window.__renderComplete = true;\n              window.__renderWidth = rect.width;\n              window.__renderHeight = rect.height;\n              return;\n            }\n          }\n        }\n        setTimeout(poll, 150);\n      })();\n    };\n  \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e`;\n}\n\n// --- Extract mxGraph XML from .drawio input (supports mxGraphModel and mxfile) ---\nfunction extractMxGraphModelXml(inputXml) {\n  const trimmed = inputXml.trim();\n\n  if (trimmed.startsWith(\"\u003cmxGraphModel\")) {\n    return trimmed;\n  }\n\n  const diagramMatch = trimmed.match(/\u003cdiagram\\b[^\u003e]*\u003e([\\s\\S]*?)\u003c\\/diagram\u003e/i);\n  if (!diagramMatch) {\n    throw new Error(\"Unsupported .drawio format: missing \u003cmxGraphModel\u003e or \u003cdiagram\u003e content\");\n  }\n\n  const diagramContent = diagramMatch[1].trim();\n\n  if (diagramContent.startsWith(\"\u003cmxGraphModel\")) {\n    return diagramContent;\n  }\n\n  // draw.io compressed diagrams are base64(deflateRaw(encodeURIComponent(xml)))\n  try {\n    const inflated = inflateRawSync(Buffer.from(diagramContent, \"base64\")).toString(\"utf-8\");\n    const decoded = decodeURIComponent(inflated);\n    if (!decoded.trim().startsWith(\"\u003cmxGraphModel\")) {\n      throw new Error(\"decoded content is not mxGraphModel XML\");\n    }\n    return decoded;\n  } catch (err) {\n    throw new Error(`Failed to decode compressed \u003cdiagram\u003e content: ${err.message}`);\n  }\n}\n\nfunction resolveRenderer(rawArgs) {\n  let renderer = \"auto\";\n  const args = [];\n\n  for (const arg of rawArgs) {\n    if (arg.startsWith(\"--renderer=\")) {\n      renderer = arg.substring(\"--renderer=\".length).trim().toLowerCase();\n      continue;\n    }\n    args.push(arg);\n  }\n\n  if (![\"auto\", \"cli\", \"viewer\"].includes(renderer)) {\n    throw new Error(`Invalid renderer '${renderer}'. Use auto, cli, or viewer.`);\n  }\n\n  return { renderer, args };\n}\n\nfunction findDrawioCliPath() {\n  const envPath = process.env.DRAWIO_PATH;\n  if (envPath) {\n    try {\n      if (statSync(envPath).isFile()) return envPath;\n    } catch { /* ignore */ }\n  }\n\n  const candidates = [\n    \"C:\\\\Program Files\\\\draw.io\\\\draw.io.exe\",\n    \"C:\\\\Program Files (x86)\\\\draw.io\\\\draw.io.exe\",\n    \"/Applications/draw.io.app/Contents/MacOS/draw.io\",\n    \"/usr/bin/drawio\",\n    \"/usr/local/bin/drawio\",\n  ];\n\n  for (const p of candidates) {\n    try {\n      if (statSync(p).isFile()) return p;\n    } catch { /* ignore */ }\n  }\n\n  const locator = process.platform === \"win32\" ? \"where\" : \"which\";\n  const names = process.platform === \"win32\" ? [\"drawio\", \"draw.io\"] : [\"drawio\"];\n\n  for (const name of names) {\n    const probe = spawnSync(locator, [name], { encoding: \"utf-8\" });\n    if (probe.status === 0 \u0026\u0026 probe.stdout) {\n      const first = probe.stdout.split(/\\r?\\n/).map(line =\u003e line.trim()).find(Boolean);\n      if (first) return first;\n    }\n  }\n\n  return null;\n}\n\nfunction exportWithDrawioCli(drawioPath, input, output) {\n  const args = [\"-x\", \"-f\", \"png\", \"-e\", \"-b\", \"10\", \"-o\", output, input];\n  const result = spawnSync(drawioPath, args, { encoding: \"utf-8\" });\n  if (result.status !== 0) {\n    const stderr = (result.stderr || \"\").trim();\n    const stdout = (result.stdout || \"\").trim();\n    throw new Error(stderr || stdout || `draw.io CLI failed with exit code ${result.status}`);\n  }\n}\n\n// --- Main ---\nasync function main() {\n  const parsed = resolveRenderer(process.argv.slice(2));\n  const renderer = parsed.renderer;\n  const args = parsed.args;\n\n  let files = [];\n  if (args[0] === \"--dir\") {\n    const dir = resolve(args[1] || \".\");\n    files = readdirSync(dir)\n      .filter(f =\u003e f.endsWith(\".drawio\"))\n      .map(f =\u003e ({\n        input: join(dir, f),\n        output: join(dir, f.replace(/\\.drawio$/, \".drawio.png\"))\n      }));\n  } else if (args[0]) {\n    const input = resolve(args[0]);\n    const output = args[1] || input.replace(/\\.drawio$/, \".drawio.png\");\n    files = [{ input, output }];\n  } else {\n    console.error(\"Usage: node drawio-to-png.mjs \u003cinput.drawio\u003e [output.png]\");\n    console.error(\"       node drawio-to-png.mjs --dir \u003cdirectory\u003e\");\n    console.error(\"       node drawio-to-png.mjs --renderer=cli|auto|custom \u003cinput.drawio\u003e [output.png]\");\n    process.exit(1);\n  }\n\n  if (files.length === 0) {\n    console.log(\"No .drawio files found.\");\n    return;\n  }\n\n  const drawioCliPath = findDrawioCliPath();\n\n  // --- Path 1: draw.io CLI (best fidelity, no network needed) ---\n  if (renderer === \"cli\" || (renderer === \"auto\" \u0026\u0026 drawioCliPath)) {\n    if (!drawioCliPath) {\n      console.error(\"draw.io CLI not found. Install draw.io desktop or set DRAWIO_PATH.\");\n      process.exit(1);\n    }\n    console.log(`Using renderer: draw.io CLI (${basename(drawioCliPath)})`);\n    for (const { input, output } of files) {\n      console.log(`Rendering: ${basename(input)}`);\n      try {\n        exportWithDrawioCli(drawioCliPath, input, output);\n        let kb = \"?\";\n        try {\n          kb = (statSync(output).size / 1024).toFixed(0);\n        } catch { /* ignore size read errors */ }\n        console.log(`  -\u003e ${basename(output)} (${kb} KB)`);\n      } catch (err) {\n        console.error(`  Error rendering ${basename(input)}: ${err.message}`);\n      }\n    }\n    console.log(\"Done.\");\n    return;\n  }\n\n  // --- Path 2: Official draw.io viewer in headless browser ---\n  // Find browser\n  const browserPaths = [\n    process.env.CHROME_PATH,\n    process.env.EDGE_PATH,\n    \"C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe\",\n    \"C:\\\\Program Files\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe\",\n    \"C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n    \"C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n    \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\",\n    \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\",\n    \"/usr/bin/google-chrome\",\n    \"/usr/bin/chromium-browser\",\n    \"/usr/bin/microsoft-edge\",\n  ].filter(Boolean);\n\n  let execPath;\n  for (const p of browserPaths) {\n    try {\n      if (statSync(p).isFile()) { execPath = p; break; }\n    } catch { /* not found */ }\n  }\n\n  if (!execPath) {\n    console.error(\"No browser found. Set CHROME_PATH or EDGE_PATH environment variable.\");\n    process.exit(1);\n  }\n\n  console.log(`Using renderer: draw.io viewer (${basename(execPath)})`);\n\n  const browser = await puppeteer.launch({\n    executablePath: execPath,\n    headless: true,\n    args: [\"--no-sandbox\", \"--disable-setuid-sandbox\", \"--disable-gpu\"],\n  });\n\n  for (const { input, output } of files) {\n    console.log(`Rendering: ${basename(input)}`);\n    try {\n      const rawContent = readFileSync(input, \"utf-8\");\n      const html = buildViewerHtml(rawContent);\n\n      const page = await browser.newPage();\n      await page.setViewport({ width: 2400, height: 1600, deviceScaleFactor: 2 });\n\n      // Set HTML content first (sets up the .mxgraph div with diagram XML)\n      await page.setContent(html, { waitUntil: \"domcontentloaded\" });\n\n      // Load the official draw.io viewer JS via addScriptTag (more reliable than inline src)\n      const VIEWER_URL = \"https://viewer.diagrams.net/js/viewer-static.min.js\";\n      try {\n        await page.addScriptTag({ url: VIEWER_URL });\n      } catch (scriptErr) {\n        throw new Error(`Failed to load draw.io viewer JS: ${scriptErr.message}`);\n      }\n\n      // Start polling for the rendered diagram\n      await page.evaluate(() =\u003e window.__startPoll());\n\n      // Wait for the viewer to finish rendering\n      await page.waitForFunction(() =\u003e window.__renderComplete === true, { timeout: 30000 });\n\n      // Check rendering succeeded\n      const viewerOk = await page.evaluate(() =\u003e window.__renderWidth \u003e 0);\n      if (!viewerOk) {\n        throw new Error(\"draw.io viewer failed to load or render (check network access)\");\n      }\n\n      // Take element screenshot of just the diagram div for exact bounds\n      const containerHandle = await page.$('.mxgraph');\n      let pngBuffer;\n\n      if (containerHandle) {\n        pngBuffer = await containerHandle.screenshot({ type: \"png\" });\n      } else {\n        // Fallback: full-page screenshot\n        const dims = await page.evaluate(() =\u003e ({\n          w: Math.ceil(window.__renderWidth),\n          h: Math.ceil(window.__renderHeight)\n        }));\n        pngBuffer = await page.screenshot({\n          type: \"png\",\n          clip: { x: 0, y: 0, width: dims.w + 20, height: dims.h + 20 },\n        });\n      }\n\n      writeFileSync(output, pngBuffer);\n      console.log(`  -\u003e ${basename(output)} (${(pngBuffer.length / 1024).toFixed(0)} KB)`);\n\n      await page.close();\n    } catch (err) {\n      console.error(`  Error rendering ${basename(input)}: ${err.message}`);\n    }\n  }\n\n  await browser.close();\n  console.log(\"Done.\");\n}\n\nmain().catch(err =\u003e { console.error(err); process.exit(1); });\n","scripts/package.json":"{\n  \"private\": true,\n  \"type\": \"module\",\n  \"description\": \"Dependencies for the draw.io diagram export skill\",\n  \"dependencies\": {\n    \"puppeteer-core\": \"^24.39.1\"\n  }\n}\n"},"import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/tree/541b7819d8c3545c6df122491af4fa1eae415779/plugins/project-documenter/skills/drawio"}},"content_hash":[170,208,241,224,200,251,151,163,160,210,147,94,44,110,150,235,219,113,245,130,134,250,156,112,0,195,27,12,70,154,90,172],"trust_level":"unsigned","yanked":false}
