feat(cli): skip unchanged files using device file_hashes.json
Read and update file_hashes.json on deploy; add --force-upload and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
45
deploy_manifest.py
Normal file
45
deploy_manifest.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Host-side helpers for file_hashes.json (same format as led-driver/src/file_hashes.py).
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
||||
MANIFEST_VERSION = 1
|
||||
MANIFEST_FILENAME = "file_hashes.json"
|
||||
HASH_ALGO = "sha256"
|
||||
|
||||
|
||||
def normalize_remote_path(remote_file: str) -> str:
|
||||
"""Device-relative path with forward slashes (no leading slash)."""
|
||||
return remote_file.replace("\\", "/").lstrip("/")
|
||||
|
||||
|
||||
def sha256_hex_file(path: str) -> str:
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def parse_manifest(data: bytes) -> dict:
|
||||
"""Return path -> hash map from manifest bytes."""
|
||||
try:
|
||||
doc = json.loads(data.decode("utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return {}
|
||||
if not isinstance(doc, dict):
|
||||
return {}
|
||||
files = doc.get("files")
|
||||
return dict(files) if isinstance(files, dict) else {}
|
||||
|
||||
|
||||
def build_manifest_bytes(files: dict) -> bytes:
|
||||
doc = {
|
||||
"version": MANIFEST_VERSION,
|
||||
"algorithm": HASH_ALGO,
|
||||
"files": files,
|
||||
}
|
||||
return json.dumps(doc, separators=(",", ":")).encode("utf-8")
|
||||
Reference in New Issue
Block a user