From e8dc9669af931220dd3c5399bc83273d37dd1c8a Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:20:32 -0600 Subject: [PATCH 1/2] Prototype build-time Pele diagrams and measure production build impact Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../workflows/mermaid-build-comparison.yml | 92 +++++++++++++++++++ src/frontend/bun.lock | 27 ++++-- src/frontend/config/remark-mermaid.mjs | 9 +- src/frontend/package.json | 5 + .../summarize-mermaid-build-comparison.mjs | 71 ++++++++++++++ .../src/components/SampleReadmeBlocks.astro | 3 +- .../api-reference/InheritanceDiagram.astro | 3 +- .../src/components/starlight/Head.astro | 1 - .../docs/community/contributor-guide.mdx | 2 +- src/frontend/src/styles/mermaid.css | 14 +++ src/frontend/src/utils/mermaid.mjs | 30 ++++++ .../tests/e2e/site-ui-navigation.spec.ts | 36 +++----- .../unit/custom-components.vitest.test.ts | 3 +- .../mermaid-build-comparison.vitest.test.ts | 35 +++++++ .../unit/navigation-theme.vitest.test.ts | 2 +- .../tests/unit/pele-mermaid.vitest.test.ts | 69 ++++++++++++++ .../tests/unit/remark-mermaid.vitest.test.ts | 25 +++-- 17 files changed, 375 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/mermaid-build-comparison.yml create mode 100644 src/frontend/scripts/summarize-mermaid-build-comparison.mjs create mode 100644 src/frontend/src/utils/mermaid.mjs create mode 100644 src/frontend/tests/unit/mermaid-build-comparison.vitest.test.ts create mode 100644 src/frontend/tests/unit/pele-mermaid.vitest.test.ts diff --git a/.github/workflows/mermaid-build-comparison.yml b/.github/workflows/mermaid-build-comparison.yml new file mode 100644 index 000000000..a9dbdf396 --- /dev/null +++ b/.github/workflows/mermaid-build-comparison.yml @@ -0,0 +1,92 @@ +name: Mermaid build comparison + +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + paths: + - .github/workflows/mermaid-build-comparison.yml + - src/frontend/** + +permissions: {} + +concurrency: + group: mermaid-build-comparison-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + compare: + if: ${{ contains(github.event.pull_request.labels.*.name, 'build-profile') && github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: 8-core-ubuntu-latest + permissions: + contents: read + env: + ASTRO_TELEMETRY_DISABLED: 1 + NODE_OPTIONS: --max-old-space-size=8192 + BUILD_TIMING: 1 + steps: + - name: Check out baseline + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.base.sha }} + path: baseline + fetch-depth: 0 + persist-credentials: false + + - name: Check out candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # Include the exact base content in both variants, even if the PR is behind main. + ref: ${{ github.sha }} + path: candidate + fetch-depth: 0 + persist-credentials: false + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version-file: candidate/src/frontend/package.json + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 24.x + + - name: Install both checkouts before timing + shell: bash + run: | + set -euo pipefail + for variant in baseline candidate; do + (cd "$variant/src/frontend" && bun install --frozen-lockfile) + done + + - name: Compare cold production builds on the same runner + shell: bash + run: | + set -euo pipefail + for variant in baseline candidate; do + ( + cd "$variant/src/frontend" + export BUILD_METRICS_DIR="$RUNNER_TEMP/mermaid-build-comparison/$variant" + export BUILD_TIMING_OUT="$BUILD_METRICS_DIR/timing.jsonl" + export BUILD_TIMING_LABEL="$variant" + export GITHUB_SHA="$(git rev-parse HEAD)" + mkdir -p "$BUILD_METRICS_DIR" + # No Astro cache is restored; install time is excluded for both builds. + /usr/bin/time -v -o "$BUILD_METRICS_DIR/resources.txt" \ + bun run build:production 2>&1 | tee "$BUILD_METRICS_DIR/build.log" + node scripts/measure-build-output.mjs + ) + done + + - name: Summarize baseline and candidate + working-directory: candidate/src/frontend + env: + COMPARISON_DIR: ${{ runner.temp }}/mermaid-build-comparison + run: node scripts/summarize-mermaid-build-comparison.mjs + + - name: Upload measurements and logs + if: ${{ always() }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: mermaid-build-comparison + path: ${{ runner.temp }}/mermaid-build-comparison + if-no-files-found: warn + retention-days: 7 diff --git a/src/frontend/bun.lock b/src/frontend/bun.lock index 1a9a09747..79f0bf7f9 100644 --- a/src/frontend/bun.lock +++ b/src/frontend/bun.lock @@ -17,15 +17,20 @@ "@fontsource-variable/fira-code": "^5.3.0", "@fontsource-variable/rubik": "^5.3.0", "@fontsource/poppins": "^5.3.0", + "@iconify-json/iconoir": "^1.2.12", + "@iconify-json/logos": "^1.2.18", + "@iconify/utils": "3.1.4", "@lunariajs/core": "^0.1.1", "@lunariajs/starlight": "^0.1.1", "@mdx-js/mdx": "^3.1.1", "asciinema-player": "^3.17.0", "astro": "^7.4.1", "astro-expressive-code": "^0.44.1", + "hast-util-from-html": "^2.0.3", "hast-util-to-html": "^9.0.5", "mdast-util-to-hast": "^13.2.1", "mermaid": "^11.16.1", + "pele": "1.12.0", "remark-directive": "^4.0.0", "satori": "^0.28.1", "satteri": "^0.9.5", @@ -432,6 +437,10 @@ "@iconify-json/devicon": ["@iconify-json/devicon@1.2.62", "", { "dependencies": { "@iconify/types": "2.0.0" } }, "sha512-X0o0+fOJL2t5JTbDCGLRzrmvf8Pv5q8dr86+fW+F/VJ9qvXTtny6lqGIwjE9AY4VTLm3i8f/N+VtRheSW6OkEg=="], + "@iconify-json/iconoir": ["@iconify-json/iconoir@1.2.12", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-9l6OtKntemFvgCbQ14AheNiUpm9eicNCwPuCmAVjmYORqhGFMEf33Yfo1S1pRFh9XIA3s7tiVmly6hm+dYNgEw=="], + + "@iconify-json/logos": ["@iconify-json/logos@1.2.18", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-Qehd7RmUbYM4LSXNHFqulgvmdmqldOxQ3VDbCmzos5IGlo40+cChVdX7GnG9VODBU0H3+Bb8npYOkplooJZvtQ=="], + "@iconify-json/material-icon-theme": ["@iconify-json/material-icon-theme@1.2.69", "", { "dependencies": { "@iconify/types": "2.0.0" } }, "sha512-BqVadW0C19qyzp7Gtl2z0wExBFhfGKDPrgq4ajnGUe9atBcqSwz/yULCbZWzjPlOPrFFu4P3hhfs8Vl503nhEg=="], "@iconify-json/mdi": ["@iconify-json/mdi@1.2.3", "", { "dependencies": { "@iconify/types": "2.0.0" } }, "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg=="], @@ -1128,7 +1137,7 @@ "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], - "entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], @@ -1618,7 +1627,7 @@ "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "2.0.3", "@types/unist": "3.0.3", "nlcst-to-string": "4.0.0", "unist-util-modify-children": "4.0.0", "unist-util-visit-children": "3.0.0", "vfile": "6.0.3" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], - "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "6.0.1" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], @@ -1636,6 +1645,8 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pele": ["pele@1.12.0", "", {}, "sha512-3OdOK6rvkaTB569R4/I8U0H82MqPRxj7vfkbowa2rRhgSBPFhvWlfFy5eae0jP+2A6hE8abE17nsdyxJz2VBiQ=="], + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="], @@ -2012,6 +2023,8 @@ "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + "@astro-community/astro-embed-link-preview/parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + "@astrojs/compiler-binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "2.8.1" } }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], "@astrojs/compiler-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], @@ -2098,12 +2111,8 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "4.0.3" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "6.0.1" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "hast-util-raw/@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], - "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "6.0.1" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -2130,6 +2139,8 @@ "vite-plugin-static-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "3.1.3", "braces": "3.0.3", "glob-parent": "5.1.2", "is-binary-path": "2.1.0", "is-glob": "4.0.3", "normalize-path": "3.0.0", "readdirp": "3.6.0" }, "optionalDependencies": { "fsevents": "2.3.3" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "@astro-community/astro-embed-link-preview/parse5/entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], + "@astrojs/compiler-binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], "@astrojs/internal-helpers/shiki/@shikijs/core": ["@shikijs/core@4.4.2", "", { "dependencies": { "@shikijs/primitive": "4.4.2", "@shikijs/types": "4.4.2", "@shikijs/vscode-textmate": "10.0.2", "@types/hast": "3.0.5", "hast-util-to-html": "9.0.5" } }, "sha512-StyzbAyxg2/tBGf78gwbBkGyeQ73lf8UiJArFaQhTQIDqQOCKPCQFanvrs4/Yv3Yfyc+ONInJM6K+FMIf+P+kA=="], @@ -2174,10 +2185,6 @@ "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - "hast-util-from-html/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "hast-util-raw/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0" } }, "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg=="], diff --git a/src/frontend/config/remark-mermaid.mjs b/src/frontend/config/remark-mermaid.mjs index 77efff657..7f3b62acb 100644 --- a/src/frontend/config/remark-mermaid.mjs +++ b/src/frontend/config/remark-mermaid.mjs @@ -1,4 +1,7 @@ -/** Turn opted-in fences into plain pre elements before code highlighting. */ +import { fromHtml } from 'hast-util-from-html'; +import { renderMermaid } from '../src/utils/mermaid.mjs'; + +/** Render opted-in fences before code highlighting, including nested MDX content. */ export function remarkMermaid() { return (tree) => { function visit(parent) { @@ -9,8 +12,8 @@ export function remarkMermaid() { children: [], data: { hName: 'pre', - hProperties: { className: ['mermaid'] }, - hChildren: [{ type: 'text', value: node.value }], + hProperties: { className: ['mermaid'], 'data-processed': 'true' }, + hChildren: fromHtml(renderMermaid(node.value), { fragment: true }).children, }, position: node.position, }; diff --git a/src/frontend/package.json b/src/frontend/package.json index a27c9ceed..727b94fec 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -78,6 +78,9 @@ "@fontsource-variable/fira-code": "^5.3.0", "@fontsource-variable/rubik": "^5.3.0", "@fontsource/poppins": "^5.3.0", + "@iconify-json/iconoir": "^1.2.12", + "@iconify-json/logos": "^1.2.18", + "@iconify/utils": "3.1.4", "@lunariajs/core": "^0.1.1", "@lunariajs/starlight": "^0.1.1", "@mdx-js/mdx": "^3.1.1", @@ -85,8 +88,10 @@ "astro": "^7.4.1", "astro-expressive-code": "^0.44.1", "hast-util-to-html": "^9.0.5", + "hast-util-from-html": "^2.0.3", "mdast-util-to-hast": "^13.2.1", "mermaid": "^11.16.1", + "pele": "1.12.0", "remark-directive": "^4.0.0", "satori": "^0.28.1", "satteri": "^0.9.5", diff --git a/src/frontend/scripts/summarize-mermaid-build-comparison.mjs b/src/frontend/scripts/summarize-mermaid-build-comparison.mjs new file mode 100644 index 000000000..d2afcd5ab --- /dev/null +++ b/src/frontend/scripts/summarize-mermaid-build-comparison.mjs @@ -0,0 +1,71 @@ +import { appendFile, readFile, writeFile } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export function summarizeComparison(baseline, candidate) { + const rows = [ + ['Build command (seconds)', baseline.wallSeconds, candidate.wallSeconds], + ['Build phases (seconds)', baseline.timing.totalWallMs / 1000, candidate.timing.totalWallMs / 1000], + ['Peak RSS (MiB)', baseline.peakRssKiB / 1024, candidate.peakRssKiB / 1024], + ['All output (MiB)', baseline.output.totalBytes / 1024 ** 2, candidate.output.totalBytes / 1024 ** 2], + ['HTML output (MiB)', size(baseline, '.html') / 1024 ** 2, size(candidate, '.html') / 1024 ** 2], + ['JavaScript output (MiB)', size(baseline, '.js') / 1024 ** 2, size(candidate, '.js') / 1024 ** 2], + ['HTML files', count(baseline, '.html'), count(candidate, '.html')], + ]; + return [ + '## Mermaid build-time comparison', + '', + `Baseline: \`${baseline.output.commit}\`. Candidate: \`${candidate.output.commit}\`.`, + '', + '| Measurement | Baseline | Pele | Delta | Change |', + '| --- | ---: | ---: | ---: | ---: |', + ...rows.map(([name, before, after]) => + `| ${name} | ${before.toFixed(2)} | ${after.toFixed(2)} | ${(after - before).toFixed(2)} | ${before === 0 ? 'N/A' : `${((after / before - 1) * 100).toFixed(1)}%`} |`), + '', + 'One cold production build per ref, sequentially on the same runner, using Bun and each ref’s frozen lockfile. The candidate is the synthetic PR merge, so both variants include the exact base content. No Astro cache is restored; dependency installation is excluded.', + 'This is a directional baseline, not a statistically significant benchmark. Run order and OS caches can affect the result. HTML/JavaScript sizes are uncompressed build output, not measured browser transfer sizes.', + 'Build phases are measured by the existing Astro timing integration; resource logs also capture the complete build command. Invalid diagrams now fail page generation rather than showing a browser-only error.', + '', + ].join('\n'); +} + +function size(result, extension) { + return result.files.filter((file) => file.path.endsWith(extension)).reduce((sum, file) => sum + file.bytes, 0); +} + +function count(result, extension) { + return result.files.filter((file) => file.path.endsWith(extension)).length; +} + +async function load(directory) { + const read = (file) => readFile(join(directory, file), 'utf8'); + const reports = (await read('timing.jsonl')).trim().split('\n').map((line) => JSON.parse(line)); + const timing = reports.findLast((report) => report.kind === 'final'); + if (!timing) throw new Error(`Missing final build timing in ${directory}`); + const resources = await read('resources.txt'); + const peakRss = /Maximum resident set size \(kbytes\):\s*(\d+)/.exec(resources); + if (!peakRss) throw new Error(`Missing peak RSS in ${directory}`); + const elapsed = /Elapsed \(wall clock\) time .*:\s+([\d:.]+)/.exec(resources); + if (!elapsed) throw new Error(`Missing build elapsed time in ${directory}`); + return { + timing, + wallSeconds: elapsed[1].split(':').reduce((seconds, part) => seconds * 60 + Number(part), 0), + peakRssKiB: Number(peakRss[1]), + output: JSON.parse(await read('output.json')), + files: JSON.parse(await read('file-manifest.json')), + }; +} + +async function main() { + const directory = process.env.COMPARISON_DIR; + if (!directory) throw new Error('COMPARISON_DIR is required.'); + const [baseline, candidate] = await Promise.all(['baseline', 'candidate'].map((name) => load(join(directory, name)))); + const summary = summarizeComparison(baseline, candidate); + await writeFile(join(directory, 'comparison.md'), summary); + if (process.env.GITHUB_STEP_SUMMARY) await appendFile(process.env.GITHUB_STEP_SUMMARY, summary); + console.log(summary); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + await main(); +} diff --git a/src/frontend/src/components/SampleReadmeBlocks.astro b/src/frontend/src/components/SampleReadmeBlocks.astro index c1ac01581..defeacd46 100644 --- a/src/frontend/src/components/SampleReadmeBlocks.astro +++ b/src/frontend/src/components/SampleReadmeBlocks.astro @@ -21,6 +21,7 @@ import { toSentenceCase, } from '@utils/sample-readme-headings'; import { isStandaloneSampleImageBlock } from '@utils/sample-readme-images'; +import { renderMermaid } from '@utils/mermaid.mjs'; interface Props { blocks: RootContent[]; @@ -224,7 +225,7 @@ function matchGitHubAlert(block: Blockquote): GitHubAlert | null { if (block.type === 'code') { const code = getCodeProps(block); if (code.lang.toLowerCase() === 'mermaid') { - return
{code.code};
+ return ;
}
return ;
diff --git a/src/frontend/src/components/api-reference/InheritanceDiagram.astro b/src/frontend/src/components/api-reference/InheritanceDiagram.astro
index 194df381c..a433f3acf 100644
--- a/src/frontend/src/components/api-reference/InheritanceDiagram.astro
+++ b/src/frontend/src/components/api-reference/InheritanceDiagram.astro
@@ -9,6 +9,7 @@ interface Props {
}
import { shortTypeName } from '@utils/packages';
+import { renderMermaid } from '@utils/mermaid.mjs';
const { type, allTypes = [] } = Astro.props;
@@ -71,7 +72,7 @@ if (shouldRender) {
{shouldRender && (