[eric] app builder: fix windows-built apps failing by always creating the workspace .env (the windows build dropped the .env.example it seeds from) and writing app files as utf-8 (windows cp1252 choked on arrows and chess pieces); stays on 1.1.69 to rebuild only the windows installer

This commit is contained in:
Eric
2026-05-26 17:40:51 -07:00
parent 8c5b3bdf64
commit ca5d69301d
4 changed files with 41 additions and 11 deletions
+8 -8
View File
@@ -230,7 +230,7 @@ def ensure_webapp_workspace_seeded_and_registered(
from backend.apps.outputs.runtime import _find_free_port
frontend_port = _find_free_port()
seed_webapp_template_workspace(folder, frontend_port)
with open(os.path.join(folder, "SKILL.md"), "w") as f:
with open(os.path.join(folder, "SKILL.md"), "w", encoding="utf-8") as f:
f.write(load_app_builder_skill())
existing = [o for o in _load_all() if o.workspace_id == workspace_id]
if existing:
@@ -311,11 +311,11 @@ async def seed_workspace(body: WorkspaceSeedRequest):
# SKILL.md still goes in workspace root; agent reads it for
# context. Live content (user-editable via Skills page) is
# injected into the system prompt regardless.
with open(os.path.join(folder, "SKILL.md"), "w") as f:
with open(os.path.join(folder, "SKILL.md"), "w", encoding="utf-8") as f:
f.write(load_app_builder_skill())
meta = body.meta or {}
if body.meta and not already_seeded:
with open(os.path.join(folder, "meta.json"), "w") as f:
with open(os.path.join(folder, "meta.json"), "w", encoding="utf-8") as f:
json.dump(body.meta, f, indent=2)
# Create (or look up) the Output record so the app appears in
# the Apps sidebar the moment the user kicks off generation.
@@ -360,12 +360,12 @@ async def seed_workspace(body: WorkspaceSeedRequest):
if not full_path.startswith(os.path.normpath(folder)):
continue
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "w") as f:
with open(full_path, "w", encoding="utf-8") as f:
f.write(content)
else:
for rel_path, content in VIEW_TEMPLATE_FILES.items():
full_path = os.path.join(folder, rel_path)
with open(full_path, "w") as f:
with open(full_path, "w", encoding="utf-8") as f:
f.write(content)
# Seed the workspace's SKILL.md with the LIVE skill content so an
@@ -374,11 +374,11 @@ async def seed_workspace(body: WorkspaceSeedRequest):
# already-seeded workspaces (the system-prompt injection in
# agent_manager reads live, so the agent always has the latest
# rules regardless of this on-disk copy).
with open(os.path.join(folder, "SKILL.md"), "w") as f:
with open(os.path.join(folder, "SKILL.md"), "w", encoding="utf-8") as f:
f.write(load_app_builder_skill())
if body.meta:
with open(os.path.join(folder, "meta.json"), "w") as f:
with open(os.path.join(folder, "meta.json"), "w", encoding="utf-8") as f:
json.dump(body.meta, f, indent=2)
return {"path": os.path.abspath(folder), "template_mode": "flat"}
@@ -494,7 +494,7 @@ async def write_workspace_file(workspace_id: str, filepath: str, body: dict):
if full_path != folder_norm and not full_path.startswith(folder_norm + os.sep):
raise HTTPException(status_code=403, detail="Path traversal not allowed")
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "w") as f:
with open(full_path, "w", encoding="utf-8") as f:
f.write(body.get("content", ""))
return {"ok": True}
+11 -1
View File
@@ -467,7 +467,7 @@ def _ensure_warm_python_venv() -> str | None:
logger.warning("warm-venv pip install failed: %s", r.stderr[-1500:])
return None
with open(sentinel, "w") as fh:
with open(sentinel, "w", encoding="utf-8") as fh:
fh.write("ok\n")
logger.info("webapp-template: warm backend venv ready at %s", venv_dir)
return venv_dir
@@ -568,6 +568,16 @@ def seed_webapp_template_workspace(workspace_dir: str, frontend_port: int) -> No
src_example = os.path.join(WEBAPP_TEMPLATE_DIR, ".env.example")
if os.path.exists(src_example):
shutil.copyfile(src_example, env_path)
else:
# .env.example can be absent from a packaged build whose copy step
# stripped dotfiles (the Windows build's recursive '.env.*' exclude did
# exactly this). Write the default directly so the workspace always has
# a .env with BACKEND_PORT=NONE; without it run.sh sees no BACKEND_PORT,
# takes the backend branch, and dies on a backend that isn't there,
# leaving the app stuck on the splash. Mac was unaffected because its
# build anchors the exclude and ships .env.example.
with open(env_path, "w", encoding="utf-8") as f:
f.write("BACKEND_PORT=NONE\nFRONTEND_PORT=4949\n")
_patch_env_port(env_path, "FRONTEND_PORT", str(frontend_port))
_patch_env_port(env_example_path, "FRONTEND_PORT", str(frontend_port))
+8 -2
View File
@@ -39,8 +39,14 @@ cleanup() {
}
trap cleanup EXIT
if [[ "${BACKEND_PORT}" == "NONE" ]]; then
echo "BACKEND_PORT=NONE — running frontend only (no backend)."
if [[ "${BACKEND_PORT}" == "NONE" || -z "${BACKEND_PORT}" || ! -f "$ROOT_DIR/backend/run.sh" ]]; then
# Frontend-only is the safe default: BACKEND_PORT=NONE (frontend-only app),
# OR unset/empty (e.g. .env missing — never start a backend that isn't
# configured), OR there is genuinely no backend/run.sh to run. Without the
# last two guards an unset BACKEND_PORT fell through to the backend branch
# and `bash backend/run.sh` died with "No such file or directory", tearing
# the whole app down before the frontend could show.
echo "Running frontend only (no backend configured)."
echo ""
bash "$ROOT_DIR/frontend/run.sh" 2>&1 | awk '{printf "\033[32m[frontend]\033[0m %s\n", $0; fflush()}' &
+14
View File
@@ -329,6 +329,20 @@ function Copy-Excluded($Source, $Dest, $Exclude) {
Copy-Excluded `
(Join-Path $ProjectRoot 'backend') (Join-Path $Staging 'backend') `
@{ Dirs = @('__pycache__','.venv','data','uv-bin','tests'); Files = @('*.pyc','.env','.env.*') }
# The '.env.*' exclude above is recursive, so it also strips the vendored
# webapp_template/.env.example that seed_workspace copies into each new app's
# .env (BACKEND_PORT=NONE). The mac build anchors its exclude to avoid this;
# here we restore the one file. Without it, Windows-built apps seed with no
# .env, run.sh takes the backend branch, and the app dies on a missing backend.
# (seed_workspace also now writes a default .env when this is absent, but
# shipping it keeps the template snapshot complete and matches mac.)
$EnvExampleSrc = Join-Path $ProjectRoot 'backend\apps\outputs\webapp_template\.env.example'
$EnvExampleDst = Join-Path $Staging 'backend\apps\outputs\webapp_template\.env.example'
if (Test-Path $EnvExampleSrc) {
New-Item -ItemType Directory -Force -Path (Split-Path $EnvExampleDst -Parent) | Out-Null
Copy-Item -Force $EnvExampleSrc $EnvExampleDst
Write-Host "Restored webapp_template/.env.example (stripped by the .env.* exclude)"
}
# data: backend/config/paths.py points DATA_ROOT at %APPDATA%/OpenSwarm/data in
# packaged mode and no code seeds from the bundle, so the entire shipped
# backend/data/ tree was dead weight (and was leaking the dev machine's