Files
iAOP/templates/resin/template_packaging.py

115 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""树脂模板打包与版本发布 —— issue #85(父 Issue #13 子任务)。
按 `version.yaml`(#13)的 assets 清单把 iAOP-Template-Resin 打包发布:
- `validate_version()`:semver 校验 + assets 清单文件/目录存在性检查
(发布前门禁,防止打包不完整);
- `package_template()`:按清单打包 zip(含 version.yaml + README),
输出 `dist/resin-{version}.zip`;
- `render_release_notes()`:从 version.yaml 生成 Markdown 发布说明
(版本/行业/基线/资产清单/备注),供仓库 Release 使用。
纯标准库实现(zipfile),无第三方依赖。
"""
from __future__ import annotations
import os
import re
import zipfile
from typing import List, Optional
import yaml
#: 模板根目录(相对本模块)
TEMPLATE_ROOT = os.path.dirname(os.path.abspath(__file__))
#: 版本清单(与 #13 同构)
VERSION_PATH = os.path.join(TEMPLATE_ROOT, "version.yaml")
#: semver 校验(x.y.z)
_SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
#: 打包时始终包含的清单外文件
_ALWAYS_INCLUDE = ["version.yaml", "README.md"]
def load_version(path: str = VERSION_PATH) -> dict:
"""加载版本清单。"""
with open(path, "r", encoding="utf-8") as fh:
return yaml.safe_load(fh) or {}
def validate_version(path: str = VERSION_PATH) -> List[str]:
"""版本清单校验:semver 合法 + assets 文件/目录存在。
Returns:
问题列表(空 = 校验通过)。
"""
problems: List[str] = []
version = load_version(path)
ver = str(version.get("version", ""))
if not _SEMVER_RE.match(ver):
problems.append(f"version 非法(需 semver x.y.z):{ver!r}")
for rel in version.get("assets", []):
full = os.path.join(TEMPLATE_ROOT, rel)
if not os.path.exists(full):
problems.append(f"资产缺失:{rel}")
return problems
def _add_entry(zf: zipfile.ZipFile, rel: str) -> None:
"""把单个文件或目录加入 zip(保留相对路径)。"""
full = os.path.join(TEMPLATE_ROOT, rel)
if os.path.isdir(full):
for root, _, files in os.walk(full):
for name in files:
abs_path = os.path.join(root, name)
zf.write(abs_path, os.path.relpath(abs_path, TEMPLATE_ROOT))
else:
zf.write(full, rel)
def package_template(path: str = VERSION_PATH,
output_dir: Optional[str] = None) -> str:
"""按 assets 清单打包模板为 zip。
Returns:
生成的 zip 文件路径。
"""
version = load_version(path)
ver = str(version.get("version", ""))
problems = validate_version(path)
if problems:
raise ValueError("打包前校验未通过:" + "; ".join(problems))
dist = output_dir or os.path.join(TEMPLATE_ROOT, "dist")
os.makedirs(dist, exist_ok=True)
zip_path = os.path.join(dist, f"resin-{ver}.zip")
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
entries = _ALWAYS_INCLUDE + list(version.get("assets", []))
for rel in dict.fromkeys(entries): # 去重保序
_add_entry(zf, rel)
return zip_path
def render_release_notes(path: str = VERSION_PATH) -> str:
"""从 version.yaml 生成 Markdown 发布说明。"""
version = load_version(path)
lines = [
f"# iAOP-Template-Resin v{version.get('version', '?')}",
"",
f"- 行业:{version.get('industry', '')}",
f"- 基线:{version.get('baseline', '')}",
f"- 并行:{version.get('parallel_with', '')}",
f"- 状态:{version.get('status', '')}",
"",
"## 资产清单",
]
lines += [f"- `{a}`" for a in version.get("assets", [])]
notes = version.get("notes", [])
if notes:
lines.append("")
lines.append("## 备注")
lines += [f"- {n}" for n in notes]
return "\n".join(lines) + "\n"