Skip to content

Add board listing, draw.io export and bulk backup - #144

Open
marnusvanwyk459 wants to merge 2 commits into
jolle:mainfrom
marnusvanwyk459:feat/list-boards-drawio-export-and-backup
Open

Add board listing, draw.io export and bulk backup#144
marnusvanwyk459 wants to merge 2 commits into
jolle:mainfrom
marnusvanwyk459:feat/list-boards-drawio-export-and-backup

Conversation

@marnusvanwyk459

Copy link
Copy Markdown

Hi — thanks for this tool, it does the hard part (getting full-detail vector out of Miro) really well. We're using it to archive a few hundred boards, and this is what we needed on top. Happy to split it into smaller PRs if you'd prefer to take it in pieces; it's grouped into independent commits' worth of work and I've described each part separately below.

A bug worth looking at first

The wait for the Miro SDK is hard-coded to 3 seconds:

mustHaveSdk,
3_000

On every real board I tested, window.miro takes about 8.5 seconds to appear (cmd.board.api at ~4.7s, SDK at ~8.5s). So anything that addresses individual objects — all JSON export, all frame-scoped export — fails with:

Miro SDK failed to load in 3000 ms.

The timeout is now an option (sdkLoadTimeoutMs, --sdk-timeout), defaulting to 30 seconds. The existing 3-second best-effort wait is kept for the case where the SDK isn't required.

Related: whole-board SVG export previously skipped the SDK wait entirely. It now waits when it can and falls back to the old best-effort path when the SDK never loads, so the "buggy board" case in the test suite still works. On a 700-object board this took the output from 872KB to 1,022KB of vector.

Also, the CLI printed errors but exited 0, so failures were invisible to scripts. It now exits non-zero.

MIRO_TOKEN and .env

The token can come from the environment rather than --token on every invocation. Uses Node's own process.loadEnvFile, so no new dependency. Precedence is explicit token → real environment → .env. .env is git-ignored and .env.example documents the variables, including the ones the existing integration tests need.

list-boards

Lists the boards a token can reach, via the same endpoint the Miro dashboard uses. No browser, so it returns in about a second for ~190 boards. Supports table / json / ids output and a title filter.

One thing worth recording: Miro's offset paging on this endpoint is unreliable. Following nextLink from limit=25 returned 26 of 189 boards and then stopped, and nextLink disappears entirely for larger limits. So the whole list is requested in one page by default, nextLink is still followed when present, and results are de-duplicated by ID.

draw.io export (-e drawio, and an offline convert command)

Converts board objects to native mxGraph — real vertices, edges and containers, not an embedded image. Frames become containers with their children nested, sticky notes and shapes keep their fill/border/font, and connectors become edges with real source/target, arrow heads, captions and exit/entry anchors.

convert works from a saved JSON export without contacting Miro, which matters when converting many boards.

Three things Miro doesn't record, reconstructed and documented in the README:

  • Z-order. The export order isn't a z-order, so large shapes used as backdrops covered their own contents. Cells are emitted largest-first (zOrder: "source" opts out).
  • Font sizes. Miro stores no fontSize for sticky notes — it fits text to the note. Left alone, every sticky falls back to draw.io's 12pt default and a whole board reads as one size. Sizes are derived by wrapping at spaces and fitting to the shape; because text only breaks at spaces, the longest word sets the ceiling. Shapes and text keep the size Miro recorded.
  • Curved connectors. Miro stores no waypoints, and draw.io can only curve through the points of a route, so a bare curved=1 draws a straight line. Curved connectors get an orthogonal route to smooth.

Images become labelled placeholders — the JSON export carries no image data or usable URL (all 37 on my test board have url: ""). SVG export remains the only format that preserves image content.

backup

Walks the whole board list into a timestamped directory, one directory per board, with a manifest.json recording status, attempts, paths, sizes and durations.

Serial with a configurable pause between boards, since the cost is per-board browser work rather than per API call. Each board is retried with a doubling backoff capped at five minutes; a board that exhausts its retries is recorded and the run continues rather than aborting. --resume <dir> continues an interrupted run, skipping boards that already have all their files. --dry-run shows the plan without contacting Miro.

Board titles are sanitised into directory names: slashes, colons and the characters Windows reserves are replaced, control characters stripped, trailing dots/spaces removed, names Windows reserves outright (CON, NUL, COM1…) suffixed, long titles shortened. The board ID is appended so boards sharing a title never collide.

Tests

58 new unit tests, all offline and network-free, covering token resolution, board listing (stubbed fetch), the draw.io conversion, and the backup helpers. The new live test skips itself when MIRO_TOKEN isn't set.

pnpm run lint and pnpm run build pass.

One note on CI: test:board-object-types and test:api exit 1 when TEST_BOARD_ID and friends are unset, so the on: push workflow fails on a fork, which has no access to the secrets. That's pre-existing behaviour and I left it alone rather than change a decision that looked deliberate — but I'm happy to make them skip instead if you'd like fork CI to be green.

🤖 Generated with Claude Code

Adds four things on top of the existing frame/board export, plus a fix
for a bug that made most exports fail against real boards.

Fix: the wait for the Miro SDK was hard-coded to 3 seconds, but the SDK
routinely takes ~8.5 seconds to appear on a real board, so every export
that addresses individual objects (all JSON export, all frame-scoped
export) failed with "Miro SDK failed to load in 3000 ms". The timeout is
now configurable via sdkLoadTimeoutMs / --sdk-timeout and defaults to 30
seconds. Whole-board SVG export also now waits for the SDK when it can,
falling back to the previous best-effort behaviour when it never loads,
which measurably increases the detail captured.

The CLI also now exits non-zero when an export fails; previously it
printed the error and exited 0.

MIRO_TOKEN / .env support: the token can come from the environment
instead of --token on every invocation, loaded via Node's own
process.loadEnvFile so no dependency is added. An explicit token wins
over the environment, which wins over .env.

list-boards: lists the boards a token can reach, using the same endpoint
as the Miro dashboard. No browser is involved, so it returns in about a
second. Note that Miro's offset paging on this endpoint is unreliable
(following nextLink can silently stop early), so the whole list is
requested in one page by default and results are de-duplicated by ID.

draw.io export (-e drawio, and an offline `convert` command): converts
board objects to native mxGraph, not an embedded image. Shapes, sticky
notes, text and frames become draw.io cells and connectors become edges
with real source/target, exit/entry anchors and captions.

Three things Miro does not record the way draw.io needs them, each
documented in the README: sticky notes carry no font size (Miro fits
text to the note), connectors carry no waypoints (so a bare curved=1
renders straight), and square sticky notes declare a box 14.57% taller
than it is wide, the extra height being reserve for overflowing text
rather than part of the note. Taking that box at face value makes
vertically adjacent notes overlap even though Miro draws a gap, so
square notes are emitted square.

backup: walks the whole board list into a timestamped directory, one
directory per board, with a manifest. Serial with a configurable pause,
retries with a capped exponential backoff, and --resume to continue an
interrupted run. Board titles are sanitised for use as directory names.

Tests: 60 unit tests covering token resolution, board listing, the
draw.io conversion and the backup helpers, all offline. The live tests
skip themselves when MIRO_TOKEN is not set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@marnusvanwyk459
marnusvanwyk459 force-pushed the feat/list-boards-drawio-export-and-backup branch from ffb3ebd to 2243654 Compare August 14, 2026 11:43
The build appended the shebang with `echo "#!/usr/bin/env node\n$(cat
./build/cli.js)" > ./build/cli.js`, which relies on POSIX command
substitution. Under the shell npm uses on Windows it silently no-ops, so
build/cli.js is produced without a shebang and the installed bin fails to
execute.

Replace it with a `node -e` step that prepends the shebang only when it is
missing, so the build behaves the same on every platform and stays
idempotent across rebuilds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant