mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
Merge pull request #157 from MervinPraison/claude/issue-150-20260624-000000
fix(compiler): write all build output as UTF-8 (Windows cp1252)
This commit is contained in:
7 files changed
+74
-15
No files matched your search
@@ -157,7 +157,7 @@ routes:
|
||||
template: "docs"
|
||||
"""
|
||||
|
||||
config_path.write_text(minimal_config)
|
||||
config_path.write_text(minimal_config, encoding="utf-8")
|
||||
console.print(Panel(f"[green]✓[/green] Created {config_path}", title="Success"))
|
||||
|
||||
|
||||
@@ -598,7 +598,7 @@ def dev(
|
||||
if dark_mode is not None:
|
||||
config["site"]["theme"]["darkMode"] = dark_mode
|
||||
tmp_cfg = example_path / ".aiui.tmp.yaml"
|
||||
with open(tmp_cfg, "w") as f:
|
||||
with open(tmp_cfg, "w", encoding="utf-8") as f:
|
||||
yaml.dump(config, f, default_flow_style=False)
|
||||
build_config = str(tmp_cfg)
|
||||
|
||||
@@ -1025,7 +1025,7 @@ def dev(
|
||||
if example_name and example_name in examples:
|
||||
yaml_path = examples_dir / example_name / "aiui.template.yaml"
|
||||
# Write the new YAML
|
||||
with open(yaml_path, "w") as f:
|
||||
with open(yaml_path, "w", encoding="utf-8") as f:
|
||||
f.write(yaml_content)
|
||||
console.print(
|
||||
f"[yellow]Rebuilding {example_name} with edited YAML...[/yellow]"
|
||||
@@ -1520,12 +1520,14 @@ def run(
|
||||
},
|
||||
"style": style,
|
||||
}
|
||||
with open(ui_config_path, "w") as f:
|
||||
with open(ui_config_path, "w", encoding="utf-8") as f:
|
||||
_json.dump(ui_cfg, f, indent=2)
|
||||
|
||||
# Write minimal route-manifest and docs-nav so the SPA doesn't error
|
||||
(output / "route-manifest.json").write_text('{"routes": []}')
|
||||
(output / "docs-nav.json").write_text('{"items": []}')
|
||||
(output / "route-manifest.json").write_text(
|
||||
'{"routes": []}', encoding="utf-8"
|
||||
)
|
||||
(output / "docs-nav.json").write_text('{"items": []}', encoding="utf-8")
|
||||
|
||||
# Find available port
|
||||
def find_available_port(start_port: int, max_attempts: int = 10) -> int:
|
||||
|
||||
@@ -422,7 +422,9 @@ class Compiler:
|
||||
|
||||
def _write_json(self, path: Path, data: dict, indent: int | None) -> None:
|
||||
"""Write JSON data to file."""
|
||||
path.write_text(json.dumps(data, indent=indent, ensure_ascii=False))
|
||||
path.write_text(
|
||||
json.dumps(data, indent=indent, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
|
||||
def _copy_viewer(self, output_dir: Path) -> None:
|
||||
"""Copy frontend bundle to output directory."""
|
||||
@@ -478,7 +480,7 @@ class Compiler:
|
||||
"document.documentElement.classList.add('dark');",
|
||||
"/* light mode — no dark class */",
|
||||
)
|
||||
html_path.write_text(html)
|
||||
html_path.write_text(html, encoding="utf-8")
|
||||
|
||||
def _copy_plugins(self, output_dir: Path, frontend_dir: Path) -> None:
|
||||
"""Copy frontend plugins and generate plugins.json config."""
|
||||
@@ -519,7 +521,9 @@ class Compiler:
|
||||
ordered.append(p)
|
||||
|
||||
plugins_config = {"plugins": ordered}
|
||||
(plugins_dst / "plugins.json").write_text(json.dumps(plugins_config, indent=2))
|
||||
(plugins_dst / "plugins.json").write_text(
|
||||
json.dumps(plugins_config, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
def _generate_route_pages(self, output_dir: Path, nav: dict) -> list[str]:
|
||||
"""Generate per-route HTML files for SPA fallback and SEO.
|
||||
@@ -593,7 +597,7 @@ class Compiler:
|
||||
f'<div id="root"><noscript>{noscript_content}</noscript></div>',
|
||||
)
|
||||
|
||||
page_file.write_text(html)
|
||||
page_file.write_text(html, encoding="utf-8")
|
||||
files.append(f"{relative}/index.html")
|
||||
|
||||
return files
|
||||
|
||||
@@ -171,7 +171,7 @@ class YAMLConfigStore:
|
||||
# Write to temp file in same directory, then atomic rename
|
||||
fd, tmp_path = tempfile.mkstemp(dir=str(self._path.parent), suffix=".yaml.tmp")
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||
yaml.dump(
|
||||
self._data,
|
||||
f,
|
||||
|
||||
@@ -278,7 +278,7 @@ def _save_data() -> None:
|
||||
"saved_at": time.time(),
|
||||
}
|
||||
_data_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(_data_file, "w") as f:
|
||||
with open(_data_file, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f)
|
||||
except Exception:
|
||||
pass # Silently fail
|
||||
|
||||
@@ -1466,7 +1466,7 @@ async def api_config_handler(request: Request) -> JSONResponse:
|
||||
config_data = body.get("config", body)
|
||||
import yaml
|
||||
|
||||
with open(_config_path, "w") as f:
|
||||
with open(_config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
|
||||
_config_cache = config_data
|
||||
return JSONResponse({"status": "saved", "config_path": str(_config_path)})
|
||||
|
||||
@@ -256,7 +256,7 @@ def _write_disk_cache(data: dict) -> None:
|
||||
try:
|
||||
path = _disk_cache_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data))
|
||||
path.write_text(json.dumps(data), encoding="utf-8")
|
||||
except OSError:
|
||||
pass # Non-critical — build still works
|
||||
|
||||
@@ -357,7 +357,7 @@ def inject_theme_css(
|
||||
# Write to theme.css
|
||||
theme_file = output_dir / "assets" / "theme.css"
|
||||
theme_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
theme_file.write_text(css)
|
||||
theme_file.write_text(css, encoding="utf-8")
|
||||
|
||||
|
||||
def get_available_themes() -> list[str]:
|
||||
|
||||
@@ -599,3 +599,56 @@ class TestCompositionResolver:
|
||||
assert len(left_sidebar_widgets) == 1
|
||||
assert left_sidebar_widgets[0]["props"]["searchable"] is True
|
||||
assert "collapsible" not in left_sidebar_widgets[0]["props"]
|
||||
|
||||
|
||||
class TestCompilerUtf8Writes:
|
||||
"""Regression tests for issue #150 — UTF-8 on write (Windows cp1252)."""
|
||||
|
||||
SPARKLE = "\u2728"
|
||||
|
||||
def test_doc_page_written_as_utf8(self, tmp_path: Path):
|
||||
docs_dir = tmp_path / "docs"
|
||||
docs_dir.mkdir()
|
||||
(docs_dir / "index.md").write_text(
|
||||
f"# Welcome\n\n## {self.SPARKLE} Features\n\nHello!", encoding="utf-8"
|
||||
)
|
||||
|
||||
config = Config(
|
||||
site=SiteConfig(title="Unicode Site"),
|
||||
content=ContentConfig(docs=ContentSourceConfig(dir=str(docs_dir))),
|
||||
templates={"docs": TemplateConfig(layout="Default", slots={})},
|
||||
routes=[RouteConfig(match="/docs/**", template="docs")],
|
||||
)
|
||||
|
||||
compiler = Compiler(config, base_path=tmp_path)
|
||||
result = compiler.compile(tmp_path / "output")
|
||||
assert result.success is True
|
||||
|
||||
page = tmp_path / "output" / "docs" / "index" / "index.html"
|
||||
assert page.exists()
|
||||
raw = page.read_bytes()
|
||||
assert self.SPARKLE in raw.decode("utf-8")
|
||||
assert b"\xe2\x9c\xa8" in raw
|
||||
|
||||
def test_write_json_uses_utf8(self, minimal_config: Config, tmp_path: Path):
|
||||
compiler = Compiler(minimal_config, base_path=tmp_path)
|
||||
target = tmp_path / "out.json"
|
||||
compiler._write_json(target, {"sparkle": self.SPARKLE}, indent=2)
|
||||
raw = target.read_bytes()
|
||||
assert self.SPARKLE in raw.decode("utf-8")
|
||||
assert b"\xe2\x9c\xa8" in raw
|
||||
|
||||
def test_plugins_json_uses_utf8(self, tmp_path: Path):
|
||||
frontend_dir = tmp_path / "frontend"
|
||||
plugins_dst = frontend_dir / "plugins"
|
||||
plugins_dst.mkdir(parents=True)
|
||||
config = Config(
|
||||
site=SiteConfig(title="Plugin Site"),
|
||||
templates={"docs": TemplateConfig(layout="Default", slots={})},
|
||||
routes=[RouteConfig(match="/docs/**", template="docs")],
|
||||
)
|
||||
compiler = Compiler(config, base_path=tmp_path)
|
||||
compiler._copy_plugins(tmp_path / "output", frontend_dir)
|
||||
plugins_file = tmp_path / "output" / "plugins" / "plugins.json"
|
||||
assert plugins_file.exists()
|
||||
assert plugins_file.read_bytes().decode("utf-8")
|
||||
Reference in new issue
Block a user