fix(excalidraw): resolve canvas module path and add canonical file location convention
Fix convert.mjs to resolve canvas from .export-runtime via createRequire instead of bare import (which resolves relative to script location, not CWD). Add File Location Convention section to SKILL.md — diagrams save .excalidraw source alongside PNGs in the project's image directory for easy re-export. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,20 @@ This creates a `.export-runtime` directory inside `scripts/` with the Node.js de
|
|||||||
|
|
||||||
The Excalidraw MCP server must be configured. Verify availability by checking for `mcp__excalidraw__create_view` and `mcp__excalidraw__read_checkpoint` tools.
|
The Excalidraw MCP server must be configured. Verify availability by checking for `mcp__excalidraw__create_view` and `mcp__excalidraw__read_checkpoint` tools.
|
||||||
|
|
||||||
|
## File Location Convention
|
||||||
|
|
||||||
|
Save diagram source files alongside their PNG exports in the project's image directory. This enables re-exporting diagrams when content or styling changes.
|
||||||
|
|
||||||
|
**Standard pattern:**
|
||||||
|
```
|
||||||
|
docs/images/my-diagram.excalidraw # source (commit this)
|
||||||
|
docs/images/my-diagram.png # rendered output (commit this)
|
||||||
|
```
|
||||||
|
|
||||||
|
**When updating an existing diagram**, look for a `.excalidraw` file next to the PNG. If one exists, edit it and re-export rather than rebuilding from scratch.
|
||||||
|
|
||||||
|
**Temporary files** (raw checkpoint JSON) go in `/tmp/excalidraw-export/` and are discarded after conversion.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
### Step 1: Design the Diagram Elements
|
### Step 1: Design the Diagram Elements
|
||||||
@@ -63,11 +77,11 @@ Use the `convert.mjs` script to transform raw MCP checkpoint JSON into a valid `
|
|||||||
- Expands `label` properties on shapes/arrows into proper bound text elements
|
- Expands `label` properties on shapes/arrows into proper bound text elements
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Save checkpoint JSON to a file first, then convert:
|
# Save checkpoint JSON to a temp file, then convert to the project's image directory:
|
||||||
node <skill-path>/scripts/convert.mjs <input.json> <output.excalidraw>
|
node <skill-path>/scripts/convert.mjs /tmp/excalidraw-export/raw.json docs/images/my-diagram.excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
The input JSON should be the raw checkpoint data from `mcp__excalidraw__read_checkpoint` (the `{"elements": [...]}` object).
|
The input JSON should be the raw checkpoint data from `mcp__excalidraw__read_checkpoint` (the `{"elements": [...]}` object). The output `.excalidraw` file goes in the project's image directory (see File Location Convention above).
|
||||||
|
|
||||||
**For batch exports**: Write each checkpoint to a separate raw JSON file, then convert each one:
|
**For batch exports**: Write each checkpoint to a separate raw JSON file, then convert each one:
|
||||||
```bash
|
```bash
|
||||||
@@ -92,7 +106,7 @@ Bound text (labels on shapes/arrows) needs: `containerId: "<parent-id>"`, `textA
|
|||||||
Run the export script. Determine the runtime path relative to this skill's scripts directory:
|
Run the export script. Determine the runtime path relative to this skill's scripts directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd <skill-path>/scripts/.export-runtime && node <skill-path>/scripts/export_png.mjs /tmp/excalidraw-export/diagram.excalidraw /tmp/excalidraw-export/output.png
|
cd <skill-path>/scripts/.export-runtime && node <skill-path>/scripts/export_png.mjs docs/images/my-diagram.excalidraw docs/images/my-diagram.png
|
||||||
```
|
```
|
||||||
|
|
||||||
The script:
|
The script:
|
||||||
@@ -110,7 +124,7 @@ The script prints the output path on success. Verify the result with `file <outp
|
|||||||
Run the validation script on the `.excalidraw` file to catch spatial issues:
|
Run the validation script on the `.excalidraw` file to catch spatial issues:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node <skill-path>/scripts/validate.mjs /tmp/excalidraw-export/diagram.excalidraw
|
node <skill-path>/scripts/validate.mjs docs/images/my-diagram.excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
Then read the exported PNG back using the Read tool to visually inspect:
|
Then read the exported PNG back using the Read tool to visually inspect:
|
||||||
|
|||||||
@@ -4,13 +4,20 @@
|
|||||||
* Filters pseudo-elements, adds required defaults, expands labels into bound text.
|
* Filters pseudo-elements, adds required defaults, expands labels into bound text.
|
||||||
*/
|
*/
|
||||||
import { readFileSync, writeFileSync } from 'fs';
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const runtimeRequire = createRequire(join(__dirname, '.export-runtime', 'package.json'));
|
||||||
|
|
||||||
// Canvas-based text measurement with graceful fallback to heuristic.
|
// Canvas-based text measurement with graceful fallback to heuristic.
|
||||||
// Excalidraw renders with Virgil (hand-drawn font); system sans-serif
|
// Excalidraw renders with Virgil (hand-drawn font); system sans-serif
|
||||||
// is a reasonable proxy. The 1.1x multiplier accounts for Virgil being wider.
|
// is a reasonable proxy. The 1.1x multiplier accounts for Virgil being wider.
|
||||||
let measureText;
|
let measureText;
|
||||||
try {
|
try {
|
||||||
const { createCanvas } = await import('canvas');
|
const canvas = runtimeRequire('canvas');
|
||||||
|
const { createCanvas } = canvas;
|
||||||
const cvs = createCanvas(1, 1);
|
const cvs = createCanvas(1, 1);
|
||||||
const ctx = cvs.getContext('2d');
|
const ctx = cvs.getContext('2d');
|
||||||
measureText = (text, fontSize) => {
|
measureText = (text, fontSize) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user