#!/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 ") """ 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()