Files
lyxy-document/build.py
lanyuanxiaoyao a8af3cc6c4 feat: 添加 skill 发布功能和混淆构建优化
- build.py: 移除 --obfuscate 参数,默认混淆模式;从 git config 读取 author,动态注入 SKILL.md
- publish.py: 新增发布脚本,自动 clone 目标仓库、同步 build/ 内容、git commit+push
- publish.sh: 新增一键构建+发布脚本
- skill-publishing spec: 新增发布规范
- skill-packaging spec: 更新构建规范
2026-03-11 12:22:46 +08:00

335 lines
10 KiB
Python
Raw 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 未设置
请先配置 git 用户名:
git config --global user.name "Your Name"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
""")
sys.exit(1)
try:
email = get_git_config("user.email")
except subprocess.CalledProcessError:
print("""
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
错误: git user.email 未设置
请先配置 git 邮箱:
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):
print(f"清理旧构建目录: {build_dir}")
shutil.rmtree(build_dir)
os.makedirs(build_dir)
print(f"创建构建目录: {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)
print(f"生成: {target_path} (version: {version}, author: {author})")
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 未安装
请使用以下命令:
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
]
print(f" 执行: {' '.join(cmd)}")
try:
result = subprocess.run(
cmd,
check=True,
capture_output=True,
text=True
)
except subprocess.CalledProcessError as e:
print(f"\nPyArmor 混淆失败:")
print(f" 返回码: {e.returncode}")
print(f" 标准输出: {e.stdout}")
print(f" 错误输出: {e.stderr}")
sys.exit(1)
# 移动混淆后的文件到最终位置
for item in os.listdir(temp_dir):
src = os.path.join(temp_dir, item)
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)
# 清理临时目录
os.rmdir(temp_dir)
print(" 混淆完成")
def main() -> None:
"""
主函数:执行完整的混淆打包流程
"""
print("=" * 60)
print("Skill 打包构建 (混淆模式)")
print("=" * 60)
# 路径配置
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()
print(f"版本号: {version}")
# 读取 git 用户信息
git_name, git_email = get_git_user_info()
author = f"{git_name} <{git_email}>"
print(f"作者: {author}")
print()
# 清理并创建 build 目录
clean_and_create_build_dir(build_dir)
print()
# 复制 SKILL.md动态注入元数据
copy_skill_md(skill_md_path, build_dir, version, author)
print()
# 混淆代码
print("────────────────────────────────────────")
print(" 使用 PyArmor 混淆代码 (Normal Mode)")
print("────────────────────────────────────────")
obfuscate_scripts_dir(scripts_source_dir, build_dir)
print()
# 完成信息
print("=" * 60)
print("构建完成!")
print(f"版本号: {version}")
print(f"作者: {author}")
print("混淆模式: 已生成 .pyx 和 pyarmor_runtime")
print(f"输出目录: {build_dir}")
print("=" * 60)
if __name__ == "__main__":
main()