Files
xiaobaifupan/tools/build_preservation_manifest.py

140 lines
3.6 KiB
Python

from __future__ import annotations
import hashlib
import json
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
TARGET = ROOT / "app"
OUTPUT = ROOT / "docs" / "migration" / "原版资产清单.json"
DIRECTORIES = (
"backend",
"config",
"static",
"tests",
"tools",
"vendor",
"游资skills",
)
ROOT_FILES = (
".dockerignore",
".env.example",
".gitignore",
"advanced_strategies.py",
"alert_service.py",
"api_access.py",
"app_config.py",
"ARCHITECTURE.md",
"assistant_agent.py",
"chart_data_provider.py",
"compose.yaml",
"database.py",
"demo_data.py",
"Dockerfile",
"DOCKER_DEPLOY.md",
"heaven_agent.py",
"heaven_engine.py",
"ifind_client.py",
"llm_strategy.py",
"llm_stream.py",
"market_insights.py",
"mentor_agent.py",
"package-lock.json",
"package.json",
"playwright.config.js",
"README.md",
"realtime_aggregator.py",
"requirements.txt",
"screener.py",
"security.py",
"sentiment_engine.py",
"server.py",
"strategy_tracking.py",
"sync_data.py",
"THIRD_PARTY_NOTICES.md",
"trade_journal.py",
"tushare_client.py",
)
def digest(path: Path) -> str:
checksum = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
checksum.update(block)
return checksum.hexdigest()
def source_files() -> list[Path]:
files = [ROOT / name for name in ROOT_FILES]
for directory in DIRECTORIES:
files.extend(
path
for path in (ROOT / directory).rglob("*")
if path.is_file() and "__pycache__" not in path.parts
)
return sorted(set(files), key=lambda path: path.relative_to(ROOT).as_posix())
def build_manifest() -> dict[str, object]:
assets = []
mismatches = []
for source in source_files():
relative = source.relative_to(ROOT)
target = TARGET / relative
source_hash = digest(source)
target_hash = digest(target) if target.is_file() else ""
status = "identical" if source_hash == target_hash else "mismatch"
if status != "identical":
mismatches.append(relative.as_posix())
assets.append(
{
"source": relative.as_posix(),
"target": f"app/{relative.as_posix()}",
"bytes": source.stat().st_size,
"sha256": source_hash,
"disposition": "original_copy_pending_move",
"status": status,
}
)
return {
"schema_version": 1,
"generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"source_commit": "41329943c4878fc09ed82ec376eb93ab151e4092",
"source_root": ".",
"target_root": "app",
"excluded": [
"next/",
"data/review.db and SQLite sidecars",
"data/private-mentor-skills/",
".env",
"node_modules/",
"logs, caches and generated test results",
],
"asset_count": len(assets),
"mismatch_count": len(mismatches),
"mismatches": mismatches,
"assets": assets,
}
def main() -> int:
manifest = build_manifest()
OUTPUT.write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(
f"assets={manifest['asset_count']} mismatches={manifest['mismatch_count']} "
f"output={OUTPUT}"
)
return 1 if manifest["mismatch_count"] else 0
if __name__ == "__main__":
raise SystemExit(main())