From a1edde3c4aa3ef8aab60ea527827654a5710c8ba Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 14:47:32 +0100 Subject: [PATCH 01/14] Correctly handle tar.exe on Windows to use System32 one if available --- src/core/zip.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/zip.ts b/src/core/zip.ts index 33cdeeeb5e9..b9bc8170c91 100644 --- a/src/core/zip.ts +++ b/src/core/zip.ts @@ -40,8 +40,18 @@ export function unzip(file: string, dir?: string) { } } else { // use the tar command to untar this + // On Windows, prefer System32 tar to avoid Git Bash tar path issues + let tarCmd = "tar"; + if (isWindows) { + const systemRoot = Deno.env.get("SystemRoot") || "C:\\Windows"; + const system32Tar = `${systemRoot}\\System32\\tar.exe`; + if (existsSync(system32Tar)) { + tarCmd = system32Tar; + } + // Otherwise fall back to "tar" in PATH + } return execProcess( - { cmd: "tar", args: ["xfz", file], cwd: dir, stdout: "piped" }, + { cmd: tarCmd, args: ["xfz", file], cwd: dir, stdout: "piped" }, ); } } From b20e0017313bb8d6467400dddbce6e3c7aca6b9d Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 14:48:05 +0100 Subject: [PATCH 02/14] Add private dev stuff in gitignored --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 918ce8d503b..a4012725b0e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ package/dist/** /tests/test-out.json *~ .env +.private-journal/ # deno_std library src/resources/deno_std/cache src/vendor-* From 1de9915d32251870bb476cef0c34e987406cfd3d Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 15:15:28 +0100 Subject: [PATCH 03/14] Add CI workflow to check regex.json in tinytex and detect changes --- .../templates/tinytex-comment-body.md | 12 ++ .../workflows/templates/tinytex-issue-body.md | 32 +++++ .github/workflows/verify-tinytex-patterns.yml | 130 ++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 .github/workflows/templates/tinytex-comment-body.md create mode 100644 .github/workflows/templates/tinytex-issue-body.md create mode 100644 .github/workflows/verify-tinytex-patterns.yml diff --git a/.github/workflows/templates/tinytex-comment-body.md b/.github/workflows/templates/tinytex-comment-body.md new file mode 100644 index 00000000000..e36410e4842 --- /dev/null +++ b/.github/workflows/templates/tinytex-comment-body.md @@ -0,0 +1,12 @@ +## Update: {{DATE}} + +New pattern changes detected. + +
+Click to expand diff + +```diff +{{DIFF}} +``` + +
diff --git a/.github/workflows/templates/tinytex-issue-body.md b/.github/workflows/templates/tinytex-issue-body.md new file mode 100644 index 00000000000..e2d72e95577 --- /dev/null +++ b/.github/workflows/templates/tinytex-issue-body.md @@ -0,0 +1,32 @@ +## TinyTeX Pattern Update: {{DATE}} + +The daily TinyTeX regex patterns have changed and need review. + +### Pattern Diff + +
+Click to expand diff + +```diff +{{DIFF}} +``` + +
+ +### Next Steps + +See [dev-docs/tinytex-pattern-maintenance.md](https://github.com/quarto-dev/quarto-cli/blob/main/dev-docs/tinytex-pattern-maintenance.md) for detailed instructions. + +**Review checklist:** + +- [ ] Review diff for significant changes +- [ ] Determine if patterns need adaptation +- [ ] Update `parse-error.ts` if needed +- [ ] Add/update filter functions +- [ ] Run tests: `unit\latexmk\parse-error.test.ts` +- [ ] Add test cases for new patterns if needed +- [ ] Close this issue when complete + +--- + +_Generated by [verify-tinytex-patterns.yml](https://github.com/quarto-dev/quarto-cli/blob/main/.github/workflows/verify-tinytex-patterns.yml)_ diff --git a/.github/workflows/verify-tinytex-patterns.yml b/.github/workflows/verify-tinytex-patterns.yml new file mode 100644 index 00000000000..76f9c3690d9 --- /dev/null +++ b/.github/workflows/verify-tinytex-patterns.yml @@ -0,0 +1,130 @@ +name: Verify TinyTeX Pattern Coverage + +on: + schedule: + - cron: '0 2 * * *' # Daily 2am UTC (matches TinyTeX daily release) + workflow_dispatch: # Manual trigger for testing + +permissions: + contents: read + issues: write + +jobs: + verify: + name: Check TinyTeX Pattern Updates + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download and extract regex.json + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release download daily --repo rstudio/tinytex-releases --pattern "regex.tar.gz" + tar -xzf regex.tar.gz + echo "✓ Downloaded and extracted regex.json" + + - name: Restore cached regex.json + id: cache-restore + uses: actions/cache/restore@v4 + with: + path: .cache/regex.json + key: tinytex-regex-latest + + - name: Compare versions + id: compare + run: | + if [ -f .cache/regex.json ]; then + if git diff --no-index --quiet .cache/regex.json regex.json; then + echo "changed=false" >> $GITHUB_OUTPUT + echo "✓ No changes detected" + else + echo "changed=true" >> $GITHUB_OUTPUT + echo "✗ Changes detected" + git diff --no-index .cache/regex.json regex.json > pattern-diff.txt || true + fi + else + echo "changed=true" >> $GITHUB_OUTPUT + echo "⚠ No cached version (first run)" + echo "First run - no previous version to compare" > pattern-diff.txt + fi + + - name: Exit if unchanged + if: steps.compare.outputs.changed == 'false' + run: | + echo "No pattern changes detected. Cache hit - exiting." + exit 0 + + - name: Prepare cache directory + if: steps.compare.outputs.changed == 'true' + run: | + mkdir -p .cache + cp regex.json .cache/regex.json + + - name: Prepare readable diff + if: steps.compare.outputs.changed == 'true' + run: | + # Pretty-print both JSON files for readable diff + if [ -f .cache/regex.json ]; then + jq --sort-keys . .cache/regex.json > old-formatted.json + jq --sort-keys . regex.json > new-formatted.json + git diff --no-index old-formatted.json new-formatted.json > readable-diff.txt || true + else + jq --sort-keys . regex.json > new-formatted.json + echo "First run - no previous version to compare" > readable-diff.txt + fi + + - name: Create or update issue + if: steps.compare.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + ISSUE_TITLE="TinyTeX patterns require review" + CURRENT_DATE=$(date +%Y-%m-%d) + + # Search for existing open issue + ISSUE_NUM=$(gh issue list \ + --label "tinytex-patterns" \ + --state open \ + --json number,title \ + --jq ".[] | select(.title == \"$ISSUE_TITLE\") | .number") + + if [ -z "$ISSUE_NUM" ]; then + echo "No matching issue found, creating new one..." + + # Use template and replace placeholders + sed "s|{{DATE}}|$CURRENT_DATE|g" .github/workflows/templates/tinytex-issue-body.md | \ + sed -e "/{{DIFF}}/r readable-diff.txt" -e "/{{DIFF}}/d" > issue-body.md + + gh issue create \ + --title "$ISSUE_TITLE" \ + --assignee cderv \ + --label "tinytex-patterns" \ + --body-file issue-body.md + else + echo "Found existing issue #$ISSUE_NUM, adding comment..." + + # Use template and replace placeholders + sed "s|{{DATE}}|$CURRENT_DATE|g" .github/workflows/templates/tinytex-comment-body.md | \ + sed -e "/{{DIFF}}/r readable-diff.txt" -e "/{{DIFF}}/d" > comment-body.md + + gh issue comment "$ISSUE_NUM" --body-file comment-body.md + fi + + - name: Save new cache + if: steps.compare.outputs.changed == 'true' + uses: actions/cache/save@v4 + with: + path: .cache/regex.json + key: tinytex-regex-latest + + - name: Summary + if: always() + run: | + if [ "${{ steps.compare.outputs.changed }}" == "true" ]; then + echo "✗ Pattern changes detected - issue created/updated" + else + echo "✓ No pattern changes - cache hit" + fi From 8d529c739121783d9615ca82c109a6528d2f53ae Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 15:33:00 +0100 Subject: [PATCH 04/14] Add a dev-doc to explain the logic --- dev-docs/tinytex-pattern-maintenance.md | 105 ++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 dev-docs/tinytex-pattern-maintenance.md diff --git a/dev-docs/tinytex-pattern-maintenance.md b/dev-docs/tinytex-pattern-maintenance.md new file mode 100644 index 00000000000..927b278a747 --- /dev/null +++ b/dev-docs/tinytex-pattern-maintenance.md @@ -0,0 +1,105 @@ +# Quarto LaTeX engine Pattern Maintenance + +Quarto tracks **tinytex** R package's LaTeX error detection patterns to provide helpful diagnostics when LaTeX compilation fails. This document describes the automated verification process and manual adaptation workflow. + +## Overview + +The R package **tinytex** maintains a comprehensive database of LaTeX error patterns in its parsing error logic, and export this in `regex.json` in its daily release. It can detect missing packages and fonts. We track these patterns because: + +- TinyTeX is the distribution maintain by Posit team actively maintains patterns based on user reports (@yihui and @cderv) +- It is used by Quarto (`quarto install tinytex`) +- Every problem will be fixed in the R package first +- Low update frequency (~4 changes/year) makes manual adaptation practical + +**Our process:** + +- Daily automated check detects when TinyTeX patterns change +- GitHub issue created/updated when changes detected +- Manual review and adaptation for Quarto's usage + +## Pattern Differences + +tinytex R package and Quarto LaTeX engine use patterns differently: + +- R package: Matches patterns line-by-line against log array +- Quarto: Matches patterns against entire log file as string + +### Common Adaptations + +1. **Direct copy** (most common): + + ```typescript + // TinyTeX: ".*! LaTeX Error: File [`']([^']+)' not found.*" + // Quarto: + /.*! LaTeX Error: File [`']([^']+)' not found.*/g; + ``` + +2. **Anchored patterns** need multiline flag or anchor removal: + + ```typescript + // TinyTeX: "^No file ([^`'. ]+[.]fd)[.].*" + // Quarto options: + /^No file ([^`'. ]+[.]fd)[.].*/gm // multiline flag + /.*No file ([^`'. ]+[.]fd)[.].*/g // remove anchor + ``` + +3. **Filter functions** for post-processing: + ```typescript + { + regex: /.*! Font [^=]+=([^ ]+).+ not loadable.*/g, + filter: formatFontFilter, // Cleans font names + } + ``` + +## Manual Adaptation Process + +When the automated workflow detects TinyTeX pattern changes, it creates/updates a GitHub issue with: + +- Date of detection +- Category-by-category count changes +- Full diff of `regex.json` changes + +### Adaptation Steps + +1. Review the diff: + + - Identify added, modified, or removed patterns + +2. Update [parse-error.ts](../src/command/render/latexmk/parse-error.ts): + + - Add new patterns to `packageMatchers` array + - Convert TinyTeX string patterns to TypeScript regex with `/g` flag + - Add multiline flag `/gm` if pattern uses `^` or `$` anchors + - Add filter function if pattern needs post-processing + +3. Test changes + + ```bash + cd tests + # Windows + pwsh -Command '$env:QUARTO_TESTS_NO_CONFIG="true"; .\run-tests.ps1 unit\latexmk\parse-error.test.ts' + # Linux/macOS + QUARTO_TESTS_NO_CONFIG=true ./run-tests.sh unit/latexmk/parse-error.test.ts + ``` + +4. Commit and close issue + +## Verification Workflow + +The automated workflow runs daily: + +1. Downloads `regex.tar.gz` from [TinyTeX releases](https://github.com/rstudio/tinytex-releases) +2. Extracts and compares `regex.json` with cached version +3. If changed: generates diff and creates/updates issue +4. If unchanged: exits early (no notification) + +**Workflow location**: [.github/workflows/verify-tinytex-patterns.yml](../.github/workflows/verify-tinytex-patterns.yml) + +**Manual trigger**: Run workflow from GitHub Actions tab when testing or after TinyTeX release announcement + +## Resources + +- [parse-error.ts](../src/command/render/latexmk/parse-error.ts) - Pattern implementation +- [parse-error.test.ts](../tests/unit/latexmk/parse-error.test.ts) - Unit tests +- [TinyTeX R source](https://github.com/rstudio/tinytex/blob/main/R/latex.R) - How patterns are used in R +- [TinyTeX releases](https://github.com/rstudio/tinytex-releases) - Source of regex.json From 2e16fca93ff4a73780c9078bc220c54bb9b0e849 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:05:05 +0100 Subject: [PATCH 05/14] Add missing tests from tinytex R package known errors --- tests/unit/latexmk/parse-error.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/unit/latexmk/parse-error.test.ts b/tests/unit/latexmk/parse-error.test.ts index 2a80106b8eb..95fa1e88514 100644 --- a/tests/unit/latexmk/parse-error.test.ts +++ b/tests/unit/latexmk/parse-error.test.ts @@ -49,6 +49,29 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => assertFound("! I can't find file `hyph-de-1901.ec.tex'.", "hyph-de-1901.ec.tex"); assertFound("luaotfload-features.lua:835: module 'lua-uni-normalize' not found:", "lua-uni-algos.lua"); assertFound("! LuaTeX-ja error: File 'jfm-zh_CN.lua' not found.", "jfm-zh_CN.lua"); + + // Additional test cases from tinytex R package examples (latex.R lines 537-607) + // https://github.com/rstudio/tinytex/blob/e96be3143b9af07768a124215b5fb5a1e6d183d3/R/latex.R#L538-L558 + // xdvipdfmx variant of TFM error + assertFound('xdvipdfmx:fatal: Unable to find TFM file "rsfs10"', fontSearchTerm("rsfs10")); + // biblatex bibliography style file + assertFound("Package biblatex Info: ... file 'trad-abbrv.bbx' not found", "trad-abbrv.bbx"); + // epstopdf - eps-converted-to.pdf pattern + assertFound("! Package pdftex.def Error: File `logo-mdpi-eps-converted-to.pdf' not found", "epstopdf"); + // epstopdf - pdf_ref_obj pattern + assertFound("! xdvipdfmx:fatal: pdf_ref_obj(): passed invalid object.", "epstopdf"); + // tikz library with error context + assertFound( + "! Package tikz Error: I did not find the tikz library 'hobby'. This error message was issued because the library or one of its sublibraries could not be found, probably because of a misspelling. Processed options: \"library={hobby}\". The possibly misspelled library name is \"hobby\". The library name should be one of the following (or you misspelled it): named tikzlibraryhobby.code.tex", + "tikzlibraryhobby.code.tex" + ); + // support file missing + assertFound("support file `supp-pdf.mkii' (supp-pdf.tex) is missing", "supp-pdf.mkii"); + + // Note: The following error types from tinytex are not yet testable because + // patterns don't exist in parse-error.ts: + // - .fd files: "No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly." + // - pdfx color profiles: "! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found" },{ cwd: () => "unit/latexmk/" }) \ No newline at end of file From d871c99475cd95c44a8bb360da5c4807b4cf32c2 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:06:32 +0100 Subject: [PATCH 06/14] Add support for colorprofiles.sty --- src/command/render/latexmk/parse-error.ts | 6 ++++++ tests/unit/latexmk/parse-error.test.ts | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/command/render/latexmk/parse-error.ts b/src/command/render/latexmk/parse-error.ts index 6b7d64f2c3f..8daf2c7d066 100644 --- a/src/command/render/latexmk/parse-error.ts +++ b/src/command/render/latexmk/parse-error.ts @@ -267,6 +267,12 @@ const packageMatchers = [ return "lua-uni-algos.lua"; }, }, + { + regex: /.* Package pdfx Error: No color profile ([^\s]*).*/g, + filter: (_match: string, _text: string) => { + return "colorprofiles.sty"; + }, + }, { regex: /.* Loading '([^']+)' aborted!.*/g }, { regex: /.*! LaTeX Error: File `([^']+)' not found.*/g }, { regex: /.* [fF]ile ['`]?([^' ]+)'? not found.*/g }, diff --git a/tests/unit/latexmk/parse-error.test.ts b/tests/unit/latexmk/parse-error.test.ts index 95fa1e88514..d4ee4c2a309 100644 --- a/tests/unit/latexmk/parse-error.test.ts +++ b/tests/unit/latexmk/parse-error.test.ts @@ -67,11 +67,12 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => ); // support file missing assertFound("support file `supp-pdf.mkii' (supp-pdf.tex) is missing", "supp-pdf.mkii"); + // pdfx color profile error + assertFound("! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found", "colorprofiles.sty"); - // Note: The following error types from tinytex are not yet testable because - // patterns don't exist in parse-error.ts: + // Note: The following error type from tinytex is not yet testable because + // the pattern doesn't exist in parse-error.ts: // - .fd files: "No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly." - // - pdfx color profiles: "! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found" },{ cwd: () => "unit/latexmk/" }) \ No newline at end of file From 8a71069ca24428767d4b325734fc7b4a4c4952cf Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:07:56 +0100 Subject: [PATCH 07/14] support missing font definition files --- src/command/render/latexmk/parse-error.ts | 6 ++++++ tests/unit/latexmk/parse-error.test.ts | 6 ++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/command/render/latexmk/parse-error.ts b/src/command/render/latexmk/parse-error.ts index 8daf2c7d066..8ea4dfad67f 100644 --- a/src/command/render/latexmk/parse-error.ts +++ b/src/command/render/latexmk/parse-error.ts @@ -273,6 +273,12 @@ const packageMatchers = [ return "colorprofiles.sty"; }, }, + { + regex: /.*No file ([^`'. ]+[.]fd)[.].*/g, + filter: (match: string, _text: string) => { + return match.toLowerCase(); + }, + }, { regex: /.* Loading '([^']+)' aborted!.*/g }, { regex: /.*! LaTeX Error: File `([^']+)' not found.*/g }, { regex: /.* [fF]ile ['`]?([^' ]+)'? not found.*/g }, diff --git a/tests/unit/latexmk/parse-error.test.ts b/tests/unit/latexmk/parse-error.test.ts index d4ee4c2a309..49179b92f59 100644 --- a/tests/unit/latexmk/parse-error.test.ts +++ b/tests/unit/latexmk/parse-error.test.ts @@ -69,10 +69,8 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => assertFound("support file `supp-pdf.mkii' (supp-pdf.tex) is missing", "supp-pdf.mkii"); // pdfx color profile error assertFound("! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found", "colorprofiles.sty"); - - // Note: The following error type from tinytex is not yet testable because - // the pattern doesn't exist in parse-error.ts: - // - .fd files: "No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly." + // font definition file (converted to lowercase) + assertFound("No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly.", "lgrcmr.fd"); },{ cwd: () => "unit/latexmk/" }) \ No newline at end of file From f6c41514dd0fd7d43661344c6038927299b42b1f Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:10:39 +0100 Subject: [PATCH 08/14] Remove too many comments --- tests/unit/latexmk/parse-error.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/unit/latexmk/parse-error.test.ts b/tests/unit/latexmk/parse-error.test.ts index 49179b92f59..f01cede5d6b 100644 --- a/tests/unit/latexmk/parse-error.test.ts +++ b/tests/unit/latexmk/parse-error.test.ts @@ -52,24 +52,16 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => // Additional test cases from tinytex R package examples (latex.R lines 537-607) // https://github.com/rstudio/tinytex/blob/e96be3143b9af07768a124215b5fb5a1e6d183d3/R/latex.R#L538-L558 - // xdvipdfmx variant of TFM error assertFound('xdvipdfmx:fatal: Unable to find TFM file "rsfs10"', fontSearchTerm("rsfs10")); - // biblatex bibliography style file assertFound("Package biblatex Info: ... file 'trad-abbrv.bbx' not found", "trad-abbrv.bbx"); - // epstopdf - eps-converted-to.pdf pattern assertFound("! Package pdftex.def Error: File `logo-mdpi-eps-converted-to.pdf' not found", "epstopdf"); - // epstopdf - pdf_ref_obj pattern assertFound("! xdvipdfmx:fatal: pdf_ref_obj(): passed invalid object.", "epstopdf"); - // tikz library with error context assertFound( "! Package tikz Error: I did not find the tikz library 'hobby'. This error message was issued because the library or one of its sublibraries could not be found, probably because of a misspelling. Processed options: \"library={hobby}\". The possibly misspelled library name is \"hobby\". The library name should be one of the following (or you misspelled it): named tikzlibraryhobby.code.tex", "tikzlibraryhobby.code.tex" ); - // support file missing assertFound("support file `supp-pdf.mkii' (supp-pdf.tex) is missing", "supp-pdf.mkii"); - // pdfx color profile error assertFound("! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found", "colorprofiles.sty"); - // font definition file (converted to lowercase) assertFound("No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly.", "lgrcmr.fd"); },{ cwd: () => "unit/latexmk/" From e4fb33396cb5b62d2880b712bc022c81f46cba5d Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:19:52 +0100 Subject: [PATCH 09/14] Support backtick and single quotes like in tinytex --- src/command/render/latexmk/parse-error.ts | 10 +++++----- tests/unit/latexmk/parse-error.test.ts | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/command/render/latexmk/parse-error.ts b/src/command/render/latexmk/parse-error.ts index 8ea4dfad67f..ee99c76330a 100644 --- a/src/command/render/latexmk/parse-error.ts +++ b/src/command/render/latexmk/parse-error.ts @@ -248,7 +248,7 @@ const packageMatchers = [ return `${match}.sty`; }, }, - { regex: /.* File `(.+eps-converted-to.pdf)'.*/g, filter: estoPdfFilter }, + { regex: /.* File [`'](.+eps-converted-to.pdf)'.*/g, filter: estoPdfFilter }, { regex: /.*xdvipdfmx:fatal: pdf_ref_obj.*/g, filter: estoPdfFilter }, { @@ -280,14 +280,14 @@ const packageMatchers = [ }, }, { regex: /.* Loading '([^']+)' aborted!.*/g }, - { regex: /.*! LaTeX Error: File `([^']+)' not found.*/g }, + { regex: /.*! LaTeX Error: File [`']([^']+)' not found.*/g }, { regex: /.* [fF]ile ['`]?([^' ]+)'? not found.*/g }, { regex: /.*the language definition file ([^\s]*).*/g }, { regex: /.* \\(file ([^)]+)\\): cannot open .*/g }, - { regex: /.*file `([^']+)' .*is missing.*/g }, - { regex: /.*! CTeX fontset `([^']+)' is unavailable.*/g }, + { regex: /.*file [`']([^']+)' .*is missing.*/g }, + { regex: /.*! CTeX fontset [`']([^']+)' is unavailable.*/g }, { regex: /.*: ([^:]+): command not found.*/g }, - { regex: /.*! I can't find file `([^']+)'.*/g }, + { regex: /.*! I can't find file [`']([^']+)'.*/g }, ]; function fontSearchTerm(font: string): string { diff --git a/tests/unit/latexmk/parse-error.test.ts b/tests/unit/latexmk/parse-error.test.ts index f01cede5d6b..3a34e4f8f36 100644 --- a/tests/unit/latexmk/parse-error.test.ts +++ b/tests/unit/latexmk/parse-error.test.ts @@ -33,6 +33,7 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => assertFound('(fontspec) The font "LibertinusSerif-Regular" cannot be', fontSearchTerm("LibertinusSerif-Regular")); assertFound('! Font \\JY3/mc/m/n/10=file:HaranoAjiMincho-Regular.otf:-kern;jfm=ujis at 9.24713pt not loadable: metric data not found or bad.', "HaranoAjiMincho-Regular.otf"); assertFound("! LaTeX Error: File `framed.sty' not found.", "framed.sty"); + assertFound("! LaTeX Error: File 'framed.sty' not found.", "framed.sty"); assertFound("/usr/local/bin/mktexpk: line 123: mf: command not found", "mf"); assertFound("or the language definition file ngerman.ldf was not found", "ngerman.ldf"); assertFound(`Package babel Error: Unknown option 'ngerman'. Either you misspelled it @@ -43,10 +44,12 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => (babel) to the babel package options.`, "ngerman.ldf") assertFound("!pdfTeX error: pdflatex (file 8r.enc): cannot open encoding file for reading", "8r.enc"); assertFound("! CTeX fontset `fandol' is unavailable in current mode", "fandol"); + assertFound("! CTeX fontset 'fandol' is unavailable in current mode", "fandol"); assertFound('Package widetext error: Install the flushend package which is a part of sttools', "flushend.sty"); assertFound('! Package isodate.sty Error: Package file substr.sty not found.', "substr.sty"); assertFound("! Package fontenc Error: Encoding file `t2aenc.def' not found.", "t2aenc.def"); assertFound("! I can't find file `hyph-de-1901.ec.tex'.", "hyph-de-1901.ec.tex"); + assertFound("! I can't find file 'hyph-de-1901.ec.tex'.", "hyph-de-1901.ec.tex"); assertFound("luaotfload-features.lua:835: module 'lua-uni-normalize' not found:", "lua-uni-algos.lua"); assertFound("! LuaTeX-ja error: File 'jfm-zh_CN.lua' not found.", "jfm-zh_CN.lua"); @@ -55,12 +58,14 @@ unitTest("Detect missing files with `findMissingFontsAndPackages`", async () => assertFound('xdvipdfmx:fatal: Unable to find TFM file "rsfs10"', fontSearchTerm("rsfs10")); assertFound("Package biblatex Info: ... file 'trad-abbrv.bbx' not found", "trad-abbrv.bbx"); assertFound("! Package pdftex.def Error: File `logo-mdpi-eps-converted-to.pdf' not found", "epstopdf"); + assertFound("! Package pdftex.def Error: File 'logo-mdpi-eps-converted-to.pdf' not found", "epstopdf"); assertFound("! xdvipdfmx:fatal: pdf_ref_obj(): passed invalid object.", "epstopdf"); assertFound( "! Package tikz Error: I did not find the tikz library 'hobby'. This error message was issued because the library or one of its sublibraries could not be found, probably because of a misspelling. Processed options: \"library={hobby}\". The possibly misspelled library name is \"hobby\". The library name should be one of the following (or you misspelled it): named tikzlibraryhobby.code.tex", "tikzlibraryhobby.code.tex" ); assertFound("support file `supp-pdf.mkii' (supp-pdf.tex) is missing", "supp-pdf.mkii"); + assertFound("support file 'supp-pdf.mkii' (supp-pdf.tex) is missing", "supp-pdf.mkii"); assertFound("! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found", "colorprofiles.sty"); assertFound("No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly.", "lgrcmr.fd"); },{ From aa57ce6f9ca8fdd4ea2e72755b51830d42235a05 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:30:18 +0100 Subject: [PATCH 10/14] Update dev doc with new coverage of patterns from tinytex --- dev-docs/tinytex-pattern-maintenance.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dev-docs/tinytex-pattern-maintenance.md b/dev-docs/tinytex-pattern-maintenance.md index 927b278a747..adf20b1c159 100644 --- a/dev-docs/tinytex-pattern-maintenance.md +++ b/dev-docs/tinytex-pattern-maintenance.md @@ -44,6 +44,7 @@ tinytex R package and Quarto LaTeX engine use patterns differently: ``` 3. **Filter functions** for post-processing: + ```typescript { regex: /.*! Font [^=]+=([^ ]+).+ not loadable.*/g, @@ -97,6 +98,18 @@ The automated workflow runs daily: **Manual trigger**: Run workflow from GitHub Actions tab when testing or after TinyTeX release announcement +## Current Coverage + +**Pattern implementation:** 22 of 23 patterns from TinyTeX (96%) + +**Not implemented:** +- `l3backend` pattern for LaTeX3 version mismatch detection +- Reason: Complex context-aware logic required, rare error case + +**Test coverage:** All documented TinyTeX error examples are tested + +**Important:** Patterns should support both backtick (`` ` ``) and single quote (`'`) for LaTeX error messages + ## Resources - [parse-error.ts](../src/command/render/latexmk/parse-error.ts) - Pattern implementation From 7792b8dd517d50bf260553208e4b1690dda910cf Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:37:11 +0100 Subject: [PATCH 11/14] Use relative link in issue template --- .github/workflows/templates/tinytex-issue-body.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/templates/tinytex-issue-body.md b/.github/workflows/templates/tinytex-issue-body.md index e2d72e95577..5dde77d3b7d 100644 --- a/.github/workflows/templates/tinytex-issue-body.md +++ b/.github/workflows/templates/tinytex-issue-body.md @@ -15,7 +15,7 @@ The daily TinyTeX regex patterns have changed and need review. ### Next Steps -See [dev-docs/tinytex-pattern-maintenance.md](https://github.com/quarto-dev/quarto-cli/blob/main/dev-docs/tinytex-pattern-maintenance.md) for detailed instructions. +See [dev-docs/tinytex-pattern-maintenance.md](./dev-docs/tinytex-pattern-maintenance.md) for detailed instructions. **Review checklist:** @@ -29,4 +29,4 @@ See [dev-docs/tinytex-pattern-maintenance.md](https://github.com/quarto-dev/quar --- -_Generated by [verify-tinytex-patterns.yml](https://github.com/quarto-dev/quarto-cli/blob/main/.github/workflows/verify-tinytex-patterns.yml)_ +_Generated by [verify-tinytex-patterns.yml](./.github/workflows/verify-tinytex-patterns.yml)_ From 8de90f32dd5b9cad92ed12095e7761ff90bd2e07 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 16:45:03 +0100 Subject: [PATCH 12/14] Add error handling to TinyTeX pattern download Gracefully exit with warning if TinyTeX daily release download fails. This prevents workflow failure when daily release hasn't been published yet. --- .github/workflows/verify-tinytex-patterns.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/verify-tinytex-patterns.yml b/.github/workflows/verify-tinytex-patterns.yml index 76f9c3690d9..01108a768ed 100644 --- a/.github/workflows/verify-tinytex-patterns.yml +++ b/.github/workflows/verify-tinytex-patterns.yml @@ -22,7 +22,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - gh release download daily --repo rstudio/tinytex-releases --pattern "regex.tar.gz" + if ! gh release download daily --repo rstudio/tinytex-releases --pattern "regex.tar.gz"; then + echo "::warning::Failed to download TinyTeX daily release - may not be published yet" + exit 0 + fi tar -xzf regex.tar.gz echo "✓ Downloaded and extracted regex.json" From 0c289132d5bf49d1f3da7c5e3cc3d37f7022dba0 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 17:21:14 +0100 Subject: [PATCH 13/14] Skip issue creation on first run - Add first_run detection in compare step - Create GitHub Actions summary on first run with: - TinyTeX commit SHA and link to R/latex.R - Pattern and category counts - Baseline establishment message - Update Exit if unchanged to not exit on first run - Add cache update step for pattern changes - Update Save cache condition to include first_run - Update Summary step to handle all three cases --- .github/workflows/verify-tinytex-patterns.yml | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/.github/workflows/verify-tinytex-patterns.yml b/.github/workflows/verify-tinytex-patterns.yml index 01108a768ed..84708471182 100644 --- a/.github/workflows/verify-tinytex-patterns.yml +++ b/.github/workflows/verify-tinytex-patterns.yml @@ -42,30 +42,56 @@ jobs: if [ -f .cache/regex.json ]; then if git diff --no-index --quiet .cache/regex.json regex.json; then echo "changed=false" >> $GITHUB_OUTPUT + echo "first_run=false" >> $GITHUB_OUTPUT echo "✓ No changes detected" else echo "changed=true" >> $GITHUB_OUTPUT + echo "first_run=false" >> $GITHUB_OUTPUT echo "✗ Changes detected" git diff --no-index .cache/regex.json regex.json > pattern-diff.txt || true fi else - echo "changed=true" >> $GITHUB_OUTPUT + echo "changed=false" >> $GITHUB_OUTPUT + echo "first_run=true" >> $GITHUB_OUTPUT echo "⚠ No cached version (first run)" - echo "First run - no previous version to compare" > pattern-diff.txt fi + - name: Handle first run + if: steps.compare.outputs.first_run == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + # Get tinytex commit SHA + TINYTEX_COMMIT=$(gh api repos/rstudio/tinytex/commits/main --jq '.sha') + TINYTEX_SHORT=$(echo $TINYTEX_COMMIT | cut -c1-7) + + # Count patterns and categories + PATTERN_COUNT=$(jq '[.[] | length] | add' regex.json) + CATEGORY_COUNT=$(jq 'keys | length' regex.json) + + # Write GitHub Actions summary + echo "## TinyTeX Pattern Baseline Established" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "- **Date:** $(date +%Y-%m-%d)" >> "$GITHUB_STEP_SUMMARY" + echo "- **TinyTeX commit:** [\`$TINYTEX_SHORT\`](https://github.com/rstudio/tinytex/commit/$TINYTEX_COMMIT)" >> "$GITHUB_STEP_SUMMARY" + echo "- **Pattern source:** [R/latex.R](https://github.com/rstudio/tinytex/blob/$TINYTEX_COMMIT/R/latex.R)" >> "$GITHUB_STEP_SUMMARY" + echo "- **Baseline:** $PATTERN_COUNT patterns across $CATEGORY_COUNT categories" >> "$GITHUB_STEP_SUMMARY" + echo "- **Cache key:** tinytex-regex-latest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "No issue created (first run - baseline established)." >> "$GITHUB_STEP_SUMMARY" + + # Prepare cache directory + mkdir -p .cache + cp regex.json .cache/regex.json + + echo "✓ Baseline established - cache will be saved" + - name: Exit if unchanged - if: steps.compare.outputs.changed == 'false' + if: steps.compare.outputs.changed == 'false' && steps.compare.outputs.first_run == 'false' run: | echo "No pattern changes detected. Cache hit - exiting." exit 0 - - name: Prepare cache directory - if: steps.compare.outputs.changed == 'true' - run: | - mkdir -p .cache - cp regex.json .cache/regex.json - - name: Prepare readable diff if: steps.compare.outputs.changed == 'true' run: | @@ -116,8 +142,15 @@ jobs: gh issue comment "$ISSUE_NUM" --body-file comment-body.md fi - - name: Save new cache + - name: Update cache with new patterns if: steps.compare.outputs.changed == 'true' + run: | + mkdir -p .cache + cp regex.json .cache/regex.json + echo "✓ Cache updated with new patterns" + + - name: Save new cache + if: steps.compare.outputs.changed == 'true' || steps.compare.outputs.first_run == 'true' uses: actions/cache/save@v4 with: path: .cache/regex.json @@ -126,7 +159,9 @@ jobs: - name: Summary if: always() run: | - if [ "${{ steps.compare.outputs.changed }}" == "true" ]; then + if [ "${{ steps.compare.outputs.first_run }}" == "true" ]; then + echo "✓ Baseline established - cache created" + elif [ "${{ steps.compare.outputs.changed }}" == "true" ]; then echo "✗ Pattern changes detected - issue created/updated" else echo "✓ No pattern changes - cache hit" From d6a08953fddb9e6f73949db6a7d339afda16c015 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 27 Oct 2025 17:51:26 +0100 Subject: [PATCH 14/14] Fix cache immutability by deleting old cache before saving new one --- .github/workflows/verify-tinytex-patterns.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/verify-tinytex-patterns.yml b/.github/workflows/verify-tinytex-patterns.yml index 84708471182..29fb1359c8d 100644 --- a/.github/workflows/verify-tinytex-patterns.yml +++ b/.github/workflows/verify-tinytex-patterns.yml @@ -8,6 +8,7 @@ on: permissions: contents: read issues: write + actions: write jobs: verify: @@ -149,6 +150,14 @@ jobs: cp regex.json .cache/regex.json echo "✓ Cache updated with new patterns" + - name: Delete old cache + if: steps.compare.outputs.changed == 'true' + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + run: | + gh cache delete tinytex-regex-latest || echo "No existing cache to delete" + - name: Save new cache if: steps.compare.outputs.changed == 'true' || steps.compare.outputs.first_run == 'true' uses: actions/cache/save@v4