/excalidraw-brute-export-cli

💪🔨🎭 CLI to export Excalidraw diagrams to svg/png files, using playwright+firefox.

Primary LanguageJavaScriptMIT LicenseMIT

Excalidraw Brute Export CLI

Audience: Developers Platform: Linux

🎇Features  •  🏠Installation  •  🚜Usage  •  💻CLI

✅Requirements  •  🐳Docker  •  🚸Gotchas

Top language GitHub License npm - version

Node Version

Export Excalidraw diagrams to SVG or PNG using a headless browser, using the exact same export process as Excalidraw itself


Status Stable Unstable
Master Build and Test since tagged last commit
Develop Build and Test since tagged since tagged last commit

Demo

  • ❔ What: Uses 🎭 playwright to run a headless firefox browser to export Excalidraw diagrams to svg/png files. Using a browser bypasses certain bugs that happen with other projects that attempt to export by emulating the DOM (without a browser).
  • Why:
    • To allow automated export of Excalidraw diagrams to svg/png files via the command line.
    • Currently, Excalidraw can only be exported by a human clicking on the "Export image" button.
    • Addresses/mitigates excalidraw/excalidraw#1261 excalidraw CLI #1261 which is an open feature request on the Excalidraw project.
    • Addresses/mitigates JRJurman/excalidraw-to-svg#6 Error rendering edge-labels. #6.
    • Addresses/mitigates Timmmm/excalidraw_export#6 Error rendering edge-labels. #6.
  • 🤝 Related Projects
    • ⭐⭐⭐⭐⭐ JRJurman/excalidraw-to-svg uses jsdom to simulate the DOM, then runs Excalidraw+react in nodejs, loads the diagram files and exports them.
      • Comparison: JRJurman/excalidraw-to-svg is faster and more efficient than excalidraw-brute-export-cli. However, excalidraw-brute-export-cli is a "brute force" approach to exporting Excalidraw diagrams, and in some ways might be more reliable.
    • ⭐⭐⭐⭐⭐ Timmmm/excalidraw_export similar to JRJurman/excalidraw-to-svg but simplifies the code and also embeds SVG fonts.
      • Comparison: Timmmm/excalidraw_export is faster and more efficient than excalidraw-brute-export-cli. However, excalidraw-brute-export-cli is a "brute force" approach to exporting Excalidraw diagrams, and in some ways might be more reliable.

🎇 Features

  • Export Excalidraw diagrams to SVG or PNG using a headless browser, using the exact same export process as Excalidraw itself.
  • Can point to any excalidraw instance.
  • Ability to change timeouts.
  • Debugging: Ability to take screenshots at each step.

🏠 Installation

# Install globally from npm registry.
npm install -g excalidraw-brute-export-cli

# Or install globally, direct from GitHub:
npm install -g https://github.com/realazthat/excalidraw-brute-export-cli.git#v0.4.0

# Might prompt for root.
npx playwright install-deps
npx playwright install firefox

🚜 Usage

Example:

npx excalidraw-brute-export-cli \
  -i ./examples/simple.excalidraw \
  --background 1 \
  --embed-scene 0 \
  --dark-mode 0 \
  --scale 1 \
  --format svg \
  -o "./examples/simple_example_output.svg"

ls "./examples/simple_example_output.svg"

Output of `bash ./examples/simple_example.sh`

And the resulting image (svg):

Simple Excalidraw Diagram as a SVG

💻 Command Line Options

Output of `npx excalidraw-brute-export-cli --help`

🐳 Running Excalidraw locally

# First build the image. Do this once.
git clone https://github.com/excalidraw/excalidraw.git
cd excalidraw
git checkout "v0.15.0"
docker build -t "my-excalidraw-image:v0.15.0" .

# Now we'll run Excalidraw in a container instance.

# Delete the old instance if it exists.
docker rm -f "your-instance-name" || true

# Replace 59876 with your desired port.
docker run -dit --name "your-instance-name" -p 59876:80 "my-excalidraw-image:v0.15.0"

# Visit your instance at http://localhost:59876

# Use the --url option to point to your instance.
npx excalidraw-brute-export-cli \
  ...
  --url "http://localhost:59876" \
  ...

# Or, set `EXCALIDRAW_BRUTE_EXPORT_CLI_URL` in your environment and leave out
# the --url option.

✅ Requirements

  • Tested with latest version of https://excalidraw.com as of 2024/05/05, and Excalidraw tag v0.15.0 for more consistent output, and testing.
  • Supported Node versions: >=18.0.0 <19.0.0 || >=20.0.0 <21.0.0 || >=21.0.0 <22.0.0 || >=22.0.0 <23.0.0 (See ./package.json). These versions were chosen from current supported and upcoming versions of node, from Node.js: Previous Releases.
  • Tested Node versions on GitHub Actions: ["18.20.2","20.12.1","21.7.3","22.0.0"].

Tested on

  • WSL2 Ubuntu 20.04, Node v20.12.1 using Excalidraw at tag v0.15.0.

🐳 Docker Image

Docker images are published to [ghcr.io/realazthat/excalidraw-brute-export-cli][49] at each tag.

# Use the published images at ghcr.io/realazthat/snipinator.
# /data in the docker image is the working directory, so paths are simpler.
docker run --rm --tty \
  -u "$(id -u):$(id -g)" \
  -v "${PWD}:/data" \
  ghcr.io/realazthat/excalidraw-brute-export-cli:v0.4.0 \
  -i ./examples/simple.excalidraw \
  --background 1 \
  --embed-scene 0 \
  --dark-mode 0 \
  --scale 1 \
  --format svg \
  -o "./examples/simple_example_output.svg"

ls "./examples/simple_example_output.svg"

If you want to build the image yourself, you can use the Dockerfile in the repository.

docker build -t my-excalidraw-brute-export-cli-image .

# /data in the docker image is the working directory, so paths are simpler.
docker run --rm --tty \
  -u "$(id -u):$(id -g)" \
  -v "${PWD}:/data" \
  my-excalidraw-brute-export-cli-image \
  -i ./examples/simple.excalidraw \
  --background 1 \
  --embed-scene 0 \
  --dark-mode 0 \
  --scale 1 \
  --format svg \
  -o "./examples/simple_example_output.svg"

ls "./examples/simple_example_output.svg"

🚸 Gotchas and Limitations

  • Sometimes playwright times out.
    • Mitigations:
      • Increase the timeout with the --timeout option.
      • Run the command again.
    • If this is a persistent problem, please open an issue here and upload the diagram (zip it if necessary).

🤏 Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

🔑 License

This project is licensed under the MIT License - see the ./LICENSE.md file for details.

🫡 Contributions

Development environment: Linux-like

  • For running pre.sh (Linux-like environment).

    • From ./.github/dependencies.yml, which is used for the GH Action to do a fresh install of everything:

      bash: scripts.
      findutils: scripts.
      grep: tests.
      xxd: tests.
      git: scripts, tests.
      xxhash: scripts (changeguard).
      rsync: out-of-directory test.
      expect: for `unbuffer`, useful to grab and compare ansi color symbols.
      jq: dependency for [yq](https://github.com/kislyuk/yq), which is used to generate
        the README; the README generator needs to use `tomlq` (which is a part of `yq`)
        to query `pyproject.toml`.
      
    • Requires pyenv, or an exact matching version of python as in scripts/.python-version (which is currently 3.8.18 ).

    • jq, (installation) required for yq, which is itself required for our ./README.md generation, which uses tomlq (from the yq package) to include version strings from ./scripts/pyproject.toml.

    • act (to run the GH Action locally):

      • Requires nodejs.
      • Requires Go.
      • docker.
    • Generate animation:

      • docker
    • Tests

      • docker, to serve Excalidraw.

Commit Process

  1. (Optionally) Fork the develop branch.
  2. If the .github/demo.gif will change, run bash ./scripts/generate-animation.sh, this will generate a new .github/demo.gif.
    • Sanity-check the animation visually!
    • Unfortunately, every run will make a unique gif, please don't stage this file unless it changes due to some feature change or somesuch.
  3. Stage your files: e.g git add path/to/file.py.
  4. bash ./scripts/pre.sh, this will format, lint, and test the code.
  5. git status check if anything changed (generated README.md for example), if so, git add the changes, and go back to the previous step.
  6. git commit -m "...".
  7. Make a PR to develop (or push to develop if you have the rights).

🔄🚀 Release Process

These instructions are for maintainers of the project.

  1. In the develop branch, run bash ./scripts/pre.sh to ensure everything is in order.
  2. In the develop branch, bump the version in package.json, following semantic versioning principles. Run bash ./scripts/pre.sh to ensure everything is in order.
    • If anything got generated (e.g README or terminal output images), you will have to stage those.
  3. In the develop branch, commit these changes with a message like "Prepare release X.Y.Z". (See the contributions section above).
  4. Merge the develop branch into the master branch: git checkout master && git merge develop --no-ff.
  5. master branch: Tag the release: Create a git tag for the release with git tag -a vX.Y.Z -m "Version X.Y.Z".
  6. Publish to NPM: Publish the release to NPM with bash ./scripts/deploy-to-npm.sh.
  7. Push to GitHub: Push the commit and tags to GitHub with git push && git push --tags.
  8. The --no-ff option adds a commit to the master branch for the merge, so refork the develop branch from the master branch: git checkout develop && git merge master.
  9. Push the develop branch to GitHub: git push origin develop.