diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml index c6c1e4c..638b277 100644 --- a/.github/workflows/pr-validate.yml +++ b/.github/workflows/pr-validate.yml @@ -74,9 +74,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install PDF test dependencies - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core + run: just install-pdf-deps - name: Setup run: just setup diff --git a/.github/workflows/update-golden.yml b/.github/workflows/update-golden.yml index bf1126b..0bdf87c 100644 --- a/.github/workflows/update-golden.yml +++ b/.github/workflows/update-golden.yml @@ -60,9 +60,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install PDF test dependencies - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core + run: just install-pdf-deps - name: Setup run: just setup diff --git a/Justfile b/Justfile index 93fe0fa..f1dbe0c 100644 --- a/Justfile +++ b/Justfile @@ -256,6 +256,20 @@ fmt-all: fmt fmt-analysis-guest fmt-runtime test-all: test test-analysis-guest @echo "✅ All tests passed" +# Pinned versions for reproducible golden baselines. +# Update ALL THREE locations together: Justfile, pr-validate.yml, update-golden.yml +PDF_DEPS := "poppler-utils=24.02.0-1ubuntu9.8 qpdf=11.9.0-1.1ubuntu0.1 fonts-dejavu-core=2.37-8" + +# Install PDF visual test dependencies (poppler-utils + fonts-dejavu-core). +# On Windows, installs into WSL. On Linux, installs natively. +[linux] +install-pdf-deps: + sudo apt-get update -qq && sudo apt-get install -y -qq {{PDF_DEPS}} + +[windows] +install-pdf-deps: + wsl bash -c "sudo apt-get update -qq && sudo apt-get install -y -qq {{PDF_DEPS}}" + # PDF visual regression tests test-pdf-visual: npx vitest run tests/pdf-visual.test.ts @@ -604,7 +618,7 @@ mcp-setup-everything: echo "✅ MCP 'everything' server configured in $CONFIG_FILE" echo " Start the agent and run: /plugin enable mcp && /mcp enable everything" -# Set up the MCP GitHub server (requires GITHUB_TOKEN env var) +# Set up the MCP GitHub server (uses GITHUB_TOKEN — get one via: gh auth token) [unix] mcp-setup-github: #!/usr/bin/env bash @@ -614,9 +628,16 @@ mcp-setup-github: mkdir -p "$CONFIG_DIR" if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set. The GitHub MCP server needs it at runtime." - echo " export GITHUB_TOKEN=ghp_your_token_here" - echo " Continuing with config anyway..." + echo "⚠️ GITHUB_TOKEN not set. Trying 'gh auth token'..." + if command -v gh &>/dev/null; then + export GITHUB_TOKEN=$(gh auth token 2>/dev/null || true) + fi + if [ -z "${GITHUB_TOKEN:-}" ]; then + echo " Could not get token. Run: export GITHUB_TOKEN=\$(gh auth token)" + echo " Continuing with config anyway..." + else + echo " ✅ Got token from gh CLI" + fi fi node -e " @@ -638,7 +659,7 @@ mcp-setup-github: fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n'); " echo "✅ MCP 'github' server configured in $CONFIG_FILE" - echo " Requires: export GITHUB_TOKEN=ghp_..." + echo " Tip: export GITHUB_TOKEN=\$(gh auth token)" echo " Start the agent and run: /plugin enable mcp && /mcp enable github" # Set up the MCP filesystem server (read-only access to a directory) diff --git a/builtin-modules/pdf.json b/builtin-modules/pdf.json index 8e46e4a..35f5b9d 100644 --- a/builtin-modules/pdf.json +++ b/builtin-modules/pdf.json @@ -3,8 +3,8 @@ "description": "PDF 1.7 document generation — text, graphics, metadata, standard fonts. Flow-based layout for auto-paginating documents.", "author": "system", "mutable": false, - "sourceHash": "sha256:c8716bcb3295f5bc", - "dtsHash": "sha256:f30fba88bfe5f977", + "sourceHash": "sha256:fedd6d3bc7c638e8", + "dtsHash": "sha256:e56bc4be1f1d0cd1", "importStyle": "named", "hints": { "overview": "Generate PDF documents with text, shapes, and metadata. Uses PDF's 14 standard fonts (no embedding required). Coordinates are in points (72 points = 1 inch), with top-left origin.", diff --git a/builtin-modules/pptx-tables.json b/builtin-modules/pptx-tables.json index b7ceb94..350fcb5 100644 --- a/builtin-modules/pptx-tables.json +++ b/builtin-modules/pptx-tables.json @@ -3,7 +3,7 @@ "description": "Styled tables for PPTX presentations - headers, borders, alternating rows", "author": "system", "mutable": false, - "sourceHash": "sha256:e03a2365c45ab0e6", + "sourceHash": "sha256:4fd269d16f32d3ec", "dtsHash": "sha256:130d021921083af6", "importStyle": "named", "hints": { diff --git a/builtin-modules/src/pdf.ts b/builtin-modules/src/pdf.ts index 44955f2..65aac73 100644 --- a/builtin-modules/src/pdf.ts +++ b/builtin-modules/src/pdf.ts @@ -3015,8 +3015,6 @@ export interface TableStyle { borderColor: string; /** Border line width in points. */ borderWidth: number; - /** Page background colour (set internally for contrast checking). */ - _pageBg?: string; } /** Built-in table styles matching PPTX table styles. */ @@ -3378,9 +3376,11 @@ export function comparisonTable(opts: ComparisonTableOptions): PdfElement { return _createPdfElement("comparisonTable", data); } -/** Resolve a style name or object to a TableStyle. */ +/** Resolve a style name or object to a TableStyle. + * Preset styles are shallow-cloned so that contrast auto-correction + * in renderTable never mutates the shared TABLE_STYLES singletons. */ function resolveTableStyle(style?: string | TableStyle): TableStyle { - if (!style) return TABLE_STYLES.default; + if (!style) return { ...TABLE_STYLES.default }; if (typeof style === "string") { const resolved = TABLE_STYLES[style]; if (!resolved) { @@ -3389,7 +3389,7 @@ function resolveTableStyle(style?: string | TableStyle): TableStyle { `Unknown table style "${style}". Valid styles: ${valid}.`, ); } - return resolved; + return { ...resolved }; } return style; } @@ -3487,16 +3487,22 @@ function renderTable( const rowH = tableRowHeight(fontSize, compact); const headerH = rowH; - // Ensure page background is available for contrast checking - if (!style._pageBg) { - style._pageBg = doc.theme.bg; - } - // ── Contrast auto-correction ───────────────────────────────────── // Automatically fix text colors that have poor contrast against the // page background. No errors — just silently correct to readable. + // Use a local pageBg derived from the current doc theme rather than + // caching on the style object (which could be stale across renders). const MIN_CONTRAST = 3.0; - const pageBg = style._pageBg || "FFFFFF"; + const pageBg = doc.theme.bg || "FFFFFF"; + + // If headerBg is too similar to pageBg, the header row won't stand out. + // Swap to theme accent1 so the header is visually distinct. + if (style.headerBg) { + const headerVsPage = contrastRatio(style.headerBg, pageBg); + if (headerVsPage < 1.5 && doc.theme.accent1) { + style.headerBg = doc.theme.accent1; + } + } if (style.bodyFg) { const bodyRatio = contrastRatio(style.bodyFg, pageBg); @@ -3641,17 +3647,14 @@ function renderTable( }); } - // Alternating row background FIRST - if (style.altRowBg && r % 2 === 1) { - doc.drawRect(x, curY, totalWidth, rowH, { fill: style.altRowBg }); - } - - // Auto-contrast body text against effective row background + // EVERY row gets an explicit fill — no transparent rows, no guessing const isAlt = !!(style.altRowBg && r % 2 === 1); - const rowBg = isAlt - ? style.altRowBg - : (style._pageBg || "FFFFFF"); - const rowFg = autoTextColor(rowBg); + const rowBg = isAlt ? style.altRowBg : pageBg; + doc.drawRect(x, curY, totalWidth, rowH, { fill: rowBg }); + + // Prefer the validated body text color; fall back to an automatic + // contrast color if no explicit body foreground is configured. + const rowFg = style.bodyFg ?? autoTextColor(rowBg); // Cell text AFTER background const isBoldRow = rowBold?.[r] ?? false; diff --git a/builtin-modules/src/pptx-tables.ts b/builtin-modules/src/pptx-tables.ts index 7a1ab01..f83075a 100644 --- a/builtin-modules/src/pptx-tables.ts +++ b/builtin-modules/src/pptx-tables.ts @@ -276,7 +276,7 @@ export function table(opts: TableOptions): ShapeFragment { // ── Theme-aware defaults ──────────────────────────────────────────── // If opts.theme is passed, auto-compute colors for dark/light backgrounds. // Style overrides always take precedence over theme-computed values. - const theme = opts.theme || {}; + const theme = (opts.theme || {}) as Partial; const darkMode = theme.bg ? isDark(theme.bg) : false; // Dark mode defaults: light text on dark alt-rows @@ -285,7 +285,14 @@ export function table(opts: TableOptions): ShapeFragment { const defaultAltRowColor = darkMode ? "2D333B" : "F5F5F5"; const defaultBorderColor = darkMode ? "444C56" : "CCCCCC"; - const headerBg = style.headerBg || "2196F3"; + let headerBg = style.headerBg || "2196F3"; + + // If headerBg matches the slide bg, the header won't stand out — use accent + const slideBgForCheck = theme.bg || (darkMode ? "1A1A1A" : "FFFFFF"); + if (theme.accent1 && contrastRatio(headerBg, slideBgForCheck) < 1.5) { + headerBg = theme.accent1; + } + const headerColor = style.headerColor || autoTextColor(headerBg); const headerFontSize = style.headerFontSize || 13; const styleFontSize = style.fontSize || 12; @@ -324,10 +331,10 @@ export function table(opts: TableOptions): ShapeFragment { // Build data rows // ALWAYS auto-contrast text against each row's effective background // to prevent unreadable text on dark themes or image backgrounds. - // If no theme.bg is provided, give non-alt rows an explicit fill - // matching the alt-row scheme so text is always readable. + // ALWAYS give every row an explicit fill — never rely on slide background + // inheritance, because we can't guarantee we know the actual slide bg. + // This ensures autoTextColor always computes against the real fill. const slideBg = theme.bg || (darkMode ? "1A1A1A" : "FFFFFF"); - const nonAltFill = darkMode ? slideBg : undefined; const dataRows = rows .map((row, rowIdx) => { const isAlt = altRows && rowIdx % 2 === 1; @@ -337,7 +344,7 @@ export function table(opts: TableOptions): ShapeFragment { const cells = row .map((cell) => cellXml(cell, { - fillColor: isAlt ? altRowColor : nonAltFill, + fillColor: rowBg, // Always explicit fill — never undefined color: rowTextColor, fontSize: styleFontSize, borderColor, diff --git a/builtin-modules/src/types/ha-modules.d.ts b/builtin-modules/src/types/ha-modules.d.ts index e7036a5..b7a27e5 100644 --- a/builtin-modules/src/types/ha-modules.d.ts +++ b/builtin-modules/src/types/ha-modules.d.ts @@ -1102,8 +1102,6 @@ declare module "ha:pdf" { borderColor: string; /** Border line width in points. */ borderWidth: number; - /** Page background colour (set internally for contrast checking). */ - _pageBg?: string; } /** Built-in table styles matching PPTX table styles. */ export declare const TABLE_STYLES: Record; diff --git a/docs/MCP.md b/docs/MCP.md index 9284913..95c4993 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -205,8 +205,8 @@ MCP tools with native PPTX generation in a single workflow. ### Setup ```bash -# Set your GitHub token -export GITHUB_TOKEN="ghp_your_token_here" +# Use your existing GitHub CLI auth (no PAT needed) +export GITHUB_TOKEN=$(gh auth token) # Configure the GitHub MCP server just mcp-setup-github diff --git a/package-lock.json b/package-lock.json index 1b5007c..7a9ecb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,8 @@ }, "node_modules/@emnapi/core": { "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, @@ -84,6 +86,8 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -1145,6 +1149,8 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -1616,7 +1622,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.3", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, @@ -1829,7 +1837,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.124.0", + "version": "0.126.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz", + "integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==", "dev": true, "license": "MIT", "funding": { @@ -1837,9 +1847,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==", "cpu": [ "arm64" ], @@ -1854,9 +1864,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==", "cpu": [ "arm64" ], @@ -1871,9 +1881,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz", + "integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==", "cpu": [ "x64" ], @@ -1888,9 +1898,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz", + "integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==", "cpu": [ "x64" ], @@ -1905,9 +1915,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", - "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz", + "integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==", "cpu": [ "arm" ], @@ -1922,9 +1932,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==", "cpu": [ "arm64" ], @@ -1939,9 +1949,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz", + "integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==", "cpu": [ "arm64" ], @@ -1956,9 +1966,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==", "cpu": [ "ppc64" ], @@ -1973,9 +1983,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==", "cpu": [ "s390x" ], @@ -1990,7 +2000,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==", "cpu": [ "x64" ], @@ -2005,7 +2017,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz", + "integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==", "cpu": [ "x64" ], @@ -2020,9 +2034,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==", "cpu": [ "arm64" ], @@ -2037,9 +2051,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", - "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz", + "integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==", "cpu": [ "wasm32" ], @@ -2049,16 +2063,16 @@ "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.3" + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz", + "integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==", "cpu": [ "arm64" ], @@ -2073,9 +2087,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz", + "integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==", "cpu": [ "x64" ], @@ -2090,17 +2104,23 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz", + "integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, @@ -2110,6 +2130,8 @@ }, "node_modules/@types/chai": { "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -2119,6 +2141,8 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, @@ -2154,14 +2178,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2170,11 +2196,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.4", + "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2195,7 +2223,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { @@ -2206,11 +2236,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.4", + "@vitest/utils": "4.1.5", "pathe": "^2.0.3" }, "funding": { @@ -2218,12 +2250,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2232,7 +2266,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", "funding": { @@ -2240,11 +2276,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", + "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -2417,6 +2455,8 @@ }, "node_modules/assertion-error": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -2541,6 +2581,8 @@ }, "node_modules/chai": { "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -2623,6 +2665,8 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, @@ -2704,6 +2748,8 @@ }, "node_modules/detect-libc": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2987,6 +3033,8 @@ }, "node_modules/estree-walker": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -3168,6 +3216,8 @@ }, "node_modules/fdir": { "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -3584,6 +3634,8 @@ }, "node_modules/lightningcss": { "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -3759,6 +3811,8 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -3778,6 +3832,8 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -3853,6 +3909,8 @@ }, "node_modules/magic-string": { "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3942,6 +4000,8 @@ }, "node_modules/nanoid": { "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -4102,6 +4162,8 @@ }, "node_modules/pathe": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -4137,11 +4199,15 @@ }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -4180,7 +4246,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -4306,12 +4374,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.15", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz", + "integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" + "@oxc-project/types": "=0.126.0", + "@rolldown/pluginutils": "1.0.0-rc.16" }, "bin": { "rolldown": "bin/cli.mjs" @@ -4320,21 +4390,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + "@rolldown/binding-android-arm64": "1.0.0-rc.16", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.16", + "@rolldown/binding-darwin-x64": "1.0.0-rc.16", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.16", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.16", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.16", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16" } }, "node_modules/router": { @@ -4526,6 +4596,8 @@ }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4593,12 +4665,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -4609,6 +4683,8 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -4740,15 +4816,17 @@ } }, "node_modules/vite": { - "version": "8.0.8", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz", + "integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.16", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" @@ -4816,17 +4894,19 @@ } }, "node_modules/vitest": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.4", - "@vitest/mocker": "4.1.4", - "@vitest/pretty-format": "4.1.4", - "@vitest/runner": "4.1.4", - "@vitest/snapshot": "4.1.4", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -4854,12 +4934,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.4", - "@vitest/browser-preview": "4.1.4", - "@vitest/browser-webdriverio": "4.1.4", - "@vitest/coverage-istanbul": "4.1.4", - "@vitest/coverage-v8": "4.1.4", - "@vitest/ui": "4.1.4", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5006,666 +5086,11 @@ "license": "Apache-2.0", "devDependencies": { "@napi-rs/cli": "^3.6.2", - "vitest": "^4.1.4" + "vitest": "^4.1.5" }, "engines": { "node": ">= 18" } - }, - "src/code-validator/guest/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/@oxc-project/types": { - "version": "0.124.0", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "src/code-validator/guest/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "src/code-validator/guest/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "src/code-validator/guest/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "src/code-validator/guest/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/@vitest/expect": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "src/code-validator/guest/node_modules/@vitest/mocker": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "src/code-validator/guest/node_modules/@vitest/pretty-format": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "src/code-validator/guest/node_modules/@vitest/runner": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.4", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "src/code-validator/guest/node_modules/@vitest/snapshot": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "src/code-validator/guest/node_modules/@vitest/spy": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "src/code-validator/guest/node_modules/@vitest/utils": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "src/code-validator/guest/node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "src/code-validator/guest/node_modules/chai": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "src/code-validator/guest/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/detect-libc": { - "version": "2.1.2", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "src/code-validator/guest/node_modules/es-module-lexer": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "src/code-validator/guest/node_modules/expect-type": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "src/code-validator/guest/node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "src/code-validator/guest/node_modules/lightningcss": { - "version": "1.32.0", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "src/code-validator/guest/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "src/code-validator/guest/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "src/code-validator/guest/node_modules/magic-string": { - "version": "0.30.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "src/code-validator/guest/node_modules/nanoid": { - "version": "3.3.11", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "src/code-validator/guest/node_modules/obug": { - "version": "2.1.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "src/code-validator/guest/node_modules/pathe": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "src/code-validator/guest/node_modules/picomatch": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "src/code-validator/guest/node_modules/postcss": { - "version": "8.5.9", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "src/code-validator/guest/node_modules/rolldown": { - "version": "1.0.0-rc.15", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" - } - }, - "src/code-validator/guest/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "src/code-validator/guest/node_modules/source-map-js": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "src/code-validator/guest/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/std-env": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/tinybench": { - "version": "2.9.0", - "dev": true, - "license": "MIT" - }, - "src/code-validator/guest/node_modules/tinyexec": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "src/code-validator/guest/node_modules/tinyglobby": { - "version": "0.2.16", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "src/code-validator/guest/node_modules/tinyrainbow": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "src/code-validator/guest/node_modules/vite": { - "version": "8.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "src/code-validator/guest/node_modules/vitest": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.4", - "@vitest/mocker": "4.1.4", - "@vitest/pretty-format": "4.1.4", - "@vitest/runner": "4.1.4", - "@vitest/snapshot": "4.1.4", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.4", - "@vitest/browser-preview": "4.1.4", - "@vitest/browser-webdriverio": "4.1.4", - "@vitest/coverage-istanbul": "4.1.4", - "@vitest/coverage-v8": "4.1.4", - "@vitest/ui": "4.1.4", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "src/code-validator/guest/node_modules/why-is-node-running": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } } } } diff --git a/src/code-validator/guest/runtime/src/validator.rs b/src/code-validator/guest/runtime/src/validator.rs index ab8ee58..fc8c08f 100644 --- a/src/code-validator/guest/runtime/src/validator.rs +++ b/src/code-validator/guest/runtime/src/validator.rs @@ -304,13 +304,12 @@ fn sha256_short(content: &str) -> String { } /// Validate module.json hashes against actual source content. -/// System module mismatches are errors (potential tampering/corruption). -/// User module mismatches are warnings (they edit their own stuff). +/// Only checks system modules — user modules are mutable by design. fn validate_module_hashes( context: &ValidationContext, ) -> (Vec, Vec) { let mut errors = Vec::new(); - let mut warnings = Vec::new(); + let warnings = Vec::new(); for (specifier, json_str) in &context.module_jsons { // Parse the module.json @@ -319,7 +318,10 @@ fn validate_module_hashes( Err(_) => continue, // Skip malformed JSON }; - let is_system = meta.author == "system"; + // Skip user modules — they're mutable by design, hash drift is expected + if meta.author != "system" { + continue; + } // Check .js source hash if let Some(expected_hash) = &meta.source_hash @@ -327,26 +329,17 @@ fn validate_module_hashes( { let actual_hash = sha256_short(js_source); if expected_hash != &actual_hash { - let message = alloc::format!( - "{}: .js hash mismatch (expected {}, got {}). Run: npm run build:modules", - specifier, - expected_hash, - actual_hash - ); - if is_system { - errors.push(ValidationError { - error_type: "integrity".to_string(), - message, - line: None, - column: None, - }); - } else { - warnings.push(ValidationWarning { - warning_type: "drift".to_string(), - message, - line: None, - }); - } + errors.push(ValidationError { + error_type: "integrity".to_string(), + message: alloc::format!( + "{}: .js hash mismatch (expected {}, got {}). Run: npm run build:modules", + specifier, + expected_hash, + actual_hash + ), + line: None, + column: None, + }); } } @@ -356,26 +349,17 @@ fn validate_module_hashes( { let actual_hash = sha256_short(dts_source); if expected_hash != &actual_hash { - let message = alloc::format!( - "{}: .d.ts hash mismatch (expected {}, got {}). Run: npm run build:modules", - specifier, - expected_hash, - actual_hash - ); - if is_system { - errors.push(ValidationError { - error_type: "integrity".to_string(), - message, - line: None, - column: None, - }); - } else { - warnings.push(ValidationWarning { - warning_type: "drift".to_string(), - message, - line: None, - }); - } + errors.push(ValidationError { + error_type: "integrity".to_string(), + message: alloc::format!( + "{}: .d.ts hash mismatch (expected {}, got {}). Run: npm run build:modules", + specifier, + expected_hash, + actual_hash + ), + line: None, + column: None, + }); } } } diff --git a/tests/golden/pdf/signature-line.png b/tests/golden/pdf/signature-line.png index 70ca9fe..81cf8f9 100644 Binary files a/tests/golden/pdf/signature-line.png and b/tests/golden/pdf/signature-line.png differ diff --git a/tests/golden/pdf/table-styles.png b/tests/golden/pdf/table-styles.png index 5435dae..bf664c2 100644 Binary files a/tests/golden/pdf/table-styles.png and b/tests/golden/pdf/table-styles.png differ diff --git a/tests/golden/pdf/text-rendering.png b/tests/golden/pdf/text-rendering.png index b8d3d32..9c5a496 100644 Binary files a/tests/golden/pdf/text-rendering.png and b/tests/golden/pdf/text-rendering.png differ diff --git a/tests/golden/pdf/title-page.png b/tests/golden/pdf/title-page.png index c90ed1b..5f83362 100644 Binary files a/tests/golden/pdf/title-page.png and b/tests/golden/pdf/title-page.png differ diff --git a/tests/golden/pdf/two-column.png b/tests/golden/pdf/two-column.png index 10c0cb3..2fc682a 100644 Binary files a/tests/golden/pdf/two-column.png and b/tests/golden/pdf/two-column.png differ diff --git a/tests/pdf-visual.test.ts b/tests/pdf-visual.test.ts index 0caaa79..4b5b0c3 100644 --- a/tests/pdf-visual.test.ts +++ b/tests/pdf-visual.test.ts @@ -21,7 +21,10 @@ const pdf: any = await import("../builtin-modules/pdf.js"); // ── Config ─────────────────────────────────────────────────────────── const GOLDEN_DIR = join(__dirname, "golden", "pdf"); -const TEMP_DIR = "/tmp/pdf-visual-test"; +const TEMP_DIR = join( + process.platform === "win32" ? process.env.TEMP || "C:\\Temp" : "/tmp", + "pdf-visual-test", +); const UPDATE_GOLDEN = process.env.UPDATE_GOLDEN === "1"; const PIXEL_THRESHOLD = 0.1; // per-pixel colour distance threshold const MAX_DIFF_PIXELS = 50; // fail if more than this many pixels differ @@ -30,16 +33,20 @@ const MAX_DIFF_PIXELS = 50; // fail if more than this many pixels differ /** Check if a command-line tool is available (cross-platform). */ function hasCommand(cmd: string): boolean { - if (process.platform === "win32") return false; try { - execSync(`which ${cmd}`, { stdio: "ignore" }); + if (process.platform === "win32") { + // On Windows, check if the tool is available via WSL + execSync(`wsl which ${cmd}`, { stdio: "ignore" }); + } else { + execSync(`which ${cmd}`, { stdio: "ignore" }); + } return true; } catch { return false; } } -const HAS_PDFTOPPM = process.platform !== "win32" && hasCommand("pdftoppm"); +const HAS_PDFTOPPM = hasCommand("pdftoppm"); // Lazy-load comparison deps (only imported when pdftoppm is available) // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -53,12 +60,16 @@ if (HAS_PDFTOPPM) { pixelmatch = pm.default ?? pm; } -// ── Warn loudly on Linux if pdftoppm is missing ────────────────────── +// ── Warn if pdftoppm is missing ────────────────────────────────────── -if (process.platform !== "win32" && !HAS_PDFTOPPM) { +if (!HAS_PDFTOPPM) { + const installHint = + process.platform === "win32" + ? "Install in WSL with: wsl sudo apt-get install poppler-utils fonts-dejavu-core" + : "Install with: sudo apt-get install poppler-utils fonts-dejavu-core"; console.warn( - "\n⚠️ WARNING: pdftoppm not installed — skipping visual regression tests." + - "\n Install with: sudo apt-get install poppler-utils\n", + "\n⚠️ WARNING: pdftoppm not available — skipping visual regression tests." + + `\n ${installHint}\n`, ); } @@ -69,15 +80,37 @@ function ensureTempDir(): void { if (!existsSync(TEMP_DIR)) mkdirSync(TEMP_DIR, { recursive: true }); } +/** Convert a Windows path to a WSL path (e.g. C:\foo → /mnt/c/foo). */ +function toWslPath(winPath: string): string { + const resolved = winPath.replace(/\\/g, "/"); + const match = resolved.match(/^([A-Za-z]):\/(.*)/); + if (!match) return resolved; + return `/mnt/${match[1].toLowerCase()}/${match[2]}`; +} + /** Write PDF bytes to file and render page 1 to PNG via pdftoppm. */ function renderPage1(pdfBytes: Uint8Array, name: string): Buffer { ensureTempDir(); const pdfPath = join(TEMP_DIR, `${name}.pdf`); const pngPrefix = join(TEMP_DIR, `${name}`); writeFileSync(pdfPath, pdfBytes); - execSync(`pdftoppm -png -r 150 -singlefile "${pdfPath}" "${pngPrefix}"`, { - timeout: 10000, - }); + + if (process.platform === "win32") { + // Run pdftoppm via WSL, converting Windows paths to Linux paths + const wslPdf = toWslPath(pdfPath); + const wslPrefix = toWslPath(pngPrefix); + execSync( + `wsl pdftoppm -png -r 150 -singlefile "${wslPdf}" "${wslPrefix}"`, + { + timeout: 10000, + }, + ); + } else { + execSync(`pdftoppm -png -r 150 -singlefile "${pdfPath}" "${pngPrefix}"`, { + timeout: 10000, + }); + } + const pngPath = `${pngPrefix}.png`; return readFileSync(pngPath); }