build: 部署编排与打包脚本(deploy/scripts/Docker)
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user