219 lines
6.4 KiB
Python
219 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate OSS metadata JSON files for release artifacts.
|
|
|
|
Usage:
|
|
python generate_oss_metadata.py \
|
|
--file dist/QwenPaw-Setup-1.0.0.exe \
|
|
--product desktop \
|
|
--platform win \
|
|
--version 1.0.0 \
|
|
--output metadata.json
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
def calculate_sha256(filepath: str) -> str:
|
|
"""Calculate SHA256 hash of a file."""
|
|
sha256_hash = hashlib.sha256()
|
|
with open(filepath, "rb") as f:
|
|
for byte_block in iter(lambda: f.read(4096), b""):
|
|
sha256_hash.update(byte_block)
|
|
return sha256_hash.hexdigest()
|
|
|
|
|
|
def format_file_size(size_bytes: int) -> str:
|
|
"""Format file size to human-readable format."""
|
|
for unit in ["B", "KB", "MB", "GB"]:
|
|
if size_bytes < 1024.0:
|
|
return f"{size_bytes:.1f} {unit}"
|
|
size_bytes /= 1024.0
|
|
return f"{size_bytes:.1f} TB"
|
|
|
|
|
|
def get_file_type(filename: str) -> str:
|
|
"""Extract file type from filename extension."""
|
|
ext = Path(filename).suffix.lower()
|
|
return ext[1:] if ext else "unknown"
|
|
|
|
|
|
def generate_metadata(
|
|
filepath: str,
|
|
product: str,
|
|
platform: str,
|
|
version: str,
|
|
) -> dict:
|
|
"""Generate metadata for a single artifact file."""
|
|
file_path = Path(filepath)
|
|
if not file_path.exists():
|
|
raise FileNotFoundError(f"File not found: {filepath}")
|
|
|
|
filename = file_path.name
|
|
file_size = file_path.stat().st_size
|
|
sha256 = calculate_sha256(filepath)
|
|
file_type = get_file_type(filename)
|
|
|
|
file_id = f"{product}-{platform}-{version}"
|
|
|
|
platform_names = {
|
|
"win-tauri": {
|
|
"zh-CN": "Windows Tauri",
|
|
"en-US": "for Windows (Tauri)",
|
|
},
|
|
"mac-tauri": {
|
|
"zh-CN": "macOS Tauri",
|
|
"en-US": "for macOS (Tauri)",
|
|
},
|
|
"win": {"zh-CN": "Windows 版", "en-US": "for Windows"},
|
|
"mac": {"zh-CN": "macOS 版", "en-US": "for macOS"},
|
|
"linux": {"zh-CN": "Linux 版", "en-US": "for Linux"},
|
|
}
|
|
|
|
product_names = {
|
|
"desktop": {"zh-CN": "桌面客户端", "en-US": "Desktop Client"},
|
|
"cli": {"zh-CN": "命令行工具", "en-US": "CLI Tool"},
|
|
}
|
|
|
|
platform_suffix = platform_names.get(
|
|
platform, {"zh-CN": platform, "en-US": platform}
|
|
)
|
|
product_name = product_names.get(
|
|
product, {"zh-CN": product, "en-US": product}
|
|
)
|
|
|
|
oss_path = f"/files/apps/{product}/{platform}/{filename}"
|
|
|
|
metadata = {
|
|
"id": file_id,
|
|
"name": {
|
|
"zh-CN": f"{product_name['zh-CN']} {platform_suffix['zh-CN']}",
|
|
"en-US": f"{product_name['en-US']} {platform_suffix['en-US']}",
|
|
},
|
|
"description": {
|
|
"zh-CN": f"适用于 {platform_suffix['zh-CN']}的{product_name['zh-CN']}安装包",
|
|
"en-US": f"{product_name['en-US']} installer {platform_suffix['en-US']}",
|
|
},
|
|
"product": product,
|
|
"platform": platform,
|
|
"version": version,
|
|
"filename": filename,
|
|
"url": oss_path,
|
|
"size": format_file_size(file_size),
|
|
"size_bytes": file_size,
|
|
"sha256": sha256,
|
|
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
"type": file_type,
|
|
}
|
|
|
|
return metadata
|
|
|
|
|
|
def merge_desktop_index(
|
|
existing_index_path: str,
|
|
new_metadata: dict,
|
|
platform: str,
|
|
) -> dict:
|
|
"""Merge new metadata into desktop/index.json."""
|
|
if os.path.exists(existing_index_path):
|
|
with open(existing_index_path, "r", encoding="utf-8") as f:
|
|
index = json.load(f)
|
|
else:
|
|
index = {
|
|
"product": "desktop",
|
|
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
"platforms": {},
|
|
"files": {},
|
|
}
|
|
|
|
if "platforms" not in index:
|
|
index["platforms"] = {}
|
|
if "files" not in index:
|
|
index["files"] = {}
|
|
if "product" not in index:
|
|
index["product"] = "desktop"
|
|
|
|
index["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
|
|
if platform not in index["platforms"]:
|
|
index["platforms"][platform] = {"latest": "", "versions": []}
|
|
|
|
file_id = new_metadata["id"]
|
|
index["platforms"][platform]["latest"] = file_id
|
|
|
|
if file_id not in index["platforms"][platform]["versions"]:
|
|
index["platforms"][platform]["versions"].insert(0, file_id)
|
|
|
|
index["files"][file_id] = new_metadata
|
|
|
|
return index
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate OSS metadata for release artifacts"
|
|
)
|
|
parser.add_argument(
|
|
"--file", required=True, help="Path to the artifact file"
|
|
)
|
|
parser.add_argument(
|
|
"--product", required=True, help="Product name (e.g., desktop, cli)"
|
|
)
|
|
parser.add_argument(
|
|
"--platform",
|
|
required=True,
|
|
help="Platform name (e.g., win, mac, linux)",
|
|
)
|
|
parser.add_argument(
|
|
"--version", required=True, help="Version string (e.g., 1.0.0)"
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
default="metadata.json",
|
|
help="Output metadata JSON file path",
|
|
)
|
|
parser.add_argument(
|
|
"--merge-index",
|
|
help="Path to existing desktop/index.json to merge into",
|
|
)
|
|
parser.add_argument(
|
|
"--output-index",
|
|
help="Output path for merged desktop/index.json",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
print(f"Generating metadata for: {args.file}")
|
|
metadata = generate_metadata(
|
|
args.file, args.product, args.platform, args.version
|
|
)
|
|
|
|
with open(args.output, "w", encoding="utf-8") as f:
|
|
json.dump(metadata, f, indent=2, ensure_ascii=False)
|
|
print(f"✓ Metadata written to: {args.output}")
|
|
print(f" ID: {metadata['id']}")
|
|
print(f" Size: {metadata['size']}")
|
|
print(f" SHA256: {metadata['sha256'][:16]}...")
|
|
|
|
# Merge into desktop/index.json if requested
|
|
if args.merge_index and args.output_index:
|
|
print(f"\nMerging into desktop index: {args.merge_index}")
|
|
merged_index = merge_desktop_index(
|
|
args.merge_index, metadata, args.platform
|
|
)
|
|
with open(args.output_index, "w", encoding="utf-8") as f:
|
|
json.dump(merged_index, f, indent=2, ensure_ascii=False)
|
|
print(f"✓ Desktop index written to: {args.output_index}")
|
|
print(
|
|
f" Latest {args.platform}: {merged_index['platforms'][args.platform]['latest']}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|