From 9edda7af37902c025c76b5d4025d76cfcd1f70df Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 21 Apr 2026 14:01:20 -0700 Subject: [PATCH 1/3] Error when upload_dir contains multiple HTML files `locate_html_entrypoint` silently picked whichever HTML `WalkDir` yielded first, which produced confusing builds when users had stale exports alongside a current one (e.g. `pong.html` + `gamedevjsJam.html` after renaming). `index.html` still wins outright when present, so this doesn't break setups that intentionally ship helper HTML alongside the canonical entrypoint. When no `index.html` exists and multiple HTMLs are found, bail with a list of the ambiguous files and a message pointing at the two fixes. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/dev/entrypoint.rs | 33 +++++++++++++++++++++++++++++---- src/dev/mod.rs | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/dev/entrypoint.rs b/src/dev/entrypoint.rs index 5266f9c..26fc510 100644 --- a/src/dev/entrypoint.rs +++ b/src/dev/entrypoint.rs @@ -14,12 +14,16 @@ struct EntrypointParamsResponse { entrypoint_params: Value, } -pub fn locate_html_entrypoint(upload_dir: &Path) -> Option { +pub fn locate_html_entrypoint(upload_dir: &Path) -> Result> { + // `index.html` always wins if present — that's the Web convention and + // sidesteps ambiguity when game exporters also emit helper HTML (debug + // shells, fullscreen wrappers, etc.). let default_index = upload_dir.join("index.html"); if default_index.is_file() { - return Some(default_index); + return Ok(Some(default_index)); } + let mut found: Vec = Vec::new(); for entry in WalkDir::new(upload_dir) .min_depth(1) .into_iter() @@ -28,13 +32,34 @@ pub fn locate_html_entrypoint(upload_dir: &Path) -> Option { if entry.file_type().is_file() { if let Some(ext) = entry.path().extension() { if ext.eq_ignore_ascii_case("html") { - return Some(entry.into_path()); + found.push(entry.into_path()); } } } } - None + match found.len() { + 0 => Ok(None), + 1 => Ok(Some(found.into_iter().next().unwrap())), + _ => { + let names: Vec = found + .iter() + .map(|p| { + p.strip_prefix(upload_dir) + .unwrap_or(p) + .display() + .to_string() + }) + .collect(); + anyhow::bail!( + "Multiple HTML files found in upload_dir ({}):\n - {}\n\n\ + Name one of them `index.html`, or remove the extras (often \ + stale files from a previous export with a different name).", + upload_dir.display(), + names.join("\n - ") + ) + } + } } pub async fn fetch_entrypoint_params(engine: &str, engine_version: &str, html_path: &Path) -> Result { diff --git a/src/dev/mod.rs b/src/dev/mod.rs index 47448d8..06ea38e 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -122,7 +122,7 @@ pub async fn handle_dev(config_path: Option, verbose: bool, no_open: bo // Validate required files exist in upload directory FileStaging::prepare(&upload_dir, &wavedash_config)?; - let html_entrypoint = locate_html_entrypoint(&upload_dir); + let html_entrypoint = locate_html_entrypoint(&upload_dir)?; let engine_version = wavedash_config.engine_version(); let entrypoint_params = match engine_kind { Some(EngineKind::Godot | EngineKind::Unity) => { From 7b8ca786b53d3f2d7dcfaee0e2cd830d9bdd2148 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 21 Apr 2026 14:04:13 -0700 Subject: [PATCH 2/3] Sort ambiguous HTML names in error output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review on #28: `WalkDir` doesn't guarantee traversal order, so the list of files in the error message could vary per run on some filesystems. Also use `remove(0)` instead of `into_iter().next().unwrap()` — identical behavior, marginally clearer. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/dev/entrypoint.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dev/entrypoint.rs b/src/dev/entrypoint.rs index 26fc510..d3a2dc9 100644 --- a/src/dev/entrypoint.rs +++ b/src/dev/entrypoint.rs @@ -40,9 +40,9 @@ pub fn locate_html_entrypoint(upload_dir: &Path) -> Result> { match found.len() { 0 => Ok(None), - 1 => Ok(Some(found.into_iter().next().unwrap())), + 1 => Ok(Some(found.remove(0))), _ => { - let names: Vec = found + let mut names: Vec = found .iter() .map(|p| { p.strip_prefix(upload_dir) @@ -51,6 +51,7 @@ pub fn locate_html_entrypoint(upload_dir: &Path) -> Result> { .to_string() }) .collect(); + names.sort(); anyhow::bail!( "Multiple HTML files found in upload_dir ({}):\n - {}\n\n\ Name one of them `index.html`, or remove the extras (often \ From 2b7355eb559f2925327ab3914c36aefdc0d729e3 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 21 Apr 2026 14:46:25 -0700 Subject: [PATCH 3/3] Cap the walk at top-level, flatten nested ifs, accept .htm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on /simplify review of #28: - `max_depth(1)` — only consider HTMLs directly under upload_dir. Avoids false "ambiguous" errors on engine-emitted nested HTML (e.g. Unity's `TemplateData/` or similar helper shells) and drops the worst-case walk cost from "entire asset tree" to "top-level entries". - Short-circuit once we've collected up to 8 HTMLs — the error message doesn't benefit from more, and this caps work for pathological cases while still showing every offender in the common small-N case. - Flatten the triple-nested if/let into a single `is_html` predicate. - Accept `.htm` alongside `.html` for consistency with `file_staging.rs`, which already accepts both for the user's configured entrypoint. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/dev/entrypoint.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/dev/entrypoint.rs b/src/dev/entrypoint.rs index d3a2dc9..ba5e35a 100644 --- a/src/dev/entrypoint.rs +++ b/src/dev/entrypoint.rs @@ -23,17 +23,30 @@ pub fn locate_html_entrypoint(upload_dir: &Path) -> Result> { return Ok(Some(default_index)); } + // Only look at the upload_dir's top-level — a Godot/Unity/etc. export + // puts its entrypoint at the root of the upload folder, and recursing + // would flag engine-emitted subdir HTML (e.g. Unity's `TemplateData/`) + // as an ambiguity. let mut found: Vec = Vec::new(); for entry in WalkDir::new(upload_dir) .min_depth(1) + .max_depth(1) .into_iter() .filter_map(|e| e.ok()) { - if entry.file_type().is_file() { - if let Some(ext) = entry.path().extension() { - if ext.eq_ignore_ascii_case("html") { - found.push(entry.into_path()); - } + if !entry.file_type().is_file() { + continue; + } + let is_html = entry + .path() + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("html") || ext.eq_ignore_ascii_case("htm")); + if is_html { + found.push(entry.into_path()); + // Two is enough to know it's ambiguous — but keep collecting so + // the error message lists every offender (capped at a handful). + if found.len() >= 8 { + break; } } }