Embed webui-press template assets and fix client bundling#375
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Force esbuild to use legacy TypeScript decorator output for WebUI components and add an accessible name/id pair to the docs search input. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR makes the microsoft-webui-press installed binary self-contained by embedding the default template/ and components/ assets and extracting them on demand, and it hardens the esbuild bundling pipeline so client components always compile with WebUI’s decorator semantics regardless of the consumer’s tsconfig.
Changes:
- Embed
crates/webui-press/template/andcrates/webui-press/components/into thewebui-pressbinary and extract them into a stable temp cache when--templateis not provided. - Force esbuild to use a fixed TS config via
--tsconfig-rawto ensure decorators/class-fields semantics match WebUI expectations. - Minor template markup update to give the docs search input a stable
id/name.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/webui-press/template/docs-search/docs-search.html | Adds stable id/name attributes to the search input. |
| crates/webui-press/src/main.rs | Embeds template/components and adds extraction + hashing + a regression test. |
| crates/webui-press/src/bundler.rs | Forces esbuild decorator semantics via --tsconfig-raw and adds a test. |
| crates/webui-press/Cargo.toml | Adds include_dir dependency via workspace. |
| Cargo.toml | Adds include_dir to [workspace.dependencies]. |
| Cargo.lock | Locks include_dir and macro dependencies. |
| let template_dir = root.join("template"); | ||
| if root.join(".complete").is_file() && template_dir.join("index.html").is_file() { | ||
| return Ok(template_dir); | ||
| } |
There was a problem hiding this comment.
Fixed in 7255c30. is_complete_cache now also requires the sibling components/ directory (alongside .complete and template/index.html), so an externally corrupted cache re-extracts cleanly instead of failing later with a missing-component build error.
| if root.exists() { | ||
| fs::remove_dir_all(&root) | ||
| .map_err(|e| anyhow::anyhow!("Cannot refresh embedded template assets: {e}"))?; | ||
| } | ||
| EMBEDDED_TEMPLATE | ||
| .extract(root.join("template")) | ||
| .map_err(|e| anyhow::anyhow!("Cannot extract embedded template: {e}"))?; | ||
| EMBEDDED_COMPONENTS | ||
| .extract(root.join("components")) | ||
| .map_err(|e| anyhow::anyhow!("Cannot extract embedded components: {e}"))?; | ||
| fs::write(root.join(".complete"), []) | ||
| .map_err(|e| anyhow::anyhow!("Cannot finalize embedded template assets: {e}"))?; | ||
| Ok(template_dir) |
There was a problem hiding this comment.
Fixed in 7255c30. Extraction now writes into a unique per-process staging dir (<name>.staging-<pid>-<nonce>) and publishes with a single atomic rename; no process ever remove_dir_alls or writes into the published cache. Since the cache is content-addressed, a peer that publishes first is simply reused. A stale/incomplete root is moved aside under a unique name (so concurrent movers never collide) and replaced, with a bounded retry loop to guarantee termination.
anyhow 1.0.102 is flagged by RUSTSEC-2026-0190 (unsoundness in Error::downcast_mut when context is added then downcast_mut is called), failing the Lint job's `cargo deny check advisories`. 1.0.103 is the patched release; this updates the lockfile only. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address PR review feedback on the embedded-asset cache: - Validate the sibling components/ directory (not just .complete and template/index.html) before treating a cache as usable, so an externally corrupted cache re-extracts cleanly instead of failing later with a confusing missing-component build error. - Extract into a unique per-process staging directory and publish it with a single atomic rename, and never remove_dir_all the published cache. Concurrent webui-press runs can no longer race a delete against another process's writes or observe a partially extracted cache. A stale/incomplete cache is moved aside under a unique name (so concurrent movers never collide) and replaced; the retry loop is bounded to guarantee termination. Add a regression test for the cache-completeness contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…rocess webui-press is invoked one process at a time, so the cross-process race hardening on the embedded-asset cache was more machinery than the usage warrants. Drop the per-process nonce, the move-aside-stale dance, and the bounded publish retry loop. Keep the parts that matter for the sequential case: content-addressed cache reuse, the `.complete` sentinel (including the sibling components/ validation), and atomic-rename publish. A non-complete cache is treated as a stale or interrupted extraction, cleared, re-extracted into a staging dir, and published in one atomic rename, so an interrupted run never leaves a half-written cache behind. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Why
Installing the docs generator with
cargo install microsoft-webui-pressships only the binary, so the defaulttemplate/andcomponents/directories were missing at runtime. Builds failed unless you passed--templatepointing at a source checkout, even though the README advertises a plainwebui-press build. This makes the published binary self-contained.A second problem surfaced after per-page script bundling (#373) landed: client components compiled by esbuild inherited whatever tsconfig the consumer project had, so WebUI's decorator semantics were not guaranteed and hydration bundling could break.
What
Embed template assets (
6913ba53)template/andcomponents/into the binary using theinclude_dircrate.build/servewithout--template, extract them once into a temp directory keyed by crate version plus an FNV-1a hash of the embedded contents. A.completesentinel marks a fully extracted cache so repeat runs skip re-extraction; a stale or partial directory is wiped and rebuilt.--templatestill overrides the bundled assets for local template development.id/namefor form semantics and accessibility.Fix client bundling (
a5abcdf9)--tsconfig-rawto esbuild forcingexperimentalDecorators: trueanduseDefineForClassFields: false, so component bundling always uses WebUI decorator semantics regardless of any tsconfig in the consumer project.Notes for reviewers
cargo install microsoft-webui-pressfollowed bywebui-press build/servenow works with no source tree and no flags..completesentinel keep it stable and self-healing across runs.include_dir.