From 897605a111d14dae274f5f7006ab9477633ed0e3 Mon Sep 17 00:00:00 2001 From: Maciej Sopylo Date: Tue, 18 Nov 2025 21:50:02 +0100 Subject: [PATCH] Fix built html saved outside build directory The fix reconstructs the directory tree inside the build path so links still work the same. --- .../src/html_handlebars/hbs_renderer.rs | 27 +++++++++++++++++-- tests/testsuite/rendering.rs | 15 +++++++++++ .../parent/src/SUMMARY.md | 1 + .../parent/test.md | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests/testsuite/rendering/including_files_from_parent_directories_works/parent/src/SUMMARY.md create mode 100644 tests/testsuite/rendering/including_files_from_parent_directories_works/parent/test.md diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index 8edac3cace..27e3952ca9 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -12,7 +12,8 @@ use mdbook_core::utils::fs; use mdbook_renderer::{RenderContext, Renderer}; use serde_json::json; use std::collections::{BTreeMap, HashMap}; -use std::path::{Path, PathBuf}; +use std::iter::{repeat, zip}; +use std::path::{Component, Path, PathBuf}; use tracing::error; use tracing::{debug, info, trace, warn}; @@ -119,8 +120,30 @@ impl HtmlHandlebars { debug!("Render template"); let rendered = ctx.handlebars.render("index", &ctx.data)?; + // Calculate an output path that's contained in the specified destination + // This is required to be able to reference files in the parent directory + // without putting the HTML files there + let out_path = ctx.destination.join( + &filepath + .parent() + .context("filepath points to a directory")?, + ); + fs::create_dir_all(&out_path).context("could not create output directories")?; + let fixed_path: PathBuf = zip( + ctx.destination + .components() + // padding destination because it's always shorter + .chain(repeat(Component::CurDir)), + std::fs::canonicalize(&out_path)?.components(), + ) + .skip_while(|(out, canonicalized)| out == canonicalized) + .map(|(_, canonicalized)| canonicalized) + .collect(); + let out_path = ctx + .destination + .join(fixed_path) + .join(filepath.file_name().context("filepath points to parent")?); // Write to file - let out_path = ctx.destination.join(filepath); fs::write(&out_path, rendered)?; if prev_ch.is_none() { diff --git a/tests/testsuite/rendering.rs b/tests/testsuite/rendering.rs index 421aa5d45c..08855d4c31 100644 --- a/tests/testsuite/rendering.rs +++ b/tests/testsuite/rendering.rs @@ -304,3 +304,18 @@ HTML tags must be closed before exiting a markdown element. str![[r##"

Option

"##]], ); } + +#[test] +fn including_files_from_parent_directories_works() { + let mut test = + BookTest::from_dir("rendering/including_files_from_parent_directories_works/parent"); + test.check_main_file( + "book/index.html", + str![[r##"

Test!

"##]], + ); + + assert!( + !std::fs::exists(test.dir.join("../test.html")).unwrap(), + "test.html was rendered outside build directory" + ); +} diff --git a/tests/testsuite/rendering/including_files_from_parent_directories_works/parent/src/SUMMARY.md b/tests/testsuite/rendering/including_files_from_parent_directories_works/parent/src/SUMMARY.md new file mode 100644 index 0000000000..9427acaf9a --- /dev/null +++ b/tests/testsuite/rendering/including_files_from_parent_directories_works/parent/src/SUMMARY.md @@ -0,0 +1 @@ +[test](../test.md) diff --git a/tests/testsuite/rendering/including_files_from_parent_directories_works/parent/test.md b/tests/testsuite/rendering/including_files_from_parent_directories_works/parent/test.md new file mode 100644 index 0000000000..ef93b2403f --- /dev/null +++ b/tests/testsuite/rendering/including_files_from_parent_directories_works/parent/test.md @@ -0,0 +1 @@ +# Test!