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: 更新构建规范
This commit is contained in:
300
publish.py
Normal file
300
publish.py
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Skill 发布脚本
|
||||
|
||||
使用方式:
|
||||
uv run python publish.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
TARGET_REPO_URL = "https://github.com/lanyuanxiaoyao/skills.git"
|
||||
TARGET_PATH = "skills/lyxy-document-reader"
|
||||
|
||||
|
||||
def check_build_dir(build_dir: str) -> None:
|
||||
"""
|
||||
检查 build/ 目录是否存在
|
||||
|
||||
Args:
|
||||
build_dir: build 目录路径
|
||||
|
||||
Raises:
|
||||
SystemExit: 目录不存在时退出
|
||||
"""
|
||||
if not os.path.exists(build_dir):
|
||||
print("""
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
错误: build/ 目录不存在
|
||||
|
||||
请先运行 build.py:
|
||||
uv run python build.py
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
""")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_build_skill_md(build_skill_md_path: str) -> None:
|
||||
"""
|
||||
检查 build/SKILL.md 是否存在
|
||||
|
||||
Args:
|
||||
build_skill_md_path: build/SKILL.md 路径
|
||||
|
||||
Raises:
|
||||
SystemExit: 文件不存在时退出
|
||||
"""
|
||||
if not os.path.exists(build_skill_md_path):
|
||||
print("""
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
错误: build/SKILL.md 不存在
|
||||
|
||||
请先运行 build.py:
|
||||
uv run python build.py
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
""")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def parse_version_from_skill_md(skill_md_path: str) -> str:
|
||||
"""
|
||||
从 SKILL.md 解析出版本号
|
||||
|
||||
Args:
|
||||
skill_md_path: SKILL.md 路径
|
||||
|
||||
Returns:
|
||||
版本号字符串
|
||||
|
||||
Raises:
|
||||
SystemExit: 解析失败时退出
|
||||
"""
|
||||
with open(skill_md_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 简单解析 YAML frontmatter 中的 version
|
||||
lines = content.split("\n")
|
||||
in_frontmatter = False
|
||||
in_metadata = False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped == "---":
|
||||
if not in_frontmatter:
|
||||
in_frontmatter = True
|
||||
else:
|
||||
break
|
||||
continue
|
||||
if in_frontmatter:
|
||||
if stripped == "metadata:":
|
||||
in_metadata = True
|
||||
elif in_metadata and stripped.startswith("version:"):
|
||||
# 提取版本号,去掉引号
|
||||
version_part = stripped.split(":", 1)[1].strip()
|
||||
version = version_part.strip('"').strip("'")
|
||||
return version
|
||||
elif in_metadata and stripped and not stripped.startswith(" "):
|
||||
# metadata 块结束
|
||||
in_metadata = False
|
||||
|
||||
print("""
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
错误: 无法从 build/SKILL.md 解析版本号
|
||||
|
||||
请检查 build/SKILL.md 是否包含 metadata.version 字段
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
""")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def run_git_command(repo_dir: str, args: list[str]) -> subprocess.CompletedProcess:
|
||||
"""
|
||||
在指定目录运行 git 命令
|
||||
|
||||
Args:
|
||||
repo_dir: 仓库目录
|
||||
args: git 命令参数列表
|
||||
|
||||
Returns:
|
||||
subprocess.CompletedProcess
|
||||
|
||||
Raises:
|
||||
subprocess.CalledProcessError: 命令失败时
|
||||
"""
|
||||
cmd = ["git"] + args
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
cwd=repo_dir,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
|
||||
def clone_repo(temp_dir: str) -> str:
|
||||
"""
|
||||
在临时目录 clone 目标仓库
|
||||
|
||||
Args:
|
||||
temp_dir: 临时目录路径
|
||||
|
||||
Returns:
|
||||
仓库目录路径
|
||||
|
||||
Raises:
|
||||
SystemExit: clone 失败时退出
|
||||
"""
|
||||
repo_dir = os.path.join(temp_dir, "skills-repo")
|
||||
print(f"Clone 仓库: {TARGET_REPO_URL}")
|
||||
print(f" 到: {repo_dir}")
|
||||
|
||||
try:
|
||||
run_git_command(temp_dir, ["clone", "--depth", "1", TARGET_REPO_URL, "skills-repo"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"""
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
错误: Clone 仓库失败
|
||||
|
||||
返回码: {e.returncode}
|
||||
标准输出: {e.stdout}
|
||||
错误输出: {e.stderr}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
""")
|
||||
sys.exit(1)
|
||||
|
||||
return repo_dir
|
||||
|
||||
|
||||
def clear_target_dir(repo_dir: str) -> str:
|
||||
"""
|
||||
清空目标路径目录
|
||||
|
||||
Args:
|
||||
repo_dir: 仓库目录
|
||||
|
||||
Returns:
|
||||
目标目录路径
|
||||
"""
|
||||
target_dir = os.path.join(repo_dir, TARGET_PATH)
|
||||
|
||||
if os.path.exists(target_dir):
|
||||
print(f"清空目标目录: {target_dir}")
|
||||
shutil.rmtree(target_dir)
|
||||
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
return target_dir
|
||||
|
||||
|
||||
def copy_build_contents(build_dir: str, target_dir: str) -> None:
|
||||
"""
|
||||
复制 build/ 内容到目标目录
|
||||
|
||||
Args:
|
||||
build_dir: build 源目录
|
||||
target_dir: 目标目录
|
||||
"""
|
||||
print(f"复制 build/ 内容 -> {target_dir}")
|
||||
|
||||
for item in os.listdir(build_dir):
|
||||
src = os.path.join(build_dir, item)
|
||||
dst = os.path.join(target_dir, item)
|
||||
|
||||
if os.path.isdir(src):
|
||||
shutil.copytree(src, dst)
|
||||
print(f" 目录: {item}")
|
||||
else:
|
||||
shutil.copy2(src, dst)
|
||||
print(f" 文件: {item}")
|
||||
|
||||
|
||||
def git_commit_and_push(repo_dir: str, version: str) -> None:
|
||||
"""
|
||||
执行 git add / commit / push
|
||||
|
||||
Args:
|
||||
repo_dir: 仓库目录
|
||||
version: 版本号
|
||||
|
||||
Raises:
|
||||
SystemExit: git 操作失败时退出
|
||||
"""
|
||||
commit_message = f"publish: lyxy-document-reader {version}"
|
||||
|
||||
print(f"Git 提交: {commit_message}")
|
||||
|
||||
try:
|
||||
run_git_command(repo_dir, ["add", "."])
|
||||
run_git_command(repo_dir, ["commit", "-m", commit_message])
|
||||
print(" 推送中...")
|
||||
run_git_command(repo_dir, ["push"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"""
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
错误: Git 操作失败
|
||||
|
||||
返回码: {e.returncode}
|
||||
标准输出: {e.stdout}
|
||||
错误输出: {e.stderr}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
""")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
主函数:执行完整的发布流程
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Skill 发布")
|
||||
print("=" * 60)
|
||||
|
||||
# 路径配置
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
build_dir = os.path.join(project_root, "build")
|
||||
build_skill_md_path = os.path.join(build_dir, "SKILL.md")
|
||||
|
||||
# 检查 build/ 目录
|
||||
check_build_dir(build_dir)
|
||||
check_build_skill_md(build_skill_md_path)
|
||||
|
||||
# 解析版本号
|
||||
version = parse_version_from_skill_md(build_skill_md_path)
|
||||
print(f"版本号: {version}")
|
||||
print()
|
||||
|
||||
# 使用临时目录
|
||||
with tempfile.TemporaryDirectory(prefix="lyxy-publish-") as temp_dir:
|
||||
print(f"临时目录: {temp_dir}")
|
||||
print()
|
||||
|
||||
# Clone 仓库
|
||||
repo_dir = clone_repo(temp_dir)
|
||||
print()
|
||||
|
||||
# 清空目标路径
|
||||
target_dir = clear_target_dir(repo_dir)
|
||||
print()
|
||||
|
||||
# 复制内容
|
||||
copy_build_contents(build_dir, target_dir)
|
||||
print()
|
||||
|
||||
# Git 提交并推送
|
||||
git_commit_and_push(repo_dir, version)
|
||||
print()
|
||||
|
||||
# 完成信息
|
||||
print("=" * 60)
|
||||
print("发布完成!")
|
||||
print(f"版本号: {version}")
|
||||
print(f"目标仓库: {TARGET_REPO_URL}")
|
||||
print(f"目标路径: {TARGET_PATH}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user