Compare commits
4 Commits
82b09614d3
...
a578c0b7ac
| Author | SHA1 | Date | |
|---|---|---|---|
| a578c0b7ac | |||
| 78063b9e07 | |||
| edbdeec90d | |||
| a5c0b67360 |
84
build.py
84
build.py
@@ -58,27 +58,17 @@ def get_git_user_info() -> tuple[str, str]:
|
|||||||
try:
|
try:
|
||||||
name = get_git_config("user.name")
|
name = get_git_config("user.name")
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print("""
|
print("错误: git user.name 未设置")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print("请先配置 git 用户名:")
|
||||||
错误: git user.name 未设置
|
print(' git config --global user.name "Your Name"')
|
||||||
|
|
||||||
请先配置 git 用户名:
|
|
||||||
git config --global user.name "Your Name"
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
email = get_git_config("user.email")
|
email = get_git_config("user.email")
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print("""
|
print("错误: git user.email 未设置")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print("请先配置 git 邮箱:")
|
||||||
错误: git user.email 未设置
|
print(' git config --global user.email "your@email.com"')
|
||||||
|
|
||||||
请先配置 git 邮箱:
|
|
||||||
git config --global user.email "your@email.com"
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return name, email
|
return name, email
|
||||||
@@ -92,10 +82,8 @@ def clean_and_create_build_dir(build_dir: str) -> None:
|
|||||||
build_dir: 构建目录路径
|
build_dir: 构建目录路径
|
||||||
"""
|
"""
|
||||||
if os.path.exists(build_dir):
|
if os.path.exists(build_dir):
|
||||||
print(f"清理旧构建目录: {build_dir}")
|
|
||||||
shutil.rmtree(build_dir)
|
shutil.rmtree(build_dir)
|
||||||
os.makedirs(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:
|
def copy_skill_md(source_path: str, target_dir: str, version: str, author: str) -> None:
|
||||||
@@ -203,8 +191,6 @@ def copy_skill_md(source_path: str, target_dir: str, version: str, author: str)
|
|||||||
with open(target_path, "w", encoding="utf-8") as f:
|
with open(target_path, "w", encoding="utf-8") as f:
|
||||||
f.write(new_content)
|
f.write(new_content)
|
||||||
|
|
||||||
print(f"生成: {target_path} (version: {version}, author: {author})")
|
|
||||||
|
|
||||||
|
|
||||||
def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None:
|
def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -218,16 +204,9 @@ def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None:
|
|||||||
try:
|
try:
|
||||||
__import__("pyarmor")
|
__import__("pyarmor")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("""
|
print("错误: PyArmor 未安装")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print("请使用以下命令:")
|
||||||
错误: PyArmor 未安装
|
print(" uv run --with pyarmor python build.py")
|
||||||
|
|
||||||
请使用以下命令:
|
|
||||||
|
|
||||||
uv run --with pyarmor python build.py
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 临时目录
|
# 临时目录
|
||||||
@@ -246,8 +225,6 @@ def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None:
|
|||||||
source_dir
|
source_dir
|
||||||
]
|
]
|
||||||
|
|
||||||
print(f" 执行: {' '.join(cmd)}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -256,38 +233,49 @@ def obfuscate_scripts_dir(source_dir: str, target_dir: str) -> None:
|
|||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"\nPyArmor 混淆失败:")
|
print("错误: PyArmor 混淆失败")
|
||||||
print(f" 返回码: {e.returncode}")
|
print(f" 返回码: {e.returncode}")
|
||||||
print(f" 标准输出: {e.stdout}")
|
print(f" 标准输出: {e.stdout}")
|
||||||
print(f" 错误输出: {e.stderr}")
|
print(f" 错误输出: {e.stderr}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 移动混淆后的文件到最终位置
|
# 移动混淆后的文件到最终位置
|
||||||
|
scripts_dst_dir = os.path.join(target_dir, "scripts")
|
||||||
|
pyarmor_runtime_dir = None
|
||||||
|
|
||||||
|
# 先移动 scripts 目录
|
||||||
for item in os.listdir(temp_dir):
|
for item in os.listdir(temp_dir):
|
||||||
src = os.path.join(temp_dir, item)
|
src = os.path.join(temp_dir, item)
|
||||||
|
if item == "scripts":
|
||||||
dst = os.path.join(target_dir, item)
|
dst = os.path.join(target_dir, item)
|
||||||
|
|
||||||
if os.path.exists(dst):
|
if os.path.exists(dst):
|
||||||
if os.path.isdir(dst):
|
if os.path.isdir(dst):
|
||||||
shutil.rmtree(dst)
|
shutil.rmtree(dst)
|
||||||
else:
|
else:
|
||||||
os.remove(dst)
|
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)
|
shutil.move(src, dst)
|
||||||
|
|
||||||
# 清理临时目录
|
# 清理临时目录
|
||||||
os.rmdir(temp_dir)
|
os.rmdir(temp_dir)
|
||||||
|
|
||||||
print(" 混淆完成")
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""
|
"""
|
||||||
主函数:执行完整的混淆打包流程
|
主函数:执行完整的混淆打包流程
|
||||||
"""
|
"""
|
||||||
print("=" * 60)
|
|
||||||
print("Skill 打包构建 (混淆模式)")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# 路径配置
|
# 路径配置
|
||||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -297,37 +285,19 @@ def main() -> None:
|
|||||||
|
|
||||||
# 生成版本号
|
# 生成版本号
|
||||||
version = generate_timestamp()
|
version = generate_timestamp()
|
||||||
print(f"版本号: {version}")
|
|
||||||
|
|
||||||
# 读取 git 用户信息
|
# 读取 git 用户信息
|
||||||
git_name, git_email = get_git_user_info()
|
git_name, git_email = get_git_user_info()
|
||||||
author = f"{git_name} <{git_email}>"
|
author = f"{git_name} <{git_email}>"
|
||||||
print(f"作者: {author}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 清理并创建 build 目录
|
# 清理并创建 build 目录
|
||||||
clean_and_create_build_dir(build_dir)
|
clean_and_create_build_dir(build_dir)
|
||||||
print()
|
|
||||||
|
|
||||||
# 复制 SKILL.md(动态注入元数据)
|
# 复制 SKILL.md(动态注入元数据)
|
||||||
copy_skill_md(skill_md_path, build_dir, version, author)
|
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)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
15
build.sh
Normal file
15
build.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# 混淆构建脚本
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# ./build.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo ">>> 构建"
|
||||||
|
uv run --with pyarmor python build.py
|
||||||
|
echo ">>> 完成"
|
||||||
@@ -8,7 +8,7 @@ context: |
|
|||||||
- 主机环境: 禁止污染配置,需操作须请求用户
|
- 主机环境: 禁止污染配置,需操作须请求用户
|
||||||
- 开发文档: README.md,每次迭代按需更新开发文档; 禁emoji/特殊字符
|
- 开发文档: README.md,每次迭代按需更新开发文档; 禁emoji/特殊字符
|
||||||
- skill文档: SKILL.md,每次迭代按需更新skill文档
|
- skill文档: SKILL.md,每次迭代按需更新skill文档
|
||||||
- 测试: 所有需求必须设计全面测试
|
- 测试: 所有需求必须设计全面测试,严禁跳过测试,无法进行的测试交用户决策
|
||||||
- 任务: 除非用户直接要求,禁止创建git变更任务(push/commit等); git读取允许(status/log/diff等)
|
- 任务: 除非用户直接要求,禁止创建git变更任务(push/commit等); git读取允许(status/log/diff等)
|
||||||
- 代码: 模块文件150-300行; 错误需自定义异常+清晰信息+位置上下文
|
- 代码: 模块文件150-300行; 错误需自定义异常+清晰信息+位置上下文
|
||||||
- 项目阶段: 未上线,无用户,破坏性变更无需迁移说明
|
- 项目阶段: 未上线,无用户,破坏性变更无需迁移说明
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
- **THEN** 系统不需要 --obfuscate 参数,直接执行混淆构建
|
- **THEN** 系统不需要 --obfuscate 参数,直接执行混淆构建
|
||||||
|
|
||||||
### Requirement: PyArmor 混淆执行
|
### Requirement: PyArmor 混淆执行
|
||||||
系统 SHALL 调用 PyArmor 工具对 scripts 目录进行混淆。
|
系统 SHALL 调用 PyArmor 工具对 scripts 目录进行混淆,然后将 pyarmor_runtime 目录移动到 scripts 内部。
|
||||||
|
|
||||||
#### Scenario: PyArmor 成功执行
|
#### Scenario: PyArmor 成功执行
|
||||||
- **WHEN** PyArmor 可用
|
- **WHEN** PyArmor 可用
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
#### Scenario: 混淆后文件输出
|
#### Scenario: 混淆后文件输出
|
||||||
- **WHEN** PyArmor 混淆完成
|
- **WHEN** PyArmor 混淆完成
|
||||||
- **THEN** build/ 目录包含混淆后的文件和 pyarmor_runtime 子目录
|
- **THEN** build/ 目录包含混淆后的 scripts 目录,且 pyarmor_runtime 子目录位于 scripts/ 内部
|
||||||
|
|
||||||
### Requirement: PyArmor 未安装友好提示
|
### Requirement: PyArmor 未安装友好提示
|
||||||
系统 SHALL 在 PyArmor 未安装时提供清晰的错误提示,引导用户正确使用 `uv run --with pyarmor`。
|
系统 SHALL 在 PyArmor 未安装时提供清晰的错误提示,引导用户正确使用 `uv run --with pyarmor`。
|
||||||
|
|||||||
88
publish.py
88
publish.py
@@ -28,14 +28,9 @@ def check_build_dir(build_dir: str) -> None:
|
|||||||
SystemExit: 目录不存在时退出
|
SystemExit: 目录不存在时退出
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(build_dir):
|
if not os.path.exists(build_dir):
|
||||||
print("""
|
print("错误: build/ 目录不存在")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print("请先运行 build.py:")
|
||||||
错误: build/ 目录不存在
|
print(" uv run python build.py")
|
||||||
|
|
||||||
请先运行 build.py:
|
|
||||||
uv run python build.py
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -50,14 +45,9 @@ def check_build_skill_md(build_skill_md_path: str) -> None:
|
|||||||
SystemExit: 文件不存在时退出
|
SystemExit: 文件不存在时退出
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(build_skill_md_path):
|
if not os.path.exists(build_skill_md_path):
|
||||||
print("""
|
print("错误: build/SKILL.md 不存在")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print("请先运行 build.py:")
|
||||||
错误: build/SKILL.md 不存在
|
print(" uv run python build.py")
|
||||||
|
|
||||||
请先运行 build.py:
|
|
||||||
uv run python build.py
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -101,13 +91,8 @@ def parse_version_from_skill_md(skill_md_path: str) -> str:
|
|||||||
# metadata 块结束
|
# metadata 块结束
|
||||||
in_metadata = False
|
in_metadata = False
|
||||||
|
|
||||||
print("""
|
print("错误: 无法从 build/SKILL.md 解析版本号")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print("请检查 build/SKILL.md 是否包含 metadata.version 字段")
|
||||||
错误: 无法从 build/SKILL.md 解析版本号
|
|
||||||
|
|
||||||
请检查 build/SKILL.md 是否包含 metadata.version 字段
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -149,21 +134,14 @@ def clone_repo(temp_dir: str) -> str:
|
|||||||
SystemExit: clone 失败时退出
|
SystemExit: clone 失败时退出
|
||||||
"""
|
"""
|
||||||
repo_dir = os.path.join(temp_dir, "skills-repo")
|
repo_dir = os.path.join(temp_dir, "skills-repo")
|
||||||
print(f"Clone 仓库: {TARGET_REPO_URL}")
|
|
||||||
print(f" 到: {repo_dir}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run_git_command(temp_dir, ["clone", "--depth", "1", TARGET_REPO_URL, "skills-repo"])
|
run_git_command(temp_dir, ["clone", "--depth", "1", TARGET_REPO_URL, "skills-repo"])
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"""
|
print(f"错误: Clone 仓库失败")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print(f" 返回码: {e.returncode}")
|
||||||
错误: Clone 仓库失败
|
print(f" 标准输出: {e.stdout}")
|
||||||
|
print(f" 错误输出: {e.stderr}")
|
||||||
返回码: {e.returncode}
|
|
||||||
标准输出: {e.stdout}
|
|
||||||
错误输出: {e.stderr}
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return repo_dir
|
return repo_dir
|
||||||
@@ -182,7 +160,6 @@ def clear_target_dir(repo_dir: str) -> str:
|
|||||||
target_dir = os.path.join(repo_dir, TARGET_PATH)
|
target_dir = os.path.join(repo_dir, TARGET_PATH)
|
||||||
|
|
||||||
if os.path.exists(target_dir):
|
if os.path.exists(target_dir):
|
||||||
print(f"清空目标目录: {target_dir}")
|
|
||||||
shutil.rmtree(target_dir)
|
shutil.rmtree(target_dir)
|
||||||
|
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
@@ -197,18 +174,14 @@ def copy_build_contents(build_dir: str, target_dir: str) -> None:
|
|||||||
build_dir: build 源目录
|
build_dir: build 源目录
|
||||||
target_dir: 目标目录
|
target_dir: 目标目录
|
||||||
"""
|
"""
|
||||||
print(f"复制 build/ 内容 -> {target_dir}")
|
|
||||||
|
|
||||||
for item in os.listdir(build_dir):
|
for item in os.listdir(build_dir):
|
||||||
src = os.path.join(build_dir, item)
|
src = os.path.join(build_dir, item)
|
||||||
dst = os.path.join(target_dir, item)
|
dst = os.path.join(target_dir, item)
|
||||||
|
|
||||||
if os.path.isdir(src):
|
if os.path.isdir(src):
|
||||||
shutil.copytree(src, dst)
|
shutil.copytree(src, dst)
|
||||||
print(f" 目录: {item}")
|
|
||||||
else:
|
else:
|
||||||
shutil.copy2(src, dst)
|
shutil.copy2(src, dst)
|
||||||
print(f" 文件: {item}")
|
|
||||||
|
|
||||||
|
|
||||||
def git_commit_and_push(repo_dir: str, version: str) -> None:
|
def git_commit_and_push(repo_dir: str, version: str) -> None:
|
||||||
@@ -224,23 +197,15 @@ def git_commit_and_push(repo_dir: str, version: str) -> None:
|
|||||||
"""
|
"""
|
||||||
commit_message = f"publish: lyxy-document-reader {version}"
|
commit_message = f"publish: lyxy-document-reader {version}"
|
||||||
|
|
||||||
print(f"Git 提交: {commit_message}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run_git_command(repo_dir, ["add", "."])
|
run_git_command(repo_dir, ["add", "."])
|
||||||
run_git_command(repo_dir, ["commit", "-m", commit_message])
|
run_git_command(repo_dir, ["commit", "-m", commit_message])
|
||||||
print(" 推送中...")
|
|
||||||
run_git_command(repo_dir, ["push"])
|
run_git_command(repo_dir, ["push"])
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"""
|
print(f"错误: Git 操作失败")
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
print(f" 返回码: {e.returncode}")
|
||||||
错误: Git 操作失败
|
print(f" 标准输出: {e.stdout}")
|
||||||
|
print(f" 错误输出: {e.stderr}")
|
||||||
返回码: {e.returncode}
|
|
||||||
标准输出: {e.stdout}
|
|
||||||
错误输出: {e.stderr}
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
""")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -248,10 +213,6 @@ def main() -> None:
|
|||||||
"""
|
"""
|
||||||
主函数:执行完整的发布流程
|
主函数:执行完整的发布流程
|
||||||
"""
|
"""
|
||||||
print("=" * 60)
|
|
||||||
print("Skill 发布")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# 路径配置
|
# 路径配置
|
||||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||||
build_dir = os.path.join(project_root, "build")
|
build_dir = os.path.join(project_root, "build")
|
||||||
@@ -263,37 +224,20 @@ def main() -> None:
|
|||||||
|
|
||||||
# 解析版本号
|
# 解析版本号
|
||||||
version = parse_version_from_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:
|
with tempfile.TemporaryDirectory(prefix="lyxy-publish-") as temp_dir:
|
||||||
print(f"临时目录: {temp_dir}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Clone 仓库
|
# Clone 仓库
|
||||||
repo_dir = clone_repo(temp_dir)
|
repo_dir = clone_repo(temp_dir)
|
||||||
print()
|
|
||||||
|
|
||||||
# 清空目标路径
|
# 清空目标路径
|
||||||
target_dir = clear_target_dir(repo_dir)
|
target_dir = clear_target_dir(repo_dir)
|
||||||
print()
|
|
||||||
|
|
||||||
# 复制内容
|
# 复制内容
|
||||||
copy_build_contents(build_dir, target_dir)
|
copy_build_contents(build_dir, target_dir)
|
||||||
print()
|
|
||||||
|
|
||||||
# Git 提交并推送
|
# Git 提交并推送
|
||||||
git_commit_and_push(repo_dir, version)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
20
publish.sh
20
publish.sh
@@ -10,21 +10,9 @@ set -e
|
|||||||
|
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
echo "============================================"
|
echo ">>> 构建 + 发布"
|
||||||
echo "Skill 混淆构建 + 发布"
|
echo "[1/2] 构建..."
|
||||||
echo "============================================"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# 1. 混淆构建
|
|
||||||
echo "[1/2] 执行混淆构建..."
|
|
||||||
uv run --with pyarmor python build.py
|
uv run --with pyarmor python build.py
|
||||||
echo
|
echo "[2/2] 发布..."
|
||||||
|
|
||||||
# 2. 发布
|
|
||||||
echo "[2/2] 执行发布..."
|
|
||||||
uv run python publish.py
|
uv run python publish.py
|
||||||
echo
|
echo ">>> 完成"
|
||||||
|
|
||||||
echo "============================================"
|
|
||||||
echo "完成!"
|
|
||||||
echo "============================================"
|
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ def generate_uv_command(
|
|||||||
dependencies: list,
|
dependencies: list,
|
||||||
input_path: str,
|
input_path: str,
|
||||||
python_version: Optional[str] = None,
|
python_version: Optional[str] = None,
|
||||||
script_path: str = "scripts/lyxy_document_reader.py"
|
script_path: str = "scripts/lyxy_document_reader.py",
|
||||||
|
include_pyarmor: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
生成 uv run 命令。
|
生成 uv run 命令。
|
||||||
@@ -102,6 +103,7 @@ def generate_uv_command(
|
|||||||
input_path: 输入文件路径或 URL
|
input_path: 输入文件路径或 URL
|
||||||
python_version: 需要的 python 版本,None 表示不指定
|
python_version: 需要的 python 版本,None 表示不指定
|
||||||
script_path: 脚本路径
|
script_path: 脚本路径
|
||||||
|
include_pyarmor: 是否包含 pyarmor 依赖
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
uv run 命令字符串
|
uv run 命令字符串
|
||||||
@@ -111,7 +113,7 @@ def generate_uv_command(
|
|||||||
if python_version:
|
if python_version:
|
||||||
parts.append(f"--python {python_version}")
|
parts.append(f"--python {python_version}")
|
||||||
|
|
||||||
# 始终添加 pyarmor 依赖(混淆后脚本需要)
|
if include_pyarmor:
|
||||||
parts.append("--with pyarmor")
|
parts.append("--with pyarmor")
|
||||||
|
|
||||||
for dep in dependencies:
|
for dep in dependencies:
|
||||||
@@ -126,10 +128,45 @@ def generate_uv_command(
|
|||||||
return " ".join(parts)
|
return " ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_uv_args(
|
||||||
|
dependencies: list,
|
||||||
|
script_path: str,
|
||||||
|
python_version: Optional[str] = None,
|
||||||
|
include_pyarmor: bool = True
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
生成 uv run 命令参数列表(用于 subprocess.run)。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dependencies: 依赖包列表
|
||||||
|
script_path: 脚本路径
|
||||||
|
python_version: 需要的 python 版本,None 表示不指定
|
||||||
|
include_pyarmor: 是否包含 pyarmor 依赖
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
uv run 命令参数列表
|
||||||
|
"""
|
||||||
|
args = ["uv", "run"]
|
||||||
|
|
||||||
|
if python_version:
|
||||||
|
args.extend(["--python", python_version])
|
||||||
|
|
||||||
|
if include_pyarmor:
|
||||||
|
args.extend(["--with", "pyarmor"])
|
||||||
|
|
||||||
|
for dep in dependencies:
|
||||||
|
args.extend(["--with", dep])
|
||||||
|
|
||||||
|
args.append(script_path)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
def generate_python_command(
|
def generate_python_command(
|
||||||
dependencies: list,
|
dependencies: list,
|
||||||
input_path: str,
|
input_path: str,
|
||||||
script_path: str = "scripts/lyxy_document_reader.py"
|
script_path: str = "scripts/lyxy_document_reader.py",
|
||||||
|
include_pyarmor: bool = True
|
||||||
) -> Tuple[str, str]:
|
) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
生成 python 命令和 pip 安装命令。
|
生成 python 命令和 pip 安装命令。
|
||||||
@@ -138,14 +175,17 @@ def generate_python_command(
|
|||||||
dependencies: 依赖包列表
|
dependencies: 依赖包列表
|
||||||
input_path: 输入文件路径或 URL
|
input_path: 输入文件路径或 URL
|
||||||
script_path: 脚本路径
|
script_path: 脚本路径
|
||||||
|
include_pyarmor: 是否包含 pyarmor 依赖
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(python_command, pip_command) 元组
|
(python_command, pip_command) 元组
|
||||||
"""
|
"""
|
||||||
python_cmd = f"python {script_path} {input_path}"
|
python_cmd = f"python {script_path} {input_path}"
|
||||||
|
|
||||||
# 构建 pip install 命令,处理带引号的依赖,始终包含 pyarmor
|
# 构建 pip install 命令,处理带引号的依赖
|
||||||
pip_parts = ["pip install", "pyarmor"]
|
pip_parts = ["pip install"]
|
||||||
|
if include_pyarmor:
|
||||||
|
pip_parts.append("pyarmor")
|
||||||
for dep in dependencies:
|
for dep in dependencies:
|
||||||
pip_parts.append(dep)
|
pip_parts.append(dep)
|
||||||
pip_cmd = " ".join(pip_parts)
|
pip_cmd = " ".join(pip_parts)
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 确定项目根目录和脚本路径
|
||||||
|
script_file = Path(__file__).resolve()
|
||||||
|
scripts_dir = script_file.parent
|
||||||
|
project_root = scripts_dir.parent
|
||||||
|
bootstrap_path = str(scripts_dir / "bootstrap.py")
|
||||||
|
|
||||||
# 将 scripts/ 目录添加到 sys.path
|
# 将 scripts/ 目录添加到 sys.path
|
||||||
scripts_dir = Path(__file__).resolve().parent
|
|
||||||
if str(scripts_dir) not in sys.path:
|
if str(scripts_dir) not in sys.path:
|
||||||
sys.path.append(str(scripts_dir))
|
sys.path.append(str(scripts_dir))
|
||||||
|
|
||||||
@@ -75,6 +80,7 @@ def main():
|
|||||||
detect_file_type_light,
|
detect_file_type_light,
|
||||||
get_platform,
|
get_platform,
|
||||||
get_dependencies,
|
get_dependencies,
|
||||||
|
generate_uv_args,
|
||||||
)
|
)
|
||||||
from readers import READERS
|
from readers import READERS
|
||||||
|
|
||||||
@@ -93,29 +99,22 @@ def main():
|
|||||||
python_version, dependencies = get_dependencies(reader_cls, platform_id)
|
python_version, dependencies = get_dependencies(reader_cls, platform_id)
|
||||||
|
|
||||||
# 生成 uv 命令参数列表
|
# 生成 uv 命令参数列表
|
||||||
uv_args = ["uv", "run"]
|
uv_args = generate_uv_args(
|
||||||
|
dependencies=dependencies,
|
||||||
if python_version:
|
script_path=bootstrap_path,
|
||||||
uv_args.extend(["--python", python_version])
|
python_version=python_version,
|
||||||
|
include_pyarmor=True
|
||||||
# 始终添加 pyarmor 依赖(混淆后脚本需要)
|
)
|
||||||
uv_args.extend(["--with", "pyarmor"])
|
|
||||||
|
|
||||||
for dep in dependencies:
|
|
||||||
uv_args.extend(["--with", dep])
|
|
||||||
|
|
||||||
# 目标脚本是 bootstrap.py
|
|
||||||
uv_args.append("scripts/bootstrap.py")
|
|
||||||
|
|
||||||
# 添加所有命令行参数
|
# 添加所有命令行参数
|
||||||
uv_args.extend(sys.argv[1:])
|
uv_args.extend(sys.argv[1:])
|
||||||
|
|
||||||
# 设置环境变量
|
# 设置环境变量
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["PYTHONPATH"] = "."
|
env["PYTHONPATH"] = str(project_root)
|
||||||
|
|
||||||
# 自启动:使用 subprocess 替代 execvpe(Windows 兼容)
|
# 自启动:使用 subprocess 替代 execvpe(Windows 兼容)
|
||||||
result = subprocess.run(uv_args, env=env)
|
result = subprocess.run(uv_args, env=env, cwd=str(project_root))
|
||||||
sys.exit(result.returncode)
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
53
tests/test_cli/test_path_resolution.py
Normal file
53
tests/test_cli/test_path_resolution.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""测试路径解析功能 - 验证从任意路径调用脚本。"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class TestPathResolution:
|
||||||
|
"""测试路径解析逻辑。"""
|
||||||
|
|
||||||
|
def test_project_root_detection(self):
|
||||||
|
"""测试项目根目录检测逻辑。"""
|
||||||
|
# 模拟 lyxy_document_reader.py 中的路径计算逻辑
|
||||||
|
# 获取当前测试文件的路径,然后向上找到项目根
|
||||||
|
test_file = Path(__file__).resolve()
|
||||||
|
tests_dir = test_file.parent.parent # tests/
|
||||||
|
project_root = tests_dir.parent # 项目根
|
||||||
|
|
||||||
|
# 验证我们能正确找到项目根
|
||||||
|
assert (project_root / "scripts").exists()
|
||||||
|
assert (project_root / "scripts" / "lyxy_document_reader.py").exists()
|
||||||
|
assert (project_root / "scripts" / "bootstrap.py").exists()
|
||||||
|
|
||||||
|
def test_bootstrap_path_absolute(self):
|
||||||
|
"""测试 bootstrap.py 路径是绝对路径。"""
|
||||||
|
# 模拟 lyxy_document_reader.py 中的路径计算
|
||||||
|
test_file = Path(__file__).resolve()
|
||||||
|
project_root = test_file.parent.parent.parent # 从 tests/test_cli/ 向上两级
|
||||||
|
scripts_dir = project_root / "scripts"
|
||||||
|
bootstrap_path = scripts_dir / "bootstrap.py"
|
||||||
|
|
||||||
|
# 验证路径是绝对路径
|
||||||
|
assert bootstrap_path.is_absolute()
|
||||||
|
assert bootstrap_path.exists()
|
||||||
|
|
||||||
|
def test_path_independent_from_cwd(self, monkeypatch, tmp_path):
|
||||||
|
"""测试路径计算不依赖当前工作目录。"""
|
||||||
|
# 保存原始路径
|
||||||
|
test_file = Path(__file__).resolve()
|
||||||
|
project_root = test_file.parent.parent.parent
|
||||||
|
scripts_dir = project_root / "scripts"
|
||||||
|
|
||||||
|
# 切换到临时目录
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
|
||||||
|
# 即使在临时目录,我们仍然能通过 __file__ 找到正确的路径
|
||||||
|
# 这里我们模拟 lyxy_document_reader.py 中的逻辑
|
||||||
|
# 注意:实际中 __file__ 是脚本本身的路径,不是测试文件的路径
|
||||||
|
# 这里我们验证原理:__file__ 给出的是脚本位置,与 cwd 无关
|
||||||
|
|
||||||
|
# 验证 scripts_dir 和 bootstrap_path 的计算只依赖 __file__
|
||||||
|
# 这个测试验证的是概念,不是实际的脚本导入
|
||||||
|
assert scripts_dir.is_absolute()
|
||||||
|
assert (scripts_dir / "bootstrap.py").exists()
|
||||||
@@ -4,32 +4,6 @@ import pytest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def temp_html(tmp_path):
|
|
||||||
"""创建临时 HTML 文件的 fixture 工厂。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
content: HTML 内容字符串
|
|
||||||
encoding: 文件编码,默认 'utf-8'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 临时文件路径
|
|
||||||
"""
|
|
||||||
def _create_html(content="<html><body><p>Test</p></body></html>", encoding='utf-8'):
|
|
||||||
file_path = tmp_path / "test.html"
|
|
||||||
|
|
||||||
# 如果内容不包含完整的 HTML 结构,添加基本结构
|
|
||||||
if not content.strip().startswith('<html'):
|
|
||||||
content = f"<html><head><meta charset='{encoding}'></head><body>{content}</body></html>"
|
|
||||||
|
|
||||||
with open(file_path, 'w', encoding=encoding) as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
return str(file_path)
|
|
||||||
|
|
||||||
return _create_html
|
|
||||||
|
|
||||||
|
|
||||||
# 静态测试文件目录
|
# 静态测试文件目录
|
||||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user