fix(publish): validate optional assets and default export inside project

Addresses review feedback on export_bundle:

- If subtitles_path or thumbnail_path is provided but the file is missing, the
  tool now fails with an explicit error instead of silently producing a package
  without that asset (which could ship an approved deliverable missing part of
  its content).
- Default export location now stays inside the project workspace: when the
  render lives at projects/<name>/renders/..., the bundle defaults to
  projects/<name>/exports/ (alongside artifacts/, assets/, renders/) rather than
  a repo-root exports/<name>/. export_dir remains an explicit override.

Tests cover both: missing optional asset errors, and the project-workspace
default path.
This commit is contained in:
0xDevNinja
2026-06-30 12:05:23 +05:30
parent 6dc01d4e67
commit 9bcccce2db
2 changed files with 52 additions and 1 deletions

View File

@@ -162,7 +162,19 @@ class ExportBundle(BaseTool):
title = inputs["title"]
project_name = inputs.get("project_name") or self._infer_project_name(video_path)
export_root = Path(inputs["export_dir"]).expanduser() if inputs.get("export_dir") else Path("exports") / project_name
# Explicitly-provided optional assets must exist — silently dropping them
# would ship a publish package missing part of an approved deliverable.
for key in ("subtitles_path", "thumbnail_path"):
val = inputs.get(key)
if val and not Path(val).expanduser().is_file():
return ToolResult(success=False, error=f"{key} provided but not found: {val}")
export_root = (
Path(inputs["export_dir"]).expanduser()
if inputs.get("export_dir")
else self._default_export_dir(video_path, project_name)
)
video_dir = export_root / "video"
meta_dir = export_root / "metadata"
@@ -272,6 +284,20 @@ class ExportBundle(BaseTool):
artifacts=[str(out_video)],
)
@staticmethod
def _default_export_dir(video_path: Path, project_name: str) -> Path:
"""Keep run output inside the project workspace.
When the render lives at ``projects/<name>/renders/...`` (the OpenMontage
convention), default the bundle to ``projects/<name>/exports/`` alongside
``artifacts/``, ``assets/`` and ``renders/``. Otherwise fall back to a
top-level ``exports/<project_name>/``.
"""
resolved = video_path.resolve()
if resolved.parent.name == "renders":
return resolved.parent.parent / "exports"
return Path("exports") / project_name
@staticmethod
def _infer_project_name(video_path: Path) -> str:
# projects/<name>/renders/final.mp4 -> <name>; fall back to the file stem.