migration: establish exact preserved app baseline
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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())
|
||||
Reference in New Issue
Block a user