From d87e80f96b231091443882c224d5725802fa4df6 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Thu, 23 Jul 2026 09:55:19 +0200 Subject: [PATCH] Match bDS2 zero-byte route validation --- crates/bds-core/src/engine/validate_site.rs | 8 +++++++- crates/bds-core/tests/m4_generation_engine.rs | 6 +++++- specs/editor_misc.allium | 3 ++- specs/generation.allium | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/bds-core/src/engine/validate_site.rs b/crates/bds-core/src/engine/validate_site.rs index 986bfa4..7f323cc 100644 --- a/crates/bds-core/src/engine/validate_site.rs +++ b/crates/bds-core/src/engine/validate_site.rs @@ -45,19 +45,24 @@ pub fn validate_site( .collect::>(); let mut actual = HashSet::new(); + let mut zero_byte = HashSet::new(); if output_dir.exists() { for entry in WalkDir::new(&output_dir).into_iter().filter_map(Result::ok) { if !entry.file_type().is_file() || entry.file_name() != "index.html" { continue; } + let path = relative_path(&output_dir, entry.path()); if entry.metadata().is_ok_and(|metadata| metadata.len() > 0) { - actual.insert(relative_path(&output_dir, entry.path())); + actual.insert(path); + } else { + zero_byte.insert(path); } } } let mut missing_pages = expected.difference(&actual).cloned().collect::>(); let mut extra_pages = actual.difference(&expected).cloned().collect::>(); + extra_pages.extend(zero_byte.difference(&expected).cloned()); let generated_at = queries::generated_file_hash::list_generated_file_hashes(conn, project_id)? .into_iter() .map(|file| (file.relative_path, file.updated_at)) @@ -74,6 +79,7 @@ pub fn validate_site( missing_pages.sort(); extra_pages.sort(); + extra_pages.dedup(); stale_pages.sort(); stale_pages.dedup(); diff --git a/crates/bds-core/tests/m4_generation_engine.rs b/crates/bds-core/tests/m4_generation_engine.rs index 546a60b..81b7584 100644 --- a/crates/bds-core/tests/m4_generation_engine.rs +++ b/crates/bds-core/tests/m4_generation_engine.rs @@ -782,12 +782,16 @@ fn site_validation_detects_missing_zero_byte_and_extra_index_routes() { std::fs::write(dir.path().join("index.html"), "").unwrap(); let extra = dir.path().join("ghost/index.html"); std::fs::create_dir_all(extra.parent().unwrap()).unwrap(); - std::fs::write(extra, "ghost").unwrap(); + std::fs::write(&extra, "ghost").unwrap(); + let zero_byte_extra = dir.path().join("empty/index.html"); + std::fs::create_dir_all(zero_byte_extra.parent().unwrap()).unwrap(); + std::fs::write(&zero_byte_extra, "").unwrap(); let report = validate_site(db.conn(), dir.path(), "p1").unwrap(); assert!(report.missing_pages.contains(&"index.html".to_string())); assert!(report.extra_pages.contains(&"ghost/index.html".to_string())); + assert!(report.extra_pages.contains(&"empty/index.html".to_string())); } #[test] diff --git a/specs/editor_misc.allium b/specs/editor_misc.allium index 458560c..d9ffda9 100644 --- a/specs/editor_misc.allium +++ b/specs/editor_misc.allium @@ -450,7 +450,8 @@ surface SiteValidationSurface { rule SiteValidationScan { when: SiteValidationScanRequested() -- Parses entries from sitemap.xml into expected URL set - -- Scans HTML output dir for index.html files (zero-byte = missing) + -- Scans HTML output dir for index.html files (an expected zero-byte route + -- is missing; an unexpected zero-byte route is extra) -- Compares source .md mtime against generated HTML mtime ensures: SiteValidationReport } diff --git a/specs/generation.allium b/specs/generation.allium index 9a978f3..9c397db 100644 --- a/specs/generation.allium +++ b/specs/generation.allium @@ -267,7 +267,8 @@ rule ValidateSite { -- expected: sitemap-eligible routes derived from published snapshots -- actual: non-empty **/index.html files on disk -- missing: expected routes with no non-empty index.html - -- extra: non-empty index.html routes absent from the expected set + -- extra: index.html routes absent from the expected set, including + -- zero-byte files -- stale: existing post routes whose source mtime is more than one second -- newer than both the output mtime and tracked generation time -- Standalone HTML, XML, JSON, assets, Pagefind files, and content hashes are