Files
Artemis-Eapil/scripts/package_source.py
T
chenmingxuan 8e757d4626
CI / Python 3.12 / ubuntu-latest (push) Canceled after 0s
CI / Python 3.12 / windows-latest (push) Canceled after 0s
CI / Frontend tests and production build (push) Canceled after 0s
CI / Build and smoke-test Python packages (push) Canceled after 0s
fix: exclude local Roo MCP settings from source archives
2026-09-22 23:51:41 +08:00

156 lines
4.1 KiB
Python

"""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",
".roo",
".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}")