313 lines
9.7 KiB
Python
313 lines
9.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
开发和测试脚本
|
|
|
|
提供功能:
|
|
- 启动开发服务器
|
|
- 文件变化监控
|
|
- 代码质量检查
|
|
- 自动化测试
|
|
|
|
使用方法:
|
|
python dev.py test # 运行测试
|
|
python dev.py lint # 代码检查
|
|
python dev.py watch # 监控文件变化
|
|
python dev.py serve # 启动开发服务器
|
|
"""
|
|
|
|
import http.server
|
|
import json
|
|
import os
|
|
import socketserver
|
|
import subprocess
|
|
import time
|
|
|
|
|
|
def start_dev_server(port=8000):
|
|
"""
|
|
启动开发服务器用于测试扩展
|
|
|
|
Args:
|
|
port (int): 服务器端口号,默认 8000
|
|
"""
|
|
|
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, directory=os.getcwd(), **kwargs)
|
|
|
|
def log_message(self, format, *args):
|
|
# 简化日志输出格式
|
|
print(f"[DEV] {format % args}")
|
|
|
|
with socketserver.TCPServer(("", port), Handler) as httpd:
|
|
print(f"开发服务器启动在 http://localhost:{port}")
|
|
print("按 Ctrl+C 停止服务器")
|
|
try:
|
|
httpd.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print("\n开发服务器已停止")
|
|
|
|
|
|
def watch_files():
|
|
"""
|
|
监控文件变化并自动重新构建
|
|
|
|
监控的文件包括:
|
|
- manifest.json / manifest-firefox.json
|
|
- JavaScript 文件 (src/background/index.js, src/content/index.js, src/popup/popup.js)
|
|
- HTML 文件 (src/popup/index.html)
|
|
- 兼容层文件 (src/shared/compat/*.js)
|
|
- 平台检测文件 (src/shared/platform-detector.js, src/shared/platforms.js)
|
|
"""
|
|
import os
|
|
|
|
watch_files = [
|
|
"manifest.json",
|
|
"manifest-firefox.json",
|
|
"src/background/index.js",
|
|
"src/content/index.js",
|
|
"src/popup/popup.js",
|
|
"src/popup/index.html",
|
|
"src/shared/download-core.js",
|
|
"src/userscript/index.js",
|
|
"src/shared/compat/webext-compat.js",
|
|
"src/shared/compat/firefox-compat.js",
|
|
"src/shared/platform-detector.js",
|
|
"src/shared/platforms.js",
|
|
]
|
|
|
|
file_times = {}
|
|
for file in watch_files:
|
|
if os.path.exists(file):
|
|
file_times[file] = os.path.getmtime(file)
|
|
|
|
print("开始监控文件变化...")
|
|
print(f"监控文件: {', '.join(watch_files)}")
|
|
|
|
while True:
|
|
try:
|
|
changed = False
|
|
for file in watch_files:
|
|
if os.path.exists(file):
|
|
current_time = os.path.getmtime(file)
|
|
if file not in file_times or current_time > file_times[file]:
|
|
print(f"检测到文件变化: {file}")
|
|
file_times[file] = current_time
|
|
changed = True
|
|
|
|
if changed:
|
|
print("重新构建...")
|
|
result = subprocess.run(
|
|
["python", "build.py", "--platform", "all"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if result.returncode == 0:
|
|
print("[OK] 构建成功")
|
|
else:
|
|
print(f"[FAIL] 构建失败: {result.stderr}")
|
|
|
|
time.sleep(1)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n停止文件监控")
|
|
break
|
|
|
|
|
|
def run_tests():
|
|
"""
|
|
运行基础测试套件
|
|
|
|
测试内容:
|
|
- 必需文件存在性检查
|
|
- Manifest 文件格式和内容验证
|
|
- 平台特定配置检查
|
|
|
|
Returns:
|
|
bool: 所有测试是否通过
|
|
"""
|
|
print("运行基础测试...")
|
|
|
|
def test_manifest(file_path, platform):
|
|
"""
|
|
测试 manifest 文件
|
|
|
|
Args:
|
|
file_path (str): manifest 文件路径
|
|
platform (str): 目标平台 (chrome/firefox)
|
|
|
|
Returns:
|
|
bool: 测试是否通过
|
|
"""
|
|
try:
|
|
with open(file_path, encoding="utf-8") as f:
|
|
manifest = json.load(f)
|
|
|
|
# 基础字段检查
|
|
required_fields = ["name", "version", "manifest_version"]
|
|
for field in required_fields:
|
|
assert field in manifest, f"缺少必需字段: {field}"
|
|
|
|
# 平台特定检查
|
|
if platform == "chrome":
|
|
assert manifest["manifest_version"] == 3, "Chrome 需要 Manifest V3"
|
|
assert "action" in manifest, "Chrome 需要 action 字段"
|
|
elif platform == "firefox":
|
|
assert manifest["manifest_version"] == 2, "Firefox 需要 Manifest V2"
|
|
assert "browser_action" in manifest, "Firefox 需要 browser_action 字段"
|
|
|
|
print(f"[OK] {platform} manifest 测试通过")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"[FAIL] {platform} manifest 测试失败: {e}")
|
|
return False
|
|
|
|
# 测试文件存在性
|
|
def test_files_exist():
|
|
required_files = [
|
|
"manifest.json",
|
|
"manifest-firefox.json",
|
|
"src/background/index.js",
|
|
"src/content/index.js",
|
|
"src/popup/popup.js",
|
|
"src/popup/index.html",
|
|
"src/shared/download-core.js",
|
|
"src/userscript/index.js",
|
|
"src/shared/compat/webext-compat.js",
|
|
"src/shared/compat/firefox-compat.js",
|
|
"src/shared/platform-detector.js",
|
|
"src/shared/platforms.js",
|
|
]
|
|
|
|
all_exist = True
|
|
for file in required_files:
|
|
if os.path.exists(file):
|
|
print(f"[OK] {file} 存在")
|
|
else:
|
|
print(f"[FAIL] {file} 不存在")
|
|
all_exist = False
|
|
|
|
return all_exist
|
|
|
|
# 运行所有测试
|
|
results = []
|
|
results.append(test_files_exist())
|
|
results.append(test_manifest("manifest.json", "chrome"))
|
|
results.append(test_manifest("manifest-firefox.json", "firefox"))
|
|
|
|
# 总结
|
|
passed = sum(results)
|
|
total = len(results)
|
|
print(f"\n测试结果: {passed}/{total} 通过")
|
|
|
|
return passed == total
|
|
|
|
|
|
def lint_code():
|
|
"""
|
|
执行代码质量检查
|
|
|
|
检查内容:
|
|
- API 使用规范性 (优先使用 webext 兼容层)
|
|
- 错误处理最佳实践
|
|
- 接口导出规范性
|
|
|
|
特殊处理:
|
|
- webext-compat.js: 允许直接使用原生 API
|
|
- firefox-compat.js: 允许直接使用原生 API
|
|
- platform-detector.js: 允许直接使用原生 API
|
|
"""
|
|
print("运行代码检查...")
|
|
|
|
js_files = [
|
|
"src/background/index.js",
|
|
"src/content/index.js",
|
|
"src/popup/popup.js",
|
|
"src/shared/download-core.js",
|
|
"src/userscript/index.js",
|
|
"src/shared/compat/webext-compat.js",
|
|
"src/shared/compat/firefox-compat.js",
|
|
"src/shared/platform-detector.js",
|
|
"src/shared/platforms.js",
|
|
]
|
|
|
|
for file in js_files:
|
|
if os.path.exists(file):
|
|
print(f"检查 {file}...")
|
|
|
|
# 读取文件内容进行检查
|
|
with open(file, encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
# 检查常见问题
|
|
issues = []
|
|
|
|
# 底层文件允许直接使用原生 API
|
|
bottom_layer_files = [
|
|
"src/shared/compat/webext-compat.js",
|
|
"src/shared/compat/firefox-compat.js",
|
|
"src/shared/platform-detector.js",
|
|
]
|
|
|
|
if file not in bottom_layer_files:
|
|
if "chrome." in content and "webext." not in content:
|
|
issues.append("发现直接使用 chrome API,应使用 webext 兼容层")
|
|
elif "webext-compat.js" in file:
|
|
# 对于 webext-compat.js,检查是否正确导出了 webext 接口
|
|
if "window.webext" not in content and "module.exports" not in content:
|
|
issues.append("兼容层未正确导出 webext 接口")
|
|
elif "firefox-compat.js" in file:
|
|
# 对于 firefox-compat.js,检查 Firefox 特定的兼容性处理
|
|
if "browser." not in content and "chrome." not in content:
|
|
issues.append("Firefox 兼容层应处理 browser/chrome API")
|
|
elif "platform-detector.js" in file:
|
|
# 对于 platform-detector.js,检查是否正确导出了平台检测接口
|
|
if "platformDetector" not in content:
|
|
issues.append("平台检测器未正确定义 platformDetector 接口")
|
|
|
|
# 检查错误处理
|
|
if "console.log(" in content and "console.error(" not in content:
|
|
# 对于底层文件和平台定义文件,console.warn 也是可以接受的
|
|
if (
|
|
file in bottom_layer_files or "platforms.js" in file
|
|
) and "console.warn(" in content:
|
|
pass # 底层文件使用 console.warn 是合理的
|
|
else:
|
|
issues.append("建议使用 console.error 处理错误")
|
|
|
|
if issues:
|
|
print(f" [WARN] {file} 发现问题:")
|
|
for issue in issues:
|
|
print(f" - {issue}")
|
|
else:
|
|
print(f" [OK] {file} 无明显问题")
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Xget Now 开发工具")
|
|
parser.add_argument(
|
|
"command", choices=["test", "lint", "watch", "serve"], help="要执行的命令"
|
|
)
|
|
parser.add_argument(
|
|
"--port", type=int, default=8000, help="开发服务器端口 (默认: 8000)"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "test":
|
|
success = run_tests()
|
|
exit(0 if success else 1)
|
|
elif args.command == "lint":
|
|
lint_code()
|
|
elif args.command == "watch":
|
|
watch_files()
|
|
elif args.command == "serve":
|
|
start_dev_server(args.port)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|