Pele 1.6.0 locates a syntax error in the text its parsers see, which is
not quite the diagram's: front matter and %% comment lines are taken out
first and a newline is added at the end, as Mermaid does. So an error
after front matter or a comment was reported lines early, and one at the
end of a diagram a line past its last (a fence's text ends in a newline,
so two past):
render('flowchart LR\n A-->') // PeleError: line 3, snippet ""
The fallback's note now names the source's line. Pele's snippet quotes
the line it means, which is the source's line as written, at or after
the one Pele counted, so diagrams.ts looks for it there; an error at the
end is on the last line with anything on it. If neither works out (an
entity in the line changes its text), Pele's number stays.
This is a workaround, in one function (errorLine), to drop when Pele
reports lines in the source; scenario diagram-error-line holds the
expected lines either way. Not yet reported upstream.- commit
- d08b3dad5c639d304c379802410a9e62fbcaefb8
- parent
- e7033c1310697bee8f81f25a583238f4be4bc6d2
- author
- cat <admin@dev.site>
- ci
- ✓ passed in 76s, signed by cat
- kind
- fix risk score 1.4 of 3, Jev's estimate
- review
- #15 Draw mermaid diagrams at build time, with Pele
ci attestation
forge-attestation 1 type ci commit d08b3dad5c639d304c379802410a9e62fbcaefb8 tree 031e79dbdbe33a14bdca2b2b52c042960d16253c script .forge/ci aa9f4eac26627c6559e68cd4b5e5e76afb8e65e8 result pass exit 0 started 2026-10-19T03:18:28Z duration 76s runner (not shown here; refs/notes/ci has it) system Darwin 25.5.0 arm64 env host log sha256:ec8a96a54be6bf1c15e03e916ece926ec29a5e7e3e4384379d17be282d0c2acc 263 lines |pushed wip/i18n |1123f15 running .forge/ci: greet.sh: say what it is | | sh: /bin/sh | | ok greet |1123f15 pass in 0s, signed |pushed main |remote: review #4: /dami/greet/reviews/4/ |pushed wip/i18n |94c713e running .forge/ci: wave at the world | | sh: /bin/sh | | FAIL greet : got 'hello, world o/', want 'hello, world' |94c713e fail in 0s, signed |forge: 1 commit(s) did not pass |remote: review #6: /dami/greet/reviews/6/ |pushed ana/wave |6187ec9 running .forge/ci: update the test for the wave | | sh: /bin/sh | | ok greet |6187ec9 pass in 0s, signed |remote: review #6: /dami/greet/reviews/6/ |pushed ana/wave |warning: You appear to have cloned an empty repository. |site home: deployed 5dff6eb -> /sites/ana/dotfiles/home/ |created ana/notes |remote: review #1: /ana/notes/reviews/1/ |pushed ana/todo |landing #1 A todo list: ana/todo -> main |cf07218 running .forge/ci: web, server and Python versions of greet | | sh: /bin/sh | | ok greet |cf07218 pass in 0s, signed |pushed main |e2346b9 running .forge/ci: messages: notes for the later regions | | sh: /bin/sh | | ok greet |e2346b9 pass in 0s, signed |remote: review #7: /dami/greet/reviews/7/ |pushed dami/regions |created dami/forge |warning: You appear to have cloned an empty repository. |site guide: mkdir -p guide/fonts && cp site/assets/fonts/*.woff2 guide/fonts/ |site guide: deployed 900f477 -> /sites/dami/forge/guide/ |(node:31592) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built ana/dotfiles in 0s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.7CaqyA/wt/demo/root/.forge/www |(node:31721) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built ana/notes in 1s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.7CaqyA/wt/demo/root/.forge/www |(node:31861) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built dami/forge in 1s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.7CaqyA/wt/demo/root/.forge/www |(node:32000) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built dami/greet in 1s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.7CaqyA/wt/demo/root/.forge/www |(node:32989) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built - in 0s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.7CaqyA/wt/demo/root/.forge/www |234 of 234 pages render the same without 11ty |guide: 8 files, links ok |ok
Signature: good, cat
| M | lib/ | 24 | ++++++++++++++++++++++- |
| M | test/ | 2 | +- |
| A | test/ | 32 | ++++++++++++++++++++++++++++++ |
@@ -74,6 +74,25 @@ const SVG: sanitize.IOptions = {
7474 parser: { lowerCaseAttributeNames: false },7575 };7676 77+// Which line of the source a syntax error is on. Pele 1.6.0 counts lines in78+// the text it parses, which is the source less its front matter and %%79+// comment lines, plus a newline, as Mermaid does: an error after either is80+// reported lines early, and one at the end a line past the last. The line81+// its snippet quotes is the source's, so find it, at or after the line82+// Pele counted; at the end, it is the last line with anything on it. Drop83+// this when Pele reports lines in the source (upstream: see the commit that84+// added it).85+function errorLine(src: string, e: PeleError): number {86+ const lines = src.split(/\r\n?|\n/);87+ const quoted = e.snippet.split('\n')[0];88+ if (quoted.trim() === '') {89+ const last = lines.findLastIndex((l) => l.trim() !== '') + 1;90+ return last > 0 && e.line > last ? last : e.line;91+ }92+ const at = lines.findIndex((l, i) => i >= e.line - 1 && l === quoted);93+ return at === -1 ? e.line : at + 1;94+}95+7796 const escape = (s: string): string => s.replace(/[&<>"]/g, (c) => `&${{ '&': 'amp', '<': 'lt', '>': 'gt', '"': 'quot' }[c]};`);7897 7998 // The width a phone gives a diagram, about. One drawn wider than this gets@@ -104,7 +123,10 @@ export function diagram(src: string, href: (url: string) => string = (u) => u):
104123 } catch (e) {105124 // Pele's message names the line, and can quote it: shown as text.106125 let why = 'Pele failed on it';107- if (e instanceof PeleError) why = e.message.split('\n')[0].replace(/[.:]$/, '').slice(0, 160);126+ if (e instanceof PeleError) {127+ why = e.message.split('\n')[0].replace(/[.:]$/, '').slice(0, 160);128+ if (e.code === 'syntax' && e.line) why = why.replace(/ on line \d+/, ` on line ${errorLine(src, e)}`);129+ }108130 return `<figure class="diagram-source"><pre><code class="language-mermaid">${escape(src)}</code></pre><figcaption>Not drawn: ${escape(why)}.</figcaption></figure>\n`;109131 }110132 const clean = sanitize(svg, {@@ -27,7 +27,7 @@ build
2727 p=$FORGE_WWW/dami/greet/index.html2828 has "$p" '<figure class="diagram-source"><pre><code class="language-mermaid">flowchart LR'2929 has "$p" ' start\[start\] -->'30-has "$p" '<figcaption>Not drawn: Parse error on line [0-9]+\.</figcaption></figure>'30+has "$p" '<figcaption>Not drawn: Parse error on line 2\.</figcaption></figure>'3131 has "$p" '<code class="language-mermaid">not a diagram <b>at all</b>'3232 has "$p" '<figcaption>Not drawn: No diagram type detected for the given text\.</figcaption>'3333 has "$p" 'Between the diagrams\.'@@ -0,0 +1,32 @@
1+# A diagram's syntax error names the line of the diagram it is on, counting2+# its front matter and comment lines, and an error at the end names the3+# last line. Pele 1.6.0 counts lines in the text it parses, without those,4+# plus a newline; lib/diagrams.ts finds the line it means.5+. "$(dirname "$0")/../lib.sh"6+user dami7+repo dami/greet8+clone dami dami/greet "$T/w/d"9+cat >"$T/w/d/README.md" <<'EOF'10+# greet11+12+```mermaid13+---14+title: Push15+---16+%% the hooks, in order17+flowchart LR18+ push --> pre19+ pre --> -->20+```21+22+```mermaid23+flowchart LR24+ push --> pre -->25+```26+EOF27+at "$T/w/d" commit "first" greet.sh28+push_as dami "$T/w/d" origin main29+build30+p=$FORGE_WWW/dami/greet/index.html31+eq "$(grep -o '<figcaption>Not drawn: [^<]*' "$p" | sed 's/<figcaption>//')" 'Not drawn: Parse error on line 7.32+Not drawn: Parse error on line 2.'