Files
computer-playbook/scripts/lint/mermaid.sh
kevinveenbirkenbachandClaude Opus 5.5 c8cb7fda8c fix(lint): provision the mermaid browser in install-lint, not in the parallel lint run
make lint runs install-lint once and then dispatches its fifteen targets
in parallel. scripts/lint/mermaid.sh provisioned chrome-headless-shell
inside that parallel phase with `npx puppeteer browsers install
--install-deps`, whose `apt-get satisfy` carries no lock timeout. In a
fresh stack container another target held the apt lock, the shared
libraries stayed missing, the script only warned, and all 289 Markdown
files then failed on libnspr4.so (#795).

utils/install/lint/mermaid_cli.py now provisions the browser right after
mmdc, inside the serial install-lint step, as root with --install-deps
and as a user without it, because puppeteer refuses to install libraries
without root. A failed install deletes the chrome-headless-shell cache
folder and retries once under a 900 second timeout per attempt; a cut-off
download otherwise leaves a folder without its executable, and puppeteer
refuses every later install of that build. The install-lint stamp counts
as fresh only while the browser executable exists, so a recreated
container provisions it again.

mermaid.sh no longer calls apt-get or npx. It renders one minimal diagram
first and, when that fails, exits with mmdc's output instead of reporting
every Markdown file.

Verified: tests/unit/python/scripts/lint/test_mermaid.py stubs mmdc,
npx and apt-get. With a browser that cannot load libnspr4.so the script
exits 1 with that line, before any Markdown file renders, and mmdc is the
only tool it calls. Against main's mermaid.sh the same test fails: it
calls apt-get and npx and reports the files one by one. The installer and
stamp cases pass, shellcheck is clean.

Not verified: a full make lint in a recreated stack container. The race
reproduced in one of two runs in the issue.

feature/store-i18n changed the provisioning in mermaid.sh with 2f5f52bd1a
and dc67bce5f0 (timeout, deleting a cut-off browser). Both behaviours
moved into mermaid_cli.py here, so its merge takes this version of
mermaid.sh.

Closes #795

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:31:36 +02:00

79 lines
2.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck shell=bash
#
# Validate every Mermaid diagram in the repository's Markdown by rendering it
# with @mermaid-js/mermaid-cli (mmdc). A diagram GitHub cannot render (syntax
# error, reserved-word node id, ...) makes mmdc exit nonzero, failing the lint.
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"
# shellcheck source=scripts/meta/env/load.sh
source scripts/meta/env/load.sh
if ! command -v mmdc >/dev/null 2>&1; then
echo "mmdc (@mermaid-js/mermaid-cli) not installed. Run 'make install-lint' first." >&2
exit 127
fi
workdir="$(mktemp -d)"
trap 'rm -rf "${workdir}"' EXIT
puppeteer_cfg="${workdir}/puppeteer-config.json"
printf '%s\n' '{"args":["--no-sandbox","--disable-gpu"]}' >"${puppeteer_cfg}"
mapfile -t md_files < <(grep -rlF --include='*.md' '```mermaid' . 2>/dev/null | sort || true)
if [[ ${#md_files[@]} -eq 0 ]]; then
echo "No Markdown files contain a mermaid block; nothing to render."
exit 0
fi
printf '%s\n' 'flowchart LR' ' a --> b' >"${workdir}/preflight.mmd"
if ! mmdc --puppeteerConfigFile "${puppeteer_cfg}" -i "${workdir}/preflight.mmd" \
-o "${workdir}/preflight.svg" >"${workdir}/preflight.log" 2>&1; then
printf "mmdc cannot render a minimal diagram, so none of the %d Markdown file(s) was checked. 'make install-lint-force' provisions its browser:\n" \
"${#md_files[@]}" >&2
sed 's/^/ /' "${workdir}/preflight.log" >&2
exit 1
fi
: "${INFINITO_WORKER_CPU:?INFINITO_WORKER_CPU must be set (provided by default.env via the env loader)}"
jobs="${INFINITO_WORKER_CPU}"
# shellcheck disable=SC2329,SC2317 # invoked indirectly through xargs + bash -c
render_one() {
local md="$1"
local slug
slug="$(printf '%s' "${md}" | tr '/.' '__')"
if ! mmdc --puppeteerConfigFile "${puppeteer_cfg}" -i "${md}" \
-o "${workdir}/${slug}.out.md" >"${workdir}/${slug}.log" 2>&1; then
printf '%s\n' "${md}" >"${workdir}/${slug}.fail"
fi
}
export -f render_one
export workdir puppeteer_cfg
# shellcheck disable=SC2016 # the single-quoted body expands inside the child bash
printf '%s\n' "${md_files[@]}" |
xargs -P "${jobs}" -I {} bash -c 'render_one "$1"' _ {}
status=0
for fail in "${workdir}"/*.fail; do
[[ -e "${fail}" ]] || continue
status=1
md="$(cat "${fail}")"
printf '❌ %s\n' "${md}"
sed 's/^/ /' "${fail%.fail}.log"
done
if [[ "${status}" -eq 0 ]]; then
printf '✅ %d Markdown file(s) with mermaid render cleanly.\n' "${#md_files[@]}"
fi
exit "${status}"