This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git


The following commit(s) were added to refs/heads/master by this push:
     new 0d1f9e7894e5 [perf](ci) Reduce sequential sample build work (#4498)
0d1f9e7894e5 is described below

commit 0d1f9e7894e56c20377c199a25f50d6b55023112
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sat Oct 24 16:42:45 2026 +0100

    [perf](ci) Reduce sequential sample build work (#4498)
    
    Shared-code PRs currently repeat translation parsing in five sequential
    processes and match invalid links against tens of thousands of logical
    routes. The measured CI Build took 38m32s. Keep one runner and the
    existing locale/version coverage, while avoiding repeated parsing and
    unnecessary article compilation.
    
    - Share content-addressed structure/heading results within one
    disposable sample build; always recompute translation decisions and
    routes.
    - Reuse rich documents across representative requirements, retain every
    changed/uncertain import owner, and sample blogs before native aggregate
    generation. Preserve lead/featured posts, complete metadata and native
    link resolution. Blog body/state edits retain the complete blog build.
    - Index fixed routes and cache native match results. Styles-only changes
    skip rendered link checks explicitly; content/code/resource changes keep
    checks.
    - Select content/hosting fixtures by subsystem, extend the five-language
    sampler fixture with omitted/edited broken blog cases, and enable
    detailed native phase timings.
    
    Validation: 275 tests passed, one existing opt-in test skipped; 22
    workflow security tests passed. Five-language real-site
    HTML/SEO/Chromium checks passed, as did nine complete production fixture
    locale builds (162 pages). Sampling fixtures prove omitted broken bodies
    do not compile and edited broken document/blog bodies fail. All original
    mandatory import owners and representative feature requirements were
    preserved.
    
    On the same macOS/Node 26 machine, English improved from 244.06s to
    67.65s (72.3% less); the optimized five-language sequential cold-cache
    build took 180.56s. Selected docs decreased from 642 to 577, blogs from
    1,030 to 25, and compiled MD/MDX modules from 1,752 to 682 per compiler.
    These local measurements do not establish GitHub runner performance; CI
    will measure that separately.
    
    ## Versions
    
    No documentation/version content changed. Production builds, published
    URLs and version/locale navigation remain unchanged.
    
    ## Languages
    
    All five enabled locales remain covered. No translations changed.
    
    ## Docs Checklist
    
    - [x] Checked by AI
    - [x] Test Cases Built
    - [x] Updated required version and language counterparts, or explained
    why not
    - [x] If only one language changed, confirmed whether source/translation
    counterparts need sync
    
    Co-authored-by: morningman <[email protected]>
---
 .github/workflows/build-check.yml              |   1 +
 scripts/build-locales.cjs                      |   5 +-
 scripts/i18n/CI_VALIDATION.md                  |  35 ++++++-
 scripts/i18n/__tests__/build-cache.test.cjs    |  48 +++++++++
 scripts/i18n/__tests__/ci-blog-sample.test.cjs |  45 +++++++++
 scripts/i18n/__tests__/ci-sampling.test.cjs    |  51 +++++++++-
 scripts/i18n/build-cache.cjs                   |  47 +++++++++
 scripts/i18n/ci-blog-sample.cjs                |  78 +++++++++++++++
 scripts/i18n/ci-plan.cjs                       |  11 ++-
 scripts/i18n/ci-sample.cjs                     | 130 ++++++++++++++++++-------
 scripts/i18n/manual-plugins.cjs                |  17 +++-
 scripts/i18n/structure.cjs                     |  12 +++
 scripts/i18n/verify-ci-sample.cjs              |   1 +
 scripts/i18n/verify-ci-sampling-fixture.cjs    |  20 +++-
 14 files changed, 450 insertions(+), 51 deletions(-)

diff --git a/.github/workflows/build-check.yml 
b/.github/workflows/build-check.yml
index 823dca5d4793..f85b9caf8cf9 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -187,6 +187,7 @@ jobs:
                   NODE_OPTIONS: --max-old-space-size=8192
               run: |
                   MODE="${{ steps.detect.outputs.validation_mode }}"
+                  export DOCUSAURUS_PERF_LOGGER=true
                   if [ "$MODE" = "skip" ] || [ "$MODE" = "fixtures" ]; then
                       echo "No real-site build required: validation mode=$MODE"
                       exit 0
diff --git a/scripts/build-locales.cjs b/scripts/build-locales.cjs
index 5fb336e424a7..b6a9b477789b 100644
--- a/scripts/build-locales.cjs
+++ b/scripts/build-locales.cjs
@@ -45,7 +45,10 @@ async function buildLocales({siteDir = process.cwd(), outDir 
= 'build', locales:
     fs.mkdirSync(reportDir, {recursive:true});
     const reportPath = path.join(reportDir, 'build-result.json');
     const execute = run ?? ((args) => new Promise((resolve, reject) => {
-        const child = 
spawn(process.execPath,[path.join(__dirname,'build-locale.cjs'), 
...args],{cwd:siteDir,stdio:'inherit'});
+        const child = 
spawn(process.execPath,[path.join(__dirname,'build-locale.cjs'), ...args],{
+            cwd:siteDir,stdio:'inherit',env:{...process.env,
+                ...(samplePlan && 
{DORIS_I18N_STRUCTURE_CACHE:path.join(stage,'structure-cache')})},
+        });
         console.log(JSON.stringify({event:'locale_process_start', 
mode:args[0], locale:args[0] === 'plan' ? null : args[3], pid:child.pid}));
         const stop = signal => child.kill(signal);
         const term = () => stop('SIGTERM');
diff --git a/scripts/i18n/CI_VALIDATION.md b/scripts/i18n/CI_VALIDATION.md
index 33dd12eb2724..0da94df2ff70 100644
--- a/scripts/i18n/CI_VALIDATION.md
+++ b/scripts/i18n/CI_VALIDATION.md
@@ -10,7 +10,7 @@ committed lockfile after source planning. Source snapshot 
checks remain enabled.
 | --- | --- |
 | README, planning or review notes only | Configuration, move/link and 
workflow security checks; no dependency installation or site build |
 | CI and tooling | Unit tests, five-language native compilation fixture and 
publishing/Apache/Chromium fixture |
-| Shared UI, routing, SEO, localization or sidebars | Representative real 
documents in five languages and every active version, plus native content and 
publishing fixtures |
+| Shared UI, routing, SEO, localization or sidebars | Representative real 
documents in five languages and every active version; native content fixtures 
for content/locale implementations, publishing fixtures for delivery/host 
implementations |
 | Documentation or assets | Changed documents and import dependants, plus 
representative real documents; English source changes select all five 
languages, localized changes select English and the affected locales |
 | Dependencies, active versions or unclassified inputs | Full five-language 
publication build and verification |
 | `ci/full` PR label or manual `validation=full` | Full five-language 
publication build and verification |
@@ -26,17 +26,34 @@ The CI adapter first loads native Docusaurus metadata for 
**all documents and
 all active versions**, including IDs, front matter, routes, sidebars, source 
and
 translation state. Optional locales remain enabled during metadata collection.
 It then removes unselected document modules from the generated route registry
-before Webpack compilation and static rendering. Non-document pages remain in
-the build. The production build does not load this adapter.
+before bundling and static rendering. Ordinary pages remain in the build.
+When no blog body or blog translation state changed, blog articles and their
+native lists/tags are sampled too. Blog metadata and logical link targets 
remain
+complete. The production build does not load this adapter.
 
 Every document plugin/version contributes its native main document and 
available
-examples of MDX/imports, SQL, math and media/relative links. Real translated 
bodies
+examples of MDX/imports, SQL, math and media/relative links. A greedy selection
+reuses rich documents across requirements while keeping each version's entry.
+Real translated bodies
 and English fallbacks are sampled where available. Changed document bodies and
 translation-state records are mandatory. Local MDX/JavaScript imports are 
followed
 transitively when an imported file changes; dynamic or unparseable imports 
select
 their owners conservatively. A selection above 500 documents fails with a 
request
 for `ci/full`, rather than silently dropping affected documents.
 
+The blog sample covers a recent article, lead/featured cards, MDX, code blocks 
and available real/fallback
+bodies. Explicit changed imports and uncertain dependencies add their owners.
+Lists, tags and neighboring-post links are rebuilt from selected articles 
before
+the native route generator runs, so an aggregate cannot import every omitted 
body.
+Edited blog bodies/state retain the complete native blog build.
+
+Locale processes still run sequentially on one runner. During a sample build,
+content-addressed Markdown structures and aligned headings are shared through a
+fresh temporary directory owned by that build. Keys include all parser inputs;
+translation decisions and route/verification results are always computed anew.
+Only a bounded memory cache is retained, and it is released before bundling.
+The cache is removed with the build stage, so cold builds need no external 
cache.
+
 Links from rendered pages use the complete native route graph, generated 
redirect
 aliases and existing static files. Anchors on rendered destinations receive the
 normal Docusaurus check. Anchors on omitted destinations cannot be verified and
@@ -44,6 +61,12 @@ are counted explicitly. Unselected document bodies are not 
compiled, rendered or
 checked for body syntax/anchors. Existing Docusaurus warning/error policies 
still
 apply; sample success is not full-site compilation success.
 
+Styles-only changes (`.css`, `.scss`, `.sass`) skip rendered link checking and
+report `linkCheckMode=skipped-style-only`. Content, code, routes, redirects and
+resource changes keep the normal path/anchor checks. The CI adapter indexes 
fixed
+routes and reuses native matching results, including invalid paths, rather than
+scanning tens of thousands of logical routes for every repeated bad link.
+
 Selected pages receive HTML/canonical checks, managed-translation SEO checks 
and a
 Chromium hydration smoke test per language. The separate publishing fixture 
tests
 resource delivery, redirects, language 404s and optimized artifacts on a small 
site.
@@ -56,7 +79,7 @@ coverage remains in full validation and deployment.
   languages and active versions.
 - `.i18n-build/ci-sample/scopes/<locale>.json`: complete metadata count, 
selected
   document routes with reasons, actual client/server MDX modules compiled,
-  metadata loading time and unverified anchor count.
+  metadata loading time, parser-cache counts, blog selection and unverified 
anchor count.
 - `.i18n-build/ci-sample/verification.json`: explicitly reports `mode=sample`,
   `completeDocumentCompilation=false` and `publicationAllowed=false`.
 - `.i18n-build/ci-sampling-fixture-proof.json`: proves an omitted broken import
@@ -90,3 +113,5 @@ node scripts/i18n/verify-ci-sample.cjs
 The verifier needs Playwright/Chromium, installed like the workflow; use
 `--no-browser` only for a local static check. The fixture can be run 
independently
 with `node scripts/i18n/verify-ci-sampling-fixture.cjs --browser`.
+The CI Build step enables `DOCUSAURUS_PERF_LOGGER=true` to separately log 
native
+plugin loading, bundler configuration/compilation, SSG, post-build hooks and 
links.
diff --git a/scripts/i18n/__tests__/build-cache.test.cjs 
b/scripts/i18n/__tests__/build-cache.test.cjs
new file mode 100644
index 000000000000..555a14f74a6a
--- /dev/null
+++ b/scripts/i18n/__tests__/build-cache.test.cjs
@@ -0,0 +1,48 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
+const {execFileSync} = require('node:child_process');
+const {createBuildCache} = require('../build-cache.cjs');
+
+function directory(t) {
+    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'doris-parse-cache-'));
+    t.after(() => fs.rmSync(root, {recursive: true, force: true}));
+    return root;
+}
+test('locale processes reuse successful content results; changed inputs and 
failures are recomputed', async t => {
+    const root = directory(t), first = createBuildCache(root);
+    let calls = 0;
+    const compute = async () => {calls++; return {heading: 'one'};};
+    await Promise.all([first.get('parse', ['one'], compute), 
first.get('parse', ['one'], compute)]);
+    assert.equal(calls, 1);
+    const next = createBuildCache(root);
+    assert.deepEqual(await next.get('parse', ['one'], () => assert.fail('disk 
reuse')), {heading: 'one'});
+    assert.equal(next.stats.diskHits, 1);
+    await next.get('parse', ['changed'], compute);
+    assert.equal(calls, 2);
+    await assert.rejects(next.get('parse', ['bad'], () => {throw new 
Error('bad Markdown');}), /bad Markdown/);
+    await next.get('parse', ['bad'], compute);
+    assert.equal(calls, 3);
+});
+test('real cached parsing and heading alignment match fresh processes across 
heading/format changes', t => {
+    const root = directory(t), modulePath = path.resolve(__dirname, 
'../structure.cjs');
+    const inputs = [
+        ['# One\n\nSQL `x`', '## Uno', '.md', false],
+        ['# One {#stable}\n', '# Uno\n', '.md', false],
+        ['# Two {#changed}\n', '# Uno\n', '.md', true],
+        ['# Markdown\n', '# Markdown\n', '.mdx', false],
+    ];
+    const script = `const 
{structure,alignHeadings}=require(${JSON.stringify(modulePath)});
+        (async()=>{const results=[];for(const [source,target,ext,keep] of 
${JSON.stringify(inputs)}) {
+            const parsed=await structure(source,ext,keep);
+            try{results.push([parsed,await 
alignHeadings(parsed,target,ext,keep)])}
+            catch(e){results.push([parsed,e.message])}
+        
}console.log(JSON.stringify(results))})().catch(e=>{console.error(e);process.exitCode=1});`;
+    const run = cache => execFileSync(process.execPath, ['-e', script], 
{encoding: 'utf8',
+        env: {...process.env, DORIS_I18N_STRUCTURE_CACHE: cache}}).trim();
+    const fresh = run('');
+    assert.equal(run(root), fresh);
+    assert.equal(run(root), fresh);
+});
diff --git a/scripts/i18n/__tests__/ci-blog-sample.test.cjs 
b/scripts/i18n/__tests__/ci-blog-sample.test.cjs
new file mode 100644
index 000000000000..8ea212b81794
--- /dev/null
+++ b/scripts/i18n/__tests__/ci-blog-sample.test.cjs
@@ -0,0 +1,45 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
+const sampler = require('../ci-blog-sample.cjs');
+const {DEFAULT_OPTIONS} = 
require('@docusaurus/plugin-content-blog/lib/options.js');
+
+test('blog lists/tags contain representative bodies and all changed import 
owners, while metadata stays complete', async t => {
+    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'doris-blog-sample-'));
+    t.after(() => fs.rmSync(root, {recursive: true, force: true}));
+    fs.mkdirSync(path.join(root, 'blog'));
+    fs.mkdirSync(path.join(root, 'src'));
+    fs.writeFileSync(path.join(root, 'src/Widget.tsx'), 'export default 
function Widget(){return null;}');
+    const contents = {'new.md': '# New', 'rich.mdx': '# Rich\n\n```sql\nSELECT 
1;\n```',
+        'old.md': '# Old', 'owner.mdx': 'import Widget from 
"@site/src/Widget.tsx";\n\n<Widget/>', 'other.md': '# Other'};
+    for (const [file, text] of Object.entries(contents)) 
fs.writeFileSync(path.join(root, 'blog', file), text);
+    const blogPosts = Object.keys(contents).map(file => ({id: file, metadata: {
+        source: '@site/blog/' + file, permalink: '/ja/blog/' + file, title: 
file, tags: [], unlisted: false,
+        frontMatter: file === 'old.md' ? {order: 1, picked: true} : {},
+    }}));
+    const entries = blogPosts.map(post => ({siteLocale: 'ja', route: 
post.metadata.permalink,
+        englishPath: 'blog/' + post.id, fallback: post.id !== 'rich.mdx', 
indexable: post.id === 'rich.mdx'}));
+    const content = {blogPosts, contentIdentity: {entries}};
+    const context = {siteDir: root, baseUrl: '/ja/', i18n: {currentLocale: 
'ja'}, siteConfig: {markdown: {format: 'detect'}}};
+    const options = {...DEFAULT_OPTIONS, id: 'default', postsPerPage: 'ALL'};
+    let restore = sampler.enable({mode: 'sample', files: []});
+    let selected = await sampler.sample(context, options, content);
+    assert.deepEqual(selected.blogPosts.map(post => post.id), ['new.md', 
'rich.mdx', 'old.md']);
+    assert.deepEqual(selected.blogListPaginated.flatMap(page => page.items), 
['new.md', 'rich.mdx', 'old.md']);
+    assert.equal(selected.contentIdentity.entries.length, 5);
+    assert.equal(content.blogPosts.length, 5);
+    restore();
+    restore = sampler.enable({mode: 'sample', files: ['src/Widget.tsx']});
+    selected = await sampler.sample(context, options, content);
+    assert(selected.blogPosts.some(post => post.id === 'owner.mdx'));
+    assert(!selected.blogPosts.some(post => post.id === 'other.md'));
+    restore();
+    for (const file of ['blog/owner.mdx', 
'i18n/ja/docusaurus-plugin-content-blog/owner.mdx', 
'translations/blog/state.json']) {
+        restore = sampler.enable({mode: 'sample', files: [file]});
+        assert.equal(await sampler.sample(context, options, content), 
undefined);
+        restore();
+    }
+    assert.equal(sampler.enabled(), false);
+});
diff --git a/scripts/i18n/__tests__/ci-sampling.test.cjs 
b/scripts/i18n/__tests__/ci-sampling.test.cjs
index a408de041676..eb0aaa01b3e7 100644
--- a/scripts/i18n/__tests__/ci-sampling.test.cjs
+++ b/scripts/i18n/__tests__/ci-sampling.test.cjs
@@ -4,7 +4,7 @@ const test = require('node:test'),
     path = require('node:path'),
     os = require('node:os');
 const {policy} = require('../ci-plan.cjs');
-const {prune, dependencies, stripUnbuiltAnchors, selectDocs} = 
require('../ci-sample.cjs');
+const {prune, dependencies, stripUnbuiltAnchors, selectDocs, 
chooseRepresentatives, indexedRouteMatcher} = require('../ci-sample.cjs');
 const {buildLocales} = require('../../build-locales.cjs');
 function directory(t) {
     const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ci-sampling-'));
@@ -66,6 +66,55 @@ test('unselected MDX modules leave the compiler graph while 
native layouts and n
     assert.equal(output[1].component, 'Home');
     assert.equal(routes[0].routes[0].routes.length, 2);
 });
+test('one rich document covers several sample requirements without losing 
entry or fallback coverage', t => {
+    const root = directory(t);
+    const source = {entry: '# Entry', sql: '```sql\nSELECT 1;\n```', math: 
'$$x$$',
+        rich: 'import X from "x";\n\n```sql\nSELECT 
1;\n```\n\n$$x$$\n\n![image](./image.png)'};
+    const rows = Object.entries(source).map(([docId, text]) => {
+        fs.writeFileSync(path.join(root, docId + '.mdx'), text);
+        return {pluginId: 'default', version: 'current', docId, englishPath: 
docId + '.mdx', main: docId === 'entry',
+            decisions: [{locale: 'en', fallback: false, indexable: true},
+                {locale: 'ja', fallback: docId !== 'rich', indexable: docId 
=== 'rich'}]};
+    });
+    const {selected} = chooseRepresentatives(rows, root);
+    assert.deepEqual([...selected.values()].map(({row}) => row.docId), 
['entry', 'rich']);
+    const reasons = [...selected.values()].flatMap(item => item.reasons);
+    for (const reason of ['version/plugin entry', 'MDX/import', 'SQL', 'math', 
'media/link', 'en real body', 'ja real body', 'ja fallback'])
+        assert(reasons.includes(reason), reason);
+});
+test('fixture selection follows the changed subsystem and preserves 
sampler-specific verification', () => {
+    const css = policy(['src/scss/_variables.scss']);
+    assert.equal(css.mode, 'sample');
+    assert.equal(css.runContentFixture, false);
+    assert.equal(css.runPublishFixture, false);
+    assert.equal(css.checkRenderedLinks, false);
+    for (const file of ['docs/topic.mdx', 'src/utils/localizedRoute.ts', 
'static/images/logo.svg', 'docusaurus.config.js'])
+        assert.equal(policy([file]).checkRenderedLinks, true, file);
+    assert.equal(policy(['src/utils/localizedRoute.ts']).runContentFixture, 
true);
+    assert.equal(policy(['static/.htaccess']).runPublishFixture, true);
+    assert.equal(policy(['scripts/i18n/ci-sample.cjs', 
'src/utils/localizedRoute.ts']).runSamplingFixture, true);
+});
+test('indexed link checking retains native fixed/dynamic route matching, case 
and slash rules', () => {
+    const {matchRoutes} = require('react-router-config');
+    let scanned = 0;
+    const indexed = indexedRouteMatcher((routes, pathname) => {
+        scanned += routes.length;
+        return matchRoutes(routes, pathname);
+    });
+    const routes = [
+        ...Array.from({length: 2000}, (_, i) => ({path: '/docs/topic-' + i, 
exact: true})),
+        {path: '/Strict/', exact: true, strict: true, sensitive: true},
+        {path: '/blog/:post', exact: true}, {path: '/中文', exact: true},
+        {path: '/nested', routes: [{path: '/nested/child', exact: true}]},
+    ];
+    for (const pathname of ['/docs/topic-1', '/DOCS/TOPIC-1/', '/Strict/', 
'/strict/', '/Strict',
+        '/blog/post', '/blog/post/extra', '/中文', '/nested/child', '/absent'])
+        assert.equal(indexed(routes, pathname).length > 0, matchRoutes(routes, 
pathname).length > 0, pathname);
+    const before = scanned;
+    indexed(routes, '/absent');
+    assert.equal(scanned, before, 'repeated invalid targets reuse the negative 
result');
+    assert(scanned < 50, 'fixed routes are indexed instead of rescanned');
+});
 test('dependency discovery follows MDX imports and their transitive code 
imports, not fenced examples', async t => {
     const root = directory(t);
     fs.mkdirSync(path.join(root, 'docs'));
diff --git a/scripts/i18n/build-cache.cjs b/scripts/i18n/build-cache.cjs
new file mode 100644
index 000000000000..07b4f874d3a1
--- /dev/null
+++ b/scripts/i18n/build-cache.cjs
@@ -0,0 +1,47 @@
+// Disposable, content-addressed data shared by the sequential locale 
processes.
+// The parent owns a fresh directory for one source snapshot; never cache 
routes,
+// translation decisions or successful verification results here.
+const fs = require('node:fs');
+const path = require('node:path');
+const {createHash} = require('node:crypto');
+
+function createBuildCache(directory, {maxEntries = 256} = {}) {
+    const memory = new Map(), pending = new Map();
+    const stats = {computed: 0, memoryHits: 0, diskHits: 0};
+    const remember = (key, value) => {
+        memory.delete(key);
+        memory.set(key, value);
+        while (memory.size > maxEntries) 
memory.delete(memory.keys().next().value);
+        return value;
+    };
+    async function get(namespace, inputs, compute) {
+        if (!directory) return compute();
+        const key = createHash('sha256').update(JSON.stringify([1, namespace, 
inputs])).digest('hex');
+        if (memory.has(key)) {stats.memoryHits++; return remember(key, 
memory.get(key));}
+        if (pending.has(key)) {stats.memoryHits++; return pending.get(key);}
+        const filename = path.join(directory, key.slice(0, 2), key + '.json');
+        try {
+            const stored = JSON.parse(fs.readFileSync(filename, 'utf8'));
+            if (stored.key === key && Object.hasOwn(stored, 'value')) {
+                stats.diskHits++;
+                return remember(key, stored.value);
+            }
+        } catch (error) {
+            if (!['ENOENT', 'ENOTDIR'].includes(error.code) && !(error 
instanceof SyntaxError)) throw error;
+        }
+        const task = Promise.resolve().then(compute).then(value => {
+            stats.computed++;
+            fs.mkdirSync(path.dirname(filename), {recursive: true});
+            const temporary = filename + '.' + process.pid + '.tmp';
+            fs.writeFileSync(temporary, JSON.stringify({key, value}));
+            fs.renameSync(temporary, filename);
+            return remember(key, value);
+        }).finally(() => pending.delete(key));
+        pending.set(key, task);
+        return task;
+    }
+    return {get, stats, clear: () => memory.clear()};
+}
+
+const buildCache = createBuildCache(process.env.DORIS_I18N_STRUCTURE_CACHE);
+module.exports = {createBuildCache, buildCache};
diff --git a/scripts/i18n/ci-blog-sample.cjs b/scripts/i18n/ci-blog-sample.cjs
new file mode 100644
index 000000000000..2367e51c00f4
--- /dev/null
+++ b/scripts/i18n/ci-blog-sample.cjs
@@ -0,0 +1,78 @@
+// Only the disposable CI adapter enables this. Publication keeps the full 
blog.
+const fs = require('node:fs');
+const path = require('node:path');
+let active;
+
+function enable(plan) {
+    // Content edits retain the existing complete blog build. This optimization
+    // is for PRs that change implementations rather than published content.
+    const contentChanged = plan.files.some(file => 
/^(blog\/|i18n\/[^/]+\/docusaurus-plugin-content-blog(?:\/|-[^/]+\/)|translations\/blog\/)/.test(file));
+    active = plan.mode === 'sample' && !contentChanged ? {plan, routes: [], 
reports: []} : undefined;
+    return () => {active = undefined;};
+}
+const enabled = () => !!active;
+const fullRoutes = () => active?.routes ?? [];
+const reports = () => active?.reports ?? [];
+function collect(route) {active.routes.push(route);}
+
+async function sample(context, options, content) {
+    if (!active || !content.blogPosts.length) return;
+    const {plan} = active, selected = new Map();
+    const posts = content.blogPosts;
+    const entries = content.contentIdentity?.entries ?? [];
+    const lookup = new Map(entries.filter(e => e.siteLocale === 
context.i18n.currentLocale)
+        .map(e => [e.route.replace(/\/+$/, ''), e]));
+    const entry = post => lookup.get(post.metadata.permalink.replace(/\/+$/, 
''));
+    const source = post => entry(post)?.englishPath ?? 
post.metadata.source.replace(/^@site\//, '');
+    const texts = new Map();
+    const text = post => {
+        const file = source(post);
+        if (!texts.has(file)) texts.set(file, 
fs.readFileSync(path.resolve(context.siteDir, file), 'utf8'));
+        return texts.get(file);
+    };
+    const add = (post, reason) => {
+        if (!post) return;
+        if (!selected.has(post.id)) selected.set(post.id, {post, reasons: []});
+        selected.get(post.id).reasons.push(reason);
+    };
+    add(posts[0], 'latest post');
+    // The site's native list renders the order=1 article as its lead card.
+    add(posts.find(post => Number(post.metadata.frontMatter?.order) === 1), 
'lead article');
+    add(posts.find(post => post.metadata.frontMatter?.picked && 
Number(post.metadata.frontMatter.order) !== 1), 'featured article');
+    add(posts.find(post => source(post).endsWith('.mdx')), 'MDX');
+    add(posts.find(post => /```(?:sql|javascript|js)\b/i.test(text(post))), 
'code block');
+    add(posts.find(post => entry(post)?.indexable && !entry(post).fallback), 
'real body');
+    add(posts.find(post => entry(post)?.fallback), 'English fallback');
+
+    // Explicitly imported components/assets still select all affected owners.
+    if (plan.files.some(file => /^(src|static)\//.test(file))) {
+        const {dependencies} = require('./ci-sample.cjs');
+        const changed = new Set(plan.files), cache = new Map();
+        for (const post of posts) {
+            const result = await dependencies(context.siteDir,
+                path.resolve(context.siteDir, 
post.metadata.source.replace(/^@site\//, '')),
+                cache, new Set(), context.siteConfig.markdown.format);
+            if (result.uncertain || [...result.files].some(file => 
changed.has(path.relative(context.siteDir, file).split(path.sep).join('/'))))
+                add(post, result.uncertain ? 'uncertain import' : 'changed 
import dependency');
+        }
+    }
+    const blogPosts = posts.filter(post => selected.has(post.id)).map(post => 
({...post,
+        metadata: {...post.metadata, prevItem: undefined, nextItem: 
undefined}}));
+    const {paginateBlogPosts, getBlogTags, shouldBeListed} = 
require('@docusaurus/plugin-content-blog/lib/blogUtils.js');
+    const {normalizeUrl} = require('@docusaurus/utils');
+    const listed = blogPosts.filter(shouldBeListed);
+    for (let i = 0; i < listed.length; i++) {
+        const neighbor = post => post && ({title: post.metadata.title, 
permalink: post.metadata.permalink});
+        listed[i].metadata.prevItem = neighbor(listed[i - 1]);
+        listed[i].metadata.nextItem = neighbor(listed[i + 1]);
+    }
+    const pagination = {postsPerPageOption: options.postsPerPage, 
pageBasePath: options.pageBasePath,
+        blogTitle: options.blogTitle, blogDescription: 
options.blogDescription};
+    active.reports.push({pluginId: options.id, metadataPosts: posts.length, 
renderedPosts: blogPosts.length,
+        selected: [...selected.values()].map(({post, reasons}) => ({source: 
source(post), route: post.metadata.permalink, reasons}))});
+    return {...content, blogPosts,
+        blogListPaginated: paginateBlogPosts({...pagination, blogPosts: listed,
+            basePageUrl: normalizeUrl([context.baseUrl, 
options.routeBasePath])}),
+        blogTags: getBlogTags({...pagination, blogPosts})};
+}
+module.exports = {enable, enabled, fullRoutes, reports, collect, sample};
diff --git a/scripts/i18n/ci-plan.cjs b/scripts/i18n/ci-plan.cjs
index 7684ee518ba1..13a4a447083a 100644
--- a/scripts/i18n/ci-plan.cjs
+++ b/scripts/i18n/ci-plan.cjs
@@ -5,7 +5,7 @@ const path = require('node:path');
 const {execFileSync} = require('node:child_process');
 const {createHash} = require('node:crypto');
 const {locales, localeRegistry} = require('../../config/i18n');
-const {buildScope} = require('./build-scope.cjs');
+const {buildScope, validationScope} = require('./build-scope.cjs');
 const {managedLocales} = require('./state.cjs');
 const runtime =
     
/^(src\/|config\/|docusaurus\.config\.js$|sidebars[^/]*\.(ts|json)$|versioned_sidebars\/|scripts\/i18n\/(docs-plugin|status-plugin|state|structure|content-plugin|content-loader|source-groups|code-prose|protection|content-manifest|manual-state|manual-plugins|native-context|route-context|page-body|page-body-loader|sitemap)\.cjs$)/;
@@ -16,6 +16,7 @@ function policy(files, {forceFull = false, forceSample = 
false} = {}) {
     const risk = files.filter(file => highRisk.test(file));
     const changed = files.some(file => content.test(file));
     const shared = files.some(file => runtime.test(file));
+    const checks = validationScope(files);
     const ci = files.some(file => /^(scripts\/|\.github\/)/.test(file));
     const unknown = files.filter(
         file =>
@@ -44,13 +45,15 @@ function policy(files, {forceFull = false, forceSample = 
false} = {}) {
                 ? unknown
                 : [mode],
         runContentFixture:
-            shared ||
+            (shared && checks.check_content_fixture) ||
             files.some(file =>
                 
/verify-content-fixture|__tests__\/fixtures\/content-manifest/.test(file),
             ),
-        runSamplingFixture: mode === 'fixtures',
-        runPublishFixture: mode === 'fixtures' || shared || mode === 'full',
+        runSamplingFixture: mode === 'fixtures' || files.some(file =>
+            
/^scripts\/(build-locales?\.cjs$|i18n\/(ci-sample|ci-blog-sample|build-cache)\.cjs$)/.test(file)),
+        runPublishFixture: mode === 'fixtures' || checks.check_publish_fixture 
|| mode === 'full',
         publicationAllowed: mode === 'full',
+        checkRenderedLinks: !(files.length && files.every(file => 
/\.(css|scss|sass)$/.test(file))),
     };
 }
 function plan({root, base, event = {}, forceFull = false, forceSample = 
false}) {
diff --git a/scripts/i18n/ci-sample.cjs b/scripts/i18n/ci-sample.cjs
index a0710b4d7b10..b6d82d912ccd 100644
--- a/scripts/i18n/ci-sample.cjs
+++ b/scripts/i18n/ci-sample.cjs
@@ -106,35 +106,38 @@ function chooseRepresentatives(rows, root) {
         groups.get(g).push(row);
     }
     for (const group of groups.values()) {
-        add(group.find(r => r.main) ?? group[0], 'version/plugin entry');
-        const texts = new Map();
-        const text = r => {
-            if (!texts.has(id(r)))
-                texts.set(id(r), fs.readFileSync(path.resolve(root, 
r.englishPath), 'utf8'));
-            return texts.get(id(r));
-        };
-        for (const [feature, pattern] of [
+        const patterns = [
             ['MDX/import', /\bimport\s|<[A-Z][A-Za-z]+/],
             ['SQL', /```sql\b/i],
             ['math', /\$\$|\\\[/],
             ['media/link', /!\[|\]\(\.\.?\//],
-        ])
-            add(
-                group.find(r => pattern.test(text(r))),
-                feature,
-            );
-        const languages = new Set(group.flatMap(r => r.decisions.map(d => 
d.locale)));
-        for (const locale of languages) {
-            add(
-                group.find(r =>
-                    r.decisions.some(d => d.locale === locale && !d.fallback 
&& d.indexable),
-                ),
-                `${locale} real body`,
-            );
-            add(
-                group.find(r => r.decisions.some(d => d.locale === locale && 
d.fallback)),
-                `${locale} fallback`,
-            );
+        ];
+        const coverage = new Map(group.map(row => {
+            const text = fs.readFileSync(path.resolve(root, row.englishPath), 
'utf8');
+            const features = patterns.filter(([, pattern]) => 
pattern.test(text)).map(([feature]) => feature);
+            for (const decision of row.decisions) {
+                if (decision.fallback) features.push(`${decision.locale} 
fallback`);
+                else if (decision.indexable) features.push(`${decision.locale} 
real body`);
+            }
+            return [row, new Set(features)];
+        }));
+        const uncovered = new Set([...coverage.values()].flatMap(features => 
[...features]));
+        const take = row => {
+            for (const feature of coverage.get(row)) if 
(uncovered.delete(feature)) add(row, feature);
+        };
+        const main = group.find(row => row.main) ?? group[0];
+        add(main, 'version/plugin entry');
+        take(main);
+        // One rich document can cover several requirements. Preserve every
+        // existing feature/locale requirement without adding a separate first
+        // match for each of them. Changed/import-dependent owners are added 
later.
+        while (uncovered.size) {
+            let best, score = 0;
+            for (const row of group) {
+                const count = [...coverage.get(row)].filter(feature => 
uncovered.has(feature)).length;
+                if (count > score) {best = row; score = count;}
+            }
+            take(best);
         }
     }
     return {selected, add};
@@ -390,6 +393,34 @@ function stripUnbuiltAnchors(collected, known, rendered, 
aliases) {
     return {collected: result, skipped, metadataValidated};
 }
 
+// Docusaurus repeatedly scans every logical route for unknown paths. Index
+// fixed ASCII exact routes, then let its original matcher decide candidates
+// with the original strict/sensitive flags. Patterns keep the native fallback.
+function indexedRouteMatcher(original) {
+    const indexes = new WeakMap();
+    const normalize = value => value.replace(/\/+$/, '').toLowerCase() || '/';
+    return (routes, pathname) => {
+        let index = indexes.get(routes);
+        if (!index) {
+            index = {literal: new Map(), other: [], results: new Map()};
+            for (const route of routes) {
+                if (route.exact && !route.routes && typeof route.path === 
'string' && /^[\w/%.~-]+$/.test(route.path)) {
+                    const k = normalize(route.path);
+                    if (!index.literal.has(k)) index.literal.set(k, []);
+                    index.literal.get(k).push(route);
+                } else index.other.push(route);
+            }
+            indexes.set(routes, index);
+        }
+        if (!index.results.has(pathname)) {
+            const candidates = index.literal.get(normalize(pathname)) ?? [];
+            const result = original(candidates, pathname);
+            index.results.set(pathname, result.length ? result : 
original(index.other, pathname));
+        }
+        return index.results.get(pathname);
+    };
+}
+
 async function install({root, outDir, locale, planFile}) {
     assert.equal(
         require('@docusaurus/core/package.json').version,
@@ -410,6 +441,8 @@ async function install({root, outDir, locale, planFile}) {
         )
         .digest('hex');
     assert.equal(plan.trackedDiffHash, diff, 'Source changed after sample 
planning');
+    const blogSample = require('./ci-blog-sample.cjs');
+    const restoreBlogSample = blogSample.enable(plan);
     const siteModule = require('@docusaurus/core/lib/server/site.js');
     const linksModule = require('@docusaurus/core/lib/server/brokenLinks.js');
     const originalSite = siteModule.loadSite,
@@ -488,29 +521,29 @@ async function install({root, outDir, locale, planFile}) {
                         JSON.stringify(doc, null, 2),
                     );
                 }
+        const rowsByRoute = new Map(rows.map(row => [key(row.route), row]));
+        const docsByIdentity = new Map(props.plugins
+            .filter(p => p.name === 'docusaurus-plugin-content-docs')
+            .flatMap(p => p.content.loadedVersions.flatMap(v => v.docs
+                .map(doc => [JSON.stringify([p.options.id, v.versionName, 
doc.id]), doc]))));
         for (const route of flatten(fullRoutes)) {
-            const row = rows.find(r => key(r.route) === key(route.path));
+            const row = rowsByRoute.get(key(route.path));
             if (row && chosen.has(key(row.route))) {
-                const plugin = props.plugins.find(
-                    p =>
-                        p.name === 'docusaurus-plugin-content-docs' &&
-                        p.options.id === row.pluginId,
-                );
-                const doc = plugin.content.loadedVersions
-                    .find(v => v.versionName === row.version)
-                    .docs.find(d => d.id === row.docId);
+                const doc = docsByIdentity.get(id(row));
                 route.metadata = {...route.metadata, lastUpdatedAt: 
doc.lastUpdatedAt};
             }
         }
         const pruned = prune(fullRoutes, docPaths, chosen, versions);
         const {getRoutesPaths} = 
require('@docusaurus/core/lib/server/routes.js');
-        const allPaths = getRoutesPaths(fullRoutes, props.baseUrl);
+        const originalPaths = new Set(getRoutesPaths(fullRoutes, 
props.baseUrl).map(key));
+        const completeRoutes = [...fullRoutes, 
...blogSample.fullRoutes().filter(route => 
!originalPaths.has(key(route.path)))];
+        const allPaths = getRoutesPaths(completeRoutes, props.baseUrl);
         known = new Set(allPaths.map(key));
         const metadata = props.plugins.flatMap(p => 
p.content?.contentIdentity?.entries ?? []);
         for (const entry of metadata) known.add(key(entry.route));
         const pathSet = new Set(allPaths.map(key));
         const logical = [
-            ...fullRoutes,
+            ...completeRoutes,
             ...metadata
                 .filter(e => !pathSet.has(key(e.route)))
                 .map(e => ({path: e.route, exact: true, component: 
'metadata-only'})),
@@ -613,7 +646,12 @@ async function install({root, outDir, locale, planFile}) {
             selected: [...selected.values()].map(({row, reasons}) => ({...row, 
reasons})),
             knownRoutes: [...known],
             anchorChecksSkipped: 0,
+            structureCache: {...require('./build-cache.cjs').buildCache.stats},
+            blogSampling: blogSample.reports(),
+            linkCheckMode: plan.checkRenderedLinks === false ? 
'skipped-style-only' : 'indexed',
         };
+        // Parser results stay on disk for the next locale, not in the bundler 
heap.
+        require('./build-cache.cjs').buildCache.clear();
         props.plugins.push({
             name: 'ci-sample-provenance',
             configureWebpack(_config, isServer) {
@@ -665,13 +703,30 @@ async function install({root, outDir, locale, planFile}) {
         return site;
     };
     linksModule.handleBrokenLinks = async params => {
+        if (plan.checkRenderedLinks === false) {
+            assert(plan.files.length && plan.files.every(file => 
/\.(css|scss|sass)$/.test(file)),
+                'Only styles-only changes may skip rendered link checks');
+            scope.linkCheckMs = 0;
+            save();
+            return;
+        }
+        const started = Date.now();
         const sanitized = stripUnbuiltAnchors(params.collectedLinks, known, 
rendered, aliases);
         scope.anchorChecksSkipped = sanitized.skipped;
         scope.linksValidatedFromMetadata = sanitized.metadataValidated;
         save();
-        return originalLinks({...params, collectedLinks: sanitized.collected, 
routes: fullRoutes});
+        const router = require('react-router-config'), matchRoutes = 
router.matchRoutes;
+        router.matchRoutes = indexedRouteMatcher(matchRoutes);
+        try {
+            return await originalLinks({...params, collectedLinks: 
sanitized.collected, routes: fullRoutes});
+        } finally {
+            router.matchRoutes = matchRoutes;
+            scope.linkCheckMs = Date.now() - started;
+            save();
+        }
     };
     return () => {
+        restoreBlogSample();
         siteModule.loadSite = originalSite;
         linksModule.handleBrokenLinks = originalLinks;
         updates.readLastUpdateData = readUpdate;
@@ -685,4 +740,5 @@ module.exports = {
     prune,
     dependencies,
     stripUnbuiltAnchors,
+    indexedRouteMatcher,
 };
diff --git a/scripts/i18n/manual-plugins.cjs b/scripts/i18n/manual-plugins.cjs
index 27d080fa8f72..8414829e2018 100644
--- a/scripts/i18n/manual-plugins.cjs
+++ b/scripts/i18n/manual-plugins.cjs
@@ -9,6 +9,7 @@ const {localizedContext} = require('./native-context.cjs');
 const fs = require('node:fs');
 const {withContentRoutes} = require('./route-context.cjs');
 const {validateMessages} = require('./page-body.cjs');
+const blogSample = require('./ci-blog-sample.cjs');
 
 function wrap(kind, native) {
     async function adapter(context, options) {
@@ -21,7 +22,9 @@ function wrap(kind, native) {
             return relative(base, source);
         }
         function nativeOptions(ctx) {
-            if (kind !== 'blog' || ctx.i18n.currentLocale === 'en') return 
options;
+            const base = kind === 'blog' && blogSample.enabled()
+                ? {...options, feedOptions: {...options.feedOptions, type: 
null}} : options;
+            if (kind !== 'blog' || ctx.i18n.currentLocale === 'en') return 
base;
             const locale = ctx.i18n.currentLocale;
             return {...options, feedOptions: {...options.feedOptions, type: 
null}, async processBlogPosts(args) {
                 const processed = await options.processBlogPosts?.(args);
@@ -46,6 +49,18 @@ function wrap(kind, native) {
             fs.writeFileSync(englishI18n, JSON.stringify({...context.i18n, 
currentLocale: context.i18n.defaultLocale}));
         }
         return withContentRoutes({...plugin,
+            async contentLoaded(args) {
+                const selected = kind === 'blog' && await 
blogSample.sample(context, options, args.content);
+                if (!selected) return plugin.contentLoaded(args);
+                // Populate native metadata and Markdown-link/asset resolvers
+                // for every post, but register only sampled native routes.
+                await plugin.contentLoaded({...args, actions: 
{...args.actions, addRoute: blogSample.collect}});
+                Object.assign(args.content, selected);
+                const {createAllRoutes} = 
require('@docusaurus/plugin-content-blog/lib/routes.js');
+                const pluginDir = path.join(context.generatedFilesDir, 
'docusaurus-plugin-content-blog');
+                return createAllRoutes({baseUrl: context.baseUrl, content: 
args.content, actions: args.actions, options,
+                    aliasedSource: source => '~blog/' + 
path.relative(pluginDir, source).split(path.sep).join('/')});
+            },
             ...(kind === 'pages' && {configureWebpack(...args) {
                 const result = plugin.configureWebpack?.(...args) ?? {};
                 return {...result, module: {...result.module, rules: 
[...(result.module?.rules ?? []), {
diff --git a/scripts/i18n/structure.cjs b/scripts/i18n/structure.cjs
index b43aedbd7fcf..0823933cdb4f 100644
--- a/scripts/i18n/structure.cjs
+++ b/scripts/i18n/structure.cjs
@@ -4,6 +4,7 @@ const headingsPlugin = 
require('@docusaurus/mdx-loader/lib/remark/headings').def
 const {escapeMarkdownHeadingIds} = require('@docusaurus/utils');
 const {getFormat} = require('@docusaurus/mdx-loader/lib/format.js');
 const {normalizedNumbers} = require('./protection.cjs');
+const {buildCache} = require('./build-cache.cjs');
 
 function withoutPositions(value) {
     if (Array.isArray(value)) return value.map(withoutPositions);
@@ -14,6 +15,11 @@ function withoutPositions(value) {
 }
 
 async function structure(raw, extension, anchorsMaintainCase = false) {
+    return buildCache.get('structure', [raw, extension, anchorsMaintainCase],
+        () => parseStructure(raw, extension, anchorsMaintainCase));
+}
+
+async function parseStructure(raw, extension, anchorsMaintainCase) {
     if (/^\.tsx?$/.test(extension)) {
         const {parseScript} = require('./code-prose.cjs');
         const file = parseScript(raw);
@@ -102,6 +108,12 @@ async function structure(raw, extension, 
anchorsMaintainCase = false) {
 }
 
 async function alignHeadings(sourceStructure, target, extension, maintainCase 
= false) {
+    const headings = sourceStructure.headings.map(({depth, id}) => [depth, 
id]);
+    return buildCache.get('aligned-headings', [headings, target, extension, 
maintainCase],
+        () => alignTargetHeadings(sourceStructure, target, extension, 
maintainCase));
+}
+
+async function alignTargetHeadings(sourceStructure, target, extension, 
maintainCase) {
     const parsed = await structure(target, extension, maintainCase);
     if (parsed.headings.length !== sourceStructure.headings.length
         || parsed.headings.some((heading, i) => heading.depth !== 
sourceStructure.headings[i].depth)) {
diff --git a/scripts/i18n/verify-ci-sample.cjs 
b/scripts/i18n/verify-ci-sample.cjs
index f989aeaa12c0..ee63924782a7 100644
--- a/scripts/i18n/verify-ci-sample.cjs
+++ b/scripts/i18n/verify-ci-sample.cjs
@@ -45,6 +45,7 @@ async function verify({root = process.cwd(), browser = true}) 
{
             compiledRoutes: scope.compiledRoutes.length,
             compiledMdxModules: new 
Set(Object.values(scope.compiledMdxModules).flat()).size,
             anchorChecksSkipped: scope.anchorChecksSkipped,
+            linkCheckMode: scope.linkCheckMode,
         });
     }
     if (browser) {
diff --git a/scripts/i18n/verify-ci-sampling-fixture.cjs 
b/scripts/i18n/verify-ci-sampling-fixture.cjs
index 2839cc23ab04..f68fcff7d456 100644
--- a/scripts/i18n/verify-ci-sampling-fixture.cjs
+++ b/scripts/i18n/verify-ci-sampling-fixture.cjs
@@ -2,14 +2,16 @@ const fs = require('node:fs'),
     path = require('node:path'),
     assert = require('node:assert/strict');
 const {execFileSync, spawnSync} = require('node:child_process');
-const {createFixture} = require('./__tests__/fixtures/multi-locale.cjs');
+const {createContentFixture} = 
require('./__tests__/fixtures/content-manifest.cjs');
 const {plan} = require('./ci-plan.cjs');
 const {verify} = require('./verify-ci-sample.cjs');
 const root = path.resolve(__dirname, '../..');
 async function main() {
     const site = path.join(root, '.i18n-build/ci-sampling-fixture');
     fs.rmSync(site, {recursive: true, force: true});
-    const {write} = await createFixture(site);
+    const {write} = await createContentFixture(site);
+    write('blog/2026-02-01-rich.mdx', '---\ntitle: Rich blog 
sample\n---\n\n<span>Rich blog body</span>\n\n```sql\nSELECT 
1;\n```\n\n[Omitted blog](./2025-01-01-omitted.mdx#unverified)\n');
+    write('blog/2025-01-01-omitted.mdx', '---\ntitle: Omitted 
blog\n---\n\nimport Missing from "./missing-blog-component";\n\n<Missing/>\n');
     write(
         'docs/02-rich.mdx',
         '---\ntitle: Rich MDX\n---\n\nimport Widget from 
"@site/src/components/FixtureRich";\n\n<Widget />\n\n$$ x + 1 $$\n',
@@ -68,6 +70,10 @@ async function main() {
         assert(!scope.selected.some(r => r.docId === 'zz-unselected'));
         assert(scope.compiledMdxModules.client.length > 0);
         
assert(!scope.compiledMdxModules.client.includes('docs/zz-unselected.mdx'));
+        assert(scope.blogSampling.some(report => report.renderedPosts < 
report.metadataPosts));
+        
assert(scope.compiledMdxModules.client.includes('blog/2026-02-01-rich.mdx'));
+        
assert(!scope.compiledMdxModules.client.includes('blog/2025-01-01-omitted.mdx'),
+            'Blog aggregates must not re-import omitted bodies');
     }
     await verify({root: site, browser: process.argv.includes('--browser')});
     assert(
@@ -85,9 +91,11 @@ async function main() {
             skippedAnchors: s.anchorChecksSkipped,
         })),
         excludedBrokenModuleWasNotCompiled: true,
+        excludedBrokenBlogWasNotCompiled: true,
     };
     // A real edit makes that same module mandatory. Its missing import must 
now
     // fail compilation instead of being silently dropped by the sampler.
+    const originalDocument = fs.readFileSync(path.join(site, 
'docs/zz-unselected.mdx'), 'utf8');
     fs.appendFileSync(path.join(site, 'docs/zz-unselected.mdx'), 
'\nChanged.\n');
     commit();
     const changed = plan({root: site, base, forceSample: true});
@@ -104,6 +112,14 @@ async function main() {
         'The changed document must reach the compiler',
     );
     proof.changedBrokenModuleFailed = true;
+    fs.writeFileSync(path.join(site, 'docs/zz-unselected.mdx'), 
originalDocument);
+    fs.appendFileSync(path.join(site, 'blog/2025-01-01-omitted.mdx'), 
'\nChanged blog.\n');
+    commit();
+    fs.writeFileSync(file, JSON.stringify(plan({root: site, base, forceSample: 
true}), null, 2));
+    const blogFailure = run('pipe');
+    assert.notEqual(blogFailure.status, 0, 'Edited blog must receive native 
compilation');
+    assert.match(blogFailure.stdout + blogFailure.stderr, 
/missing-blog-component/);
+    proof.changedBrokenBlogFailed = true;
     fs.writeFileSync(
         path.join(root, '.i18n-build/ci-sampling-fixture-proof.json'),
         JSON.stringify(proof, null, 2) + '\n',


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to