Spaces:
No application file
No application file
| from __future__ import annotations | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from typing import Any | |
| from uuid import uuid4 | |
| from zipfile import ZIP_DEFLATED, ZipFile | |
| from .file_service import ( | |
| SYSTEM_FILENAMES, | |
| build_file_tree_from_paths, | |
| build_required_docs, | |
| ensure_within_directory, | |
| sanitize_relative_path, | |
| slugify, | |
| ) | |
| def create_project_zip(preview: dict[str, Any], generated_dir: Path) -> dict[str, str]: | |
| generated_dir.mkdir(parents=True, exist_ok=True) | |
| project_name = str(preview.get("projectName") or "Generated Project") | |
| slug = slugify(project_name) | |
| timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") | |
| unique_suffix = uuid4().hex[:8] | |
| bundle_name = f"{slug}-{timestamp}-{unique_suffix}" | |
| project_dir = generated_dir / bundle_name | |
| project_dir.mkdir(parents=True, exist_ok=False) | |
| _write_project_source_files(project_dir, preview) | |
| bundle_info: dict[str, Any] = {} | |
| actual_paths = [ | |
| path.relative_to(project_dir).as_posix() | |
| for path in sorted(project_dir.rglob("*")) | |
| if path.is_file() | |
| ] | |
| bundle_info["actualFileTree"] = build_file_tree_from_paths(actual_paths) | |
| required_docs = build_required_docs(preview, bundle_info) | |
| full_paths = sorted(set(actual_paths + list(required_docs.keys()))) | |
| bundle_info["actualFileTree"] = build_file_tree_from_paths(full_paths) | |
| required_docs = build_required_docs(preview, bundle_info) | |
| for doc_name, content in required_docs.items(): | |
| target_path = project_dir / doc_name | |
| _write_text_file(project_dir, target_path, content) | |
| zip_path = generated_dir / f"{bundle_name}.zip" | |
| with ZipFile(zip_path, "w", compression=ZIP_DEFLATED) as zip_file: | |
| for file_path in sorted(project_dir.rglob("*")): | |
| if file_path.is_file(): | |
| arcname = Path(bundle_name) / file_path.relative_to(project_dir) | |
| zip_file.write(file_path, arcname=arcname) | |
| return { | |
| "filename": zip_path.name, | |
| "downloadUrl": f"/downloads/{zip_path.name}", | |
| } | |
| def _write_project_source_files(project_dir: Path, preview: dict[str, Any]) -> None: | |
| written_paths: set[Path] = set() | |
| for file_entry in preview.get("files", []): | |
| relative_path = sanitize_relative_path(str(file_entry.get("path", ""))) | |
| if relative_path in written_paths or ( | |
| relative_path.name in SYSTEM_FILENAMES and relative_path.parent == Path(".") | |
| ): | |
| continue | |
| content = str(file_entry.get("content", "")) | |
| target_path = project_dir / relative_path | |
| _write_text_file(project_dir, target_path, content) | |
| written_paths.add(relative_path) | |
| def _write_text_file(project_dir: Path, target_path: Path, content: str) -> None: | |
| target_path.parent.mkdir(parents=True, exist_ok=True) | |
| if not ensure_within_directory(project_dir, target_path): | |
| raise ValueError(f"Refusing to write outside the generated project folder: {target_path}") | |
| target_path.write_text(content, encoding="utf-8") | |