Files
lyxy-document/build.py
lanyuanxiaoyao a578c0b7ac refactor: 统一构建/发布脚本输出样式,更简约
- 添加 build.sh 调用脚本
- 移除装饰性分隔线和标题
- 移除进度输出,只保留必要的错误提示
- 使用 >>> 前缀标识脚本步骤
2026-03-15 12:54:40 +08:00

305 lines
8.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.
#!/usr/bin/env python3
"""
Skill 打包构建脚本(混淆模式)
使用方式:
uv run --with pyarmor python build.py
"""
import os
import sys
import shutil
import subprocess
from datetime import datetime
def generate_timestamp() -> str:
"""
生成 YYYYMMDD_HHMMSS 格式的时间戳
Returns:
时间戳字符串
"""
return datetime.now().strftime("%Y%m%d_%H%M%S")
def get_git_config(key: str) -> str:
"""
读取 git 配置项
Args:
key: 配置项名称,如 "user.name"
Returns:
配置值字符串
Raises:
subprocess.CalledProcessError: git config 命令失败
"""
result = subprocess.run(
["git", "config", "--get", key],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
def get_git_user_info() -> tuple[str, str]:
"""
读取 git user.name 和 user.email
Returns:
(name, email) 元组
Raises:
SystemExit: git 配置未设置时退出
"""
try:
name = get_git_config("user.name")
except subprocess.CalledProcessError:
print("错误: git user.name 未设置")
print("请先配置 git 用户名:")
print(' git config --global user.name "Your Name"')
sys.exit(1)
try:
email = get_git_config("user.email")
except subprocess.CalledProcessError:
print("错误: git user.email 未设置")
print("请先配置 git 邮箱:")
print(' git config --global user.email "your@email.com"')
sys.exit(1)
return name, email
def clean_and_create_build_dir(build_dir: str) -> None:
"""
删除旧 build 目录并创建新的空目录
Args:
build_dir: 构建目录路径
"""
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
os.makedirs(build_dir)
def copy_skill_md(source_path: str, target_dir: str, version: str, author: str) -> None:
"""
读取 SKILL.md 模板,动态注入 version 和 author 后写入 build/SKILL.md
Args:
source_path: 源 SKILL.md 路径
target_dir: 目标目录
version: 版本号
author: 作者信息 (格式: "Name <email>")
"""
target_path = os.path.join(target_dir, "SKILL.md")
with open(source_path, "r", encoding="utf-8") as f:
content = f.read()
lines = content.split("\n")
# 解析 frontmatter
frontmatter_start = -1
frontmatter_end = -1
frontmatter_count = 0
has_metadata = False
metadata_idx = -1
for i, line in enumerate(lines):
stripped = line.rstrip()
if stripped == "---":
frontmatter_count += 1
if frontmatter_count == 1:
frontmatter_start = i
elif frontmatter_count == 2:
frontmatter_end = i
break
elif frontmatter_count == 1 and stripped == "metadata:":
has_metadata = True
metadata_idx = i
result_lines = []
if frontmatter_start >= 0 and frontmatter_end > frontmatter_start:
# 有 frontmatter
i = 0
while i < len(lines):
if i < frontmatter_start or i >= frontmatter_end:
result_lines.append(lines[i])
i += 1
else:
# 在 frontmatter 内部
if has_metadata and i == metadata_idx:
# 找到 metadata: 行
result_lines.append(lines[i])
i += 1
version_written = False
author_written = False
# 遍历 metadata 子项,替换 version/author保留其他
while i < frontmatter_end:
stripped_line = lines[i].rstrip()
if stripped_line.startswith(" version:"):
if not version_written:
result_lines.append(f" version: \"{version}\"")
version_written = True
i += 1
elif stripped_line.startswith(" author:"):
if not author_written:
result_lines.append(f" author: \"{author}\"")
author_written = True
i += 1
elif stripped_line.startswith(" "):
# 其他 metadata 子项,保留
result_lines.append(lines[i])
i += 1
else:
# metadata 块结束
break
# 确保 version/author 都写了
if not version_written:
result_lines.append(f" version: \"{version}\"")
if not author_written:
result_lines.append(f" author: \"{author}\"")
elif not has_metadata and i == frontmatter_end - 1:
# 没有 metadata在 frontmatter 末尾插入
result_lines.append("metadata:")
result_lines.append(f" version: \"{version}\"")
result_lines.append(f" author: \"{author}\"")
result_lines.append(lines[i])
i += 1
else:
result_lines.append(lines[i])
i += 1
else:
# 没有 frontmatter新建一个
result_lines.append("---")
result_lines.append("name: lyxy-document-reader")
result_lines.append("metadata:")
result_lines.append(f" version: \"{version}\"")
result_lines.append(f" author: \"{author}\"")
result_lines.append("---")
result_lines.append("")
result_lines.extend(lines)
new_content = "\n".join(result_lines)
with open(target_path, "w", encoding="utf-8") as f:
f.write(new_content)
def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None:
"""
使用 PyArmor 混淆 scripts 目录
Args:
source_dir: 源代码目录 (scripts/)
target_dir: 目标构建目录 (build/)
"""
# 检查 pyarmor 是否可用
try:
__import__("pyarmor")
except ImportError:
print("错误: PyArmor 未安装")
print("请使用以下命令:")
print(" uv run --with pyarmor python build.py")
sys.exit(1)
# 临时目录
temp_dir = os.path.join(target_dir, "temp_pyarmor")
# 清理已存在的临时目录
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
# PyArmor 命令 (Normal Mode)
cmd = [
"pyarmor",
"gen",
"--recursive",
"-O", temp_dir,
source_dir
]
try:
result = subprocess.run(
cmd,
check=True,
capture_output=True,
text=True
)
except subprocess.CalledProcessError as e:
print("错误: PyArmor 混淆失败")
print(f" 返回码: {e.returncode}")
print(f" 标准输出: {e.stdout}")
print(f" 错误输出: {e.stderr}")
sys.exit(1)
# 移动混淆后的文件到最终位置
scripts_dst_dir = os.path.join(target_dir, "scripts")
pyarmor_runtime_dir = None
# 先移动 scripts 目录
for item in os.listdir(temp_dir):
src = os.path.join(temp_dir, item)
if item == "scripts":
dst = os.path.join(target_dir, item)
if os.path.exists(dst):
if os.path.isdir(dst):
shutil.rmtree(dst)
else:
os.remove(dst)
shutil.move(src, dst)
elif item.startswith("pyarmor_runtime"):
pyarmor_runtime_dir = item
# 再移动 pyarmor_runtime 到 scripts 内部
if pyarmor_runtime_dir:
src = os.path.join(temp_dir, pyarmor_runtime_dir)
dst = os.path.join(scripts_dst_dir, pyarmor_runtime_dir)
if os.path.exists(dst):
if os.path.isdir(dst):
shutil.rmtree(dst)
else:
os.remove(dst)
shutil.move(src, dst)
# 清理临时目录
os.rmdir(temp_dir)
def main() -> None:
"""
主函数:执行完整的混淆打包流程
"""
# 路径配置
project_root = os.path.dirname(os.path.abspath(__file__))
skill_md_path = os.path.join(project_root, "SKILL.md")
scripts_source_dir = os.path.join(project_root, "scripts")
build_dir = os.path.join(project_root, "build")
# 生成版本号
version = generate_timestamp()
# 读取 git 用户信息
git_name, git_email = get_git_user_info()
author = f"{git_name} <{git_email}>"
# 清理并创建 build 目录
clean_and_create_build_dir(build_dir)
# 复制 SKILL.md动态注入元数据
copy_skill_md(skill_md_path, build_dir, version, author)
# 混淆代码
obfuscate_scripts_dir(scripts_source_dir, build_dir)
if __name__ == "__main__":
main()