96 lines
5.2 KiB
Python
96 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Add a bundled native builder to a clean initialized project, without relocking."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
|
|
import ppt_project as project_cli
|
|
import template_ref as ref
|
|
|
|
|
|
def prepare(root: Path):
|
|
root = project_cli.root_path(root)
|
|
config, _ = project_cli.check(root)
|
|
ref.require(config["authoring"]["route"] == "native", "prepare requires authoring.route=native")
|
|
ref.require(config["build"]["entrypoint"] is None and not config["build"]["inputs"], "prepare refuses an existing/custom builder")
|
|
environment = project_cli.safe_path(root, config["environment"], "environment")
|
|
old_environment = environment.read_bytes()
|
|
env = ref.read_json(environment, (1,))
|
|
ref.require(not env["packages"] and not env["fonts"] and not env["tools"], "prepare refuses to overwrite a configured environment")
|
|
manifest = ref.load_manifest(root / config["template"]["manifest"])
|
|
ref.require(manifest["schema_version"] == 2, "prepare requires manifest schema 2")
|
|
template_root = (root / config["template"]["manifest"]).parent
|
|
style = ref.read_json(ref.file_path(template_root, manifest["style"], "style"), None)
|
|
workflow = root / "vendor/native-workflow"
|
|
builder = root / "tools/native_builder.py"
|
|
ref.require(not workflow.exists() and not builder.exists(), "prepare refuses existing native-workflow or native_builder.py paths")
|
|
skill = Path(__file__).resolve().parent.parent
|
|
provenance = ref.read_json(skill / "runtime/provenance.json", (1,))
|
|
package_lines = (skill / "runtime/requirements.txt").read_text(encoding="utf-8").splitlines()
|
|
packages = dict(line.split("==", 1) for line in package_lines if line.strip() and not line.startswith("#"))
|
|
fonts = [{"role": role, **entry, "distributed": False, "embedded": False,
|
|
"verification": "declared by the selected template; installation, file identity and licenses require destination readback"}
|
|
for role, entry in style["typography"]["families"].items()]
|
|
new_env = {"schema_version": 1, "python": {"minimum": "3.12", "project_cli_minimum": "3.10"},
|
|
"packages": packages, "fonts": fonts,
|
|
"tools": [{"name": provenance["component"], "version": provenance["version"],
|
|
"path": "vendor/native-workflow/runtime/ppt-master", "source": provenance["source"],
|
|
"license": provenance["license"], "upstream_modified": False}],
|
|
"external_executables": [], "capabilities": {"image_generation": "host tool when needed",
|
|
"PowerPoint": "host application and supported UI tool for application checks"}}
|
|
old_config = (root / "project.json").read_bytes()
|
|
created = []
|
|
with tempfile.TemporaryDirectory(prefix="native-prepare-", dir=root.parent) as temporary:
|
|
staging = Path(temporary) / "native-workflow"
|
|
staging.mkdir()
|
|
shutil.copytree(skill / "runtime", staging / "runtime", ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"))
|
|
(staging / "scripts").mkdir()
|
|
for name in ("native_export.py", "finalize_theme.py", "template_ref.py"):
|
|
shutil.copyfile(skill / "scripts" / name, staging / "scripts" / name)
|
|
# The snapshot is an explicit build input; no installed Skill is needed
|
|
# after this project is moved to a different directory or machine.
|
|
inputs = ["vendor/native-workflow/" + path.relative_to(staging).as_posix()
|
|
for path in sorted(staging.rglob("*")) if path.is_file()]
|
|
try:
|
|
shutil.copytree(staging, workflow)
|
|
created.append(workflow)
|
|
with builder.open("xb") as target:
|
|
target.write((skill / "scripts/native_builder.py").read_bytes())
|
|
created.append(builder)
|
|
config["build"]["entrypoint"] = "tools/native_builder.py"
|
|
config["build"]["inputs"] = inputs
|
|
project_cli.atomic_json(environment, new_env)
|
|
project_cli.atomic_json(root / "project.json", config)
|
|
project_cli.check(root, author_lock=False)
|
|
except Exception:
|
|
environment.write_bytes(old_environment)
|
|
(root / "project.json").write_bytes(old_config)
|
|
for item in reversed(created):
|
|
shutil.rmtree(item) if item.is_dir() else item.unlink(missing_ok=True)
|
|
raise
|
|
return {"ok": True, "project": str(root), "builder": config["build"]["entrypoint"],
|
|
"copied_inputs": len(inputs), "author_lock_updated": False,
|
|
"next": "Review source and environment changes, add finalized page SVGs/notes, then run tools/ppt_project.py lock --project ."}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--project", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
try:
|
|
print(json.dumps(prepare(args.project), ensure_ascii=False, indent=2))
|
|
return 0
|
|
except (ref.InputError, OSError, ValueError, KeyError, TypeError) as error:
|
|
print(json.dumps({"error": str(error)}, ensure_ascii=False), file=sys.stderr)
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|