- build.py: 移除 --obfuscate 参数,默认混淆模式;从 git config 读取 author,动态注入 SKILL.md - publish.py: 新增发布脚本,自动 clone 目标仓库、同步 build/ 内容、git commit+push - publish.sh: 新增一键构建+发布脚本 - skill-publishing spec: 新增发布规范 - skill-packaging spec: 更新构建规范
335 lines
10 KiB
Python
335 lines
10 KiB
Python
#!/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()
|