"""Create a portable source ZIP without environments, builds, or local credentials.""" import argparse from datetime import datetime import hashlib import os from pathlib import Path import zipfile EXCLUDED_DIRS = { ".git", ".venv", "venv", "env", "node_modules", "__pycache__", "__pypackages__", ".angular", ".gradle", ".pytest_cache", ".ruff_cache", ".mypy_cache", ".cache", ".tox", ".nox", ".hypothesis", ".eggs", ".idea", ".vscode", ".vs", ".codex", ".claude", ".gemini", ".cursor", ".ipynb_checkpoints", "build", "dist", "out", "coverage", "htmlcov", "artifacts", "traces", "scratch", "outputs", "trace_output", } EXCLUDED_SUFFIXES = { ".pyc", ".pyo", ".pyd", ".so", ".dll", ".exe", ".o", ".obj", ".a", ".lib", ".class", ".jar", ".apk", ".aab", ".wasm", ".map", ".tsbuildinfo", ".egg", ".whl", ".zip", ".7z", ".tar", ".gz", ".log", ".db", ".sqlite", ".sqlite3", ".pem", ".key", ".keystore", ".p12", ".pfx", ".swp", ".swo", } def excluded(path: Path) -> bool: parts = tuple(part.lower() for part in path.parts) name = parts[-1] if any(part in EXCLUDED_DIRS or part.endswith(".egg-info") for part in parts): return True # This tracked directory also contains generated Angular bundles. if parts[:3] == ("artemis", "resources", "showcase_ui"): return True if (name == ".env" or name.startswith(".env.")) and name != ".env.example": return True if name.startswith((".artemis_", ".jetski_", ".coverage")): return True if name in {".ds_store", "thumbs.db", ".ipc_port", ".ls_address", ".npmrc", ".pypirc"}: return True return path.suffix.lower() in EXCLUDED_SUFFIXES or name.endswith((".db-wal", ".db-shm", "~")) def source_files(root: Path): for directory, dirs, files in os.walk(root, followlinks=False): current = Path(directory) dirs[:] = sorted( name for name in dirs if not (current / name).is_symlink() and not excluded((current / name).relative_to(root)) ) for name in sorted(files): path = current / name if not path.is_symlink() and not excluded(path.relative_to(root)): yield path def package_source(root: Path, output: Path) -> tuple[int, str]: root = root.resolve() output = output.resolve() output.parent.mkdir(parents=True, exist_ok=True) temporary = output.with_suffix(".zip.tmp") files = [p for p in source_files(root) if p.resolve() not in {output, temporary}] try: with zipfile.ZipFile(temporary, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as archive: for path in files: archive.write(path, Path("artemis") / path.relative_to(root)) with zipfile.ZipFile(temporary) as archive: bad_file = archive.testzip() if bad_file: raise RuntimeError(f"ZIP integrity check failed: {bad_file}") if any(excluded(Path(name).relative_to("artemis")) for name in archive.namelist()): raise RuntimeError("An excluded file was found in the source archive") temporary.replace(output) finally: temporary.unlink(missing_ok=True) with output.open("rb") as stream: digest = hashlib.file_digest(stream, "sha256").hexdigest() return len(files), digest if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1]) parser.add_argument("--output", type=Path) args = parser.parse_args() output = ( args.output or args.root / "artifacts" / f"artemis-source-{datetime.now():%Y%m%d-%H%M%S}.zip" ) count, digest = package_source(args.root, output) print(f"Archive: {output.resolve()}") print(f"Files: {count}; size: {output.stat().st_size / (1024 * 1024):.2f} MiB") print(f"SHA256: {digest}")