diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..7d6ce28 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,43 @@ +name: pre-commit + +on: + push: + branches-ignore: + - main + +jobs: + pre-commit: + runs-on: ubuntu-latest + + container: + image: python:3.11-bullseye + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Mark workspace as safe for git + run: | + export HOME=/github/home + git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install system dependencies + run: | + apt-get update + apt-get install -y \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-dev \ + libcups2 \ + libdbus-1-dev \ + libgbm1 \ + libgtk-3-0 \ + libimage-exiftool-perl \ + libnss3 \ + xvfb + + - name: Install pre-commit + run: pip3 install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6aa9eae --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: no-commit-to-branch + - id: check-merge-conflict + - id: check-yaml + - id: trailing-whitespace + - id: check-symlinks + - id: end-of-file-fixer + - id: mixed-line-ending + + - repo: local + hooks: + - id: drawio-auto-export + name: Draw.io Auto Export + description: >- + Automatically exports .drawio files in repository into .png whenever you commit changes + entry: hooks/drawio-auto-export.sh + language: script + files: \.drawio$ diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..8935fd7 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,7 @@ +- id: drawio-auto-export + name: Draw.io Auto Export + description: >- + Automatically exports .drawio files in repository into .png whenever you commit changes + entry: hooks/drawio-auto-export.sh + language: script + files: \.drawio$ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1332768 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Latest Version +1.0.0 + +# Version History +## Version 1.0.0 (2025-01-28) (devon-thyne) +### Initial Version +* Pre-commit hook that will auto-export `.drawio` files in repository to `.png` whenever you commit changes +* Done so with a thin and simple bash script diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e02d16 --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +pre-commit drawio auto-export +============================= + +This pre-commit hook automatically exports [Draw.io](https://www.drawio.com/) (`.drawio`) files in repository into `.png` whenever you commit changes. + +**Table of Contents:** + +- [Installation](#installation) +- [Features](#features) +- [Requirements](#requirements) +- [Supported Platforms](#supported-platforms) +- [CI/CD Examples](#cicd-examples) + * [GitHub](#github) + * [Gitlab](#gitlab) +- [Acknowledgements](#acknowledgements) + +## Installation + +Add the hook to your `.pre-commit-config.yaml`: + +```yaml +repos: + - repo: https://github.com/devon-thyne/pre-commit-drawio-auto-export.git + rev: v1.0.0 + hooks: + - id: drawio-auto-export +``` + +## Features + +- Automatically detects all `.drawio` files within repository +- Exports diagram file(s) to `.png` using the draw.io CLI (AppImage for Linux, Desktop app for macOS) + - Exports multi-paged diagram files as multiple `.png` image(s) each suffixed with the diagram page's name +- Fails if any files are generated net-new or modified +- Automatically stages changes to new or modified exported files + +## Requirements + +* BASH interpreter +* `exiftool` +* `xvfb` (linux only) +* `Draw.io Desktop` (macos only) + +## Supported Platforms + +### Linux +- [xvfb](https://linux.die.net/man/1/xvfb) must be installed. +- **Note:** the script will automatically download and cache the Draw.io AppImage if not already present. + - path: `~/.cache/pre-commit/drawio` + +### macOS +- [Draw.io Desktop](https://github.com/jgraph/drawio-desktop/releases) must be installed in `/Applications`. +- **Note:** exports briefly display the Draw.io icon in the Dock due to macOS constraints + +### Windows +- **not supported** +- use windows subsystem for linux + +## CI/CD Examples + +> [!TIP] +> Suggest pre-building your own pre-commit docker image with the necessary dependencies + +### GitHub + +The following example demonstrates how to run this hook in GitHub Actions. +Trigger conditions (e.g. push, pull_request, branches) are intentionally left to the user. + +```yaml +name: pre-commit + +jobs: + pre-commit: + runs-on: ubuntu-latest + + container: + image: python:3.11-bullseye + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Mark workspace as safe for git + run: | + export HOME=/github/home + git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install system dependencies + run: | + apt-get update + apt-get install -y \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-dev \ + libcups2 \ + libdbus-1-dev \ + libgbm1 \ + libgtk-3-0 \ + libimage-exiftool-perl \ + libnss3 \ + xvfb + + - name: Install pre-commit + run: pip3 install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files +``` + +### Gitlab + +The following example demonstrates how to run this hook in GitLab CI/CD. +Pipeline triggers and execution rules (e.g. branches, merge requests) should be defined by the user. + +```yaml +pre-commit: + stage: pre-commit + image: python:3.11-bullseye + script: + - apt update + - | + apt install -y \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-dev \ + libcups2 \ + libdbus-1-dev \ + libgbm1 \ + libgtk-3-0 \ + libimage-exiftool-perl \ + libnss3 \ + xvfb + - pip3 install pre-commit + - pre-commit run --all-files +``` + +## Acknowledgements + +This project was inspired by an early CI/CD-based Draw.io export workflow shared by a colleague. That initial work sparked the idea to formalize and extend the approach into a reusable, pre-commit–based tool with additional logic around change detection, local generation, and reproducibility. diff --git a/hooks/drawio-auto-export.sh b/hooks/drawio-auto-export.sh new file mode 100755 index 0000000..f1c0c61 --- /dev/null +++ b/hooks/drawio-auto-export.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +# Synopsis: pre-commit script that exports .drawio files to `.png` whenever you commit changes +set -euo pipefail + +OS="$(uname -s)" +ARCH="$(uname -m)" + +FORMAT="png" +DIAGRAMS=() +CHANGES_DETECTED=0 + +# Parse Arguments +while [[ $# -gt 0 ]]; do + case "$1" in + *.drawio) + DIAGRAMS+=("$1") + shift + ;; + *) + echo "[ERROR]: Unknown option: $1" >&2 + echo "Usage: $0 ..." >&2 + exit 1 + ;; + esac +done + +# Check Requirements +if command -v exiftool >/dev/null 2>&1; then + EXIFTOOL="$(command -v exiftool)" +elif [ -x /usr/local/bin/exiftool ]; then + EXIFTOOL=/usr/local/bin/exiftool +elif [ -x /opt/homebrew/bin/exiftool ]; then + EXIFTOOL=/opt/homebrew/bin/exiftool +else + echo "[ERROR]: exiftool not found. Install it or add it to PATH." >&2 + exit 1 +fi +if [[ "$OS" == "Linux" ]]; then + if ! command -v xvfb-run &>/dev/null; then + echo "[ERROR]: xvfb-run is required but not installed." >&2 + exit 1 + fi +fi + +# Diagram Files Input Validation +if [[ ${#DIAGRAMS[@]} -eq 0 ]]; then + exit 0 +fi +echo "Diagrams to convert ($FORMAT):" +for d in "${DIAGRAMS[@]}"; do + echo " - $d" +done +echo "" + +# Functions +sha256() { + git show ":$1" | sha256sum | awk '{print $1}' +} + +check_diagram_changes() { + local tmpfile="$1" + local out="$2" + local diagram_hash="$3" + local before_hash="$4" + local before_perm="$5" + + "$EXIFTOOL" -Comment="drawio-diagram-hash:$diagram_hash" "$tmpfile" >/dev/null 2>&1 + + if [[ "$before_hash" != "n/a" ]]; then + mv -f "$tmpfile" "$out" + chmod "$before_perm" "$out" + echo "[ALERT]: file has changed - $out" + CHANGES_DETECTED=1 + else + mv "$tmpfile" "$out" + echo "[ALERT]: new file - $out" + CHANGES_DETECTED=1 + fi +} + +process_diagram_page() { + local diagram="$1" + local drawio_binary="$2" + local output_file="$3" + local page_index="${4:-1}" + + local tmpfile=$(mktemp) + local diagram_hash=$(sha256 "$diagram") + local before_hash="n/a" + local before_perm="n/a" + + if [[ -f "$output_file" ]]; then + before_hash=$("$EXIFTOOL" -Comment -s3 "$output_file" | sed 's/^drawio-diagram-hash://') + if [[ "$OS" == "Linux" ]]; then + before_perm=$(stat -c "%a" "$output_file") + else + before_perm=$(stat -f "%Lp" "$output_file") + fi + fi + if [[ "$diagram_hash" != "$before_hash" ]]; then + if [[ "$OS" == "Linux" ]]; then + xvfb-run -a "$drawio_binary" \ + --export --page-index "$page_index" --format "$FORMAT" --output "$tmpfile" "$diagram" \ + --disable-gpu --headless --no-sandbox >/dev/null 2>&1 + else + "$drawio_binary" \ + --export --page-index "$page_index" --format "$FORMAT" --output "$tmpfile" "$diagram" \ + --disable-gpu --headless --no-sandbox >/dev/null 2>&1 + fi + check_diagram_changes "$tmpfile" "$output_file" "$diagram_hash" "$before_hash" "$before_perm" + fi +} + +process_diagram() { + local diagram="$1" + local drawio_binary="$2" + + local output_file="${diagram%.drawio}.${FORMAT}" + local diagram_pages="$(\ + cat "$diagram" | grep '/dev/null 2>&1) + fi + for d in "${DIAGRAMS[@]}"; do + process_diagram "$d" "$APPIMAGE_DIR/squashfs-root/drawio" + done + +# MacOS Export +elif [[ "$OS" == "Darwin" ]]; then + DRAWIO_BIN="/Applications/draw.io.app/Contents/MacOS/draw.io" + if [[ ! -x "$DRAWIO_BIN" ]]; then + echo "[ERROR]: Draw.io app not found in /Applications. Please install it." >&2 + exit 1 + fi + for d in "${DIAGRAMS[@]}"; do + process_diagram "$d" "$DRAWIO_BIN" + done + +else + echo "[ERROR]: Unsupported OS - $OS" >&2 + exit 1 +fi + +if [[ $CHANGES_DETECTED -eq 1 ]]; then + exit 1 +fi diff --git a/tests/test-multiple-pages--page-1.png b/tests/test-multiple-pages--page-1.png new file mode 100644 index 0000000..ae6e4d1 Binary files /dev/null and b/tests/test-multiple-pages--page-1.png differ diff --git a/tests/test-multiple-pages--page-2.png b/tests/test-multiple-pages--page-2.png new file mode 100644 index 0000000..1e05cf0 Binary files /dev/null and b/tests/test-multiple-pages--page-2.png differ diff --git a/tests/test-multiple-pages.drawio b/tests/test-multiple-pages.drawio new file mode 100644 index 0000000..9e5740a --- /dev/null +++ b/tests/test-multiple-pages.drawio @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test-single-page.drawio b/tests/test-single-page.drawio new file mode 100644 index 0000000..0ae39c5 --- /dev/null +++ b/tests/test-single-page.drawio @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/test-single-page.png b/tests/test-single-page.png new file mode 100644 index 0000000..0039cb0 Binary files /dev/null and b/tests/test-single-page.png differ