diff --git a/desktop/electron/main.ts b/desktop/electron/main.ts index 71d8c0f..3e06dae 100644 --- a/desktop/electron/main.ts +++ b/desktop/electron/main.ts @@ -43,7 +43,7 @@ function startBackend(): Promise { stdio: ["ignore", "pipe", "pipe"], }); } else { - backendProcess = spawn(cmd, args, { + backendProcess = spawn(getBackendPath(), [], { env: { ...process.env, TAGENT_API_PORT: String(BACKEND_PORT), diff --git a/install.py b/install.py new file mode 100644 index 0000000..4559e3c --- /dev/null +++ b/install.py @@ -0,0 +1,527 @@ +#!/usr/bin/env python3 +"""Test-Agent 工作流一键部署脚本(跨平台:Windows / macOS / Linux) + +前置条件: + Python 3.x — 脚本运行前提,需手动安装: + Windows: winget install Python.Python.3.13 + macOS: brew install python@3 + Linux: sudo apt install python3 (或 dnf/yum/pacman) + + Git / Node.js — 脚本自动检测并安装(winget / brew / apt) + +用法: + python install.py /path/to/your-test-project # 指定目录 + python install.py # 默认 ./Test-Agent + +安全提示:不要 pipe-to-python。先下载再审查后执行: + curl -fsSL -o install.py https://raw.githubusercontent.com/Wool-xing/Test-Agent/main/install.py + python install.py /path/to/your-test-project + +环境变量(可选): + TEST_AGENT_REPO_URL 仓库 URL + TEST_AGENT_REPO_BRANCH 分支名(默认 main) + TEST_AGENT_LOCAL_SRC CI 用:本地源码路径,跳过 git clone + TEST_AGENT_NO_CN_MIRROR 设为 1 跳过清华 PyPI 镜像 +""" + +import os +import sys +import shutil +import subprocess +import tempfile +import glob +import platform + + +PROJECT_ROOT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.getcwd(), "Test-Agent") +REPO_URL = os.environ.get("TEST_AGENT_REPO_URL", "https://github.com/Wool-xing/Test-Agent.git") +REPO_BRANCH = os.environ.get("TEST_AGENT_REPO_BRANCH", "main") + +PRESERVE_FILES = [ + ".env", + os.path.join("workspace", "测试数据", "test_data.json"), + os.path.join("workspace", "执行日志", "baselines", "perf_baseline.json"), + "workspace/regression_modules.yaml", +] + +IS_WINDOWS = platform.system() == "Windows" + + +def banner(): + print("=" * 50) + print(" Test-Agent 工作流一键部署 V1.42.0") + print(f" 仓库: {REPO_URL} ({REPO_BRANCH})") + print(f" 项目目录: {PROJECT_ROOT}") + print("=" * 50) + + +def ensure_prerequisites(): + """检测并在可能时自动安装 Git / Node.js。Python 需用户手动安装。""" + missing = [] + if shutil.which("git") is None: + missing.append("git") + if shutil.which("node") is None or shutil.which("npm") is None: + missing.append("node") + + if not missing: + print("✓ 前置工具就绪: git, node, npm") + return + + print(f"→ 检测到缺失工具: {', '.join(missing)}") + installed = _auto_install(missing) + + still = [m for m in missing if m not in installed] + if still: + print(f"❌ 以下工具安装失败,请手动安装后重试: {', '.join(still)}") + _print_manual_hint(still) + sys.exit(1) + + print("✓ 前置工具就绪") + + +def _auto_install(missing): + """尝试通过平台包管理器安装缺失工具。返回成功安装的工具列表。""" + installed = [] + if IS_WINDOWS: + installed = _install_winget(missing) + elif platform.system() == "Darwin": + installed = _install_brew(missing) + else: + installed = _install_linux_pm(missing) + return installed + + +def _install_winget(missing): + """Windows: 用 winget 安装。""" + if shutil.which("winget") is None: + print("⚠️ 未检测到 winget,请手动安装") + return [] + installed = [] + pkgs = {"git": "Git.Git", "node": "OpenJS.NodeJS.LTS"} + for tool in missing: + pkg = pkgs.get(tool) + if pkg is None: + continue + print(f"→ winget install {pkg} ...") + try: + subprocess.run( + ["winget", "install", "--silent", "--accept-source-agreements", pkg], + check=True, timeout=300, + ) + installed.append(tool) + print(f" ✓ {tool} 安装完成") + except Exception as e: + print(f" ⚠️ {tool} 安装失败: {e}") + return installed + + +def _install_brew(missing): + """macOS: 用 Homebrew 安装。""" + if shutil.which("brew") is None: + print("⚠️ 未检测到 Homebrew,请手动安装: /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"") + return [] + installed = [] + for tool in missing: + print(f"→ brew install {tool} ...") + try: + subprocess.run(["brew", "install", tool], check=True, timeout=300) + installed.append(tool) + print(f" ✓ {tool} 安装完成") + except Exception as e: + print(f" ⚠️ {tool} 安装失败: {e}") + return installed + + +def _install_linux_pm(missing): + """Linux: 检测包管理器并安装。""" + pm = None + for candidate, update_cmd, install_cmd in [ + ("apk", ["sudo", "apk", "update"], ["sudo", "apk", "add"]), + ("apt-get", ["sudo", "apt-get", "update", "-qq"], ["sudo", "apt-get", "install", "-y", "-qq"]), + ("dnf", None, ["sudo", "dnf", "install", "-y", "-q"]), + ("yum", None, ["sudo", "yum", "install", "-y", "-q"]), + ("pacman", None, ["sudo", "pacman", "-S", "--noconfirm"]), + ("zypper", None, ["sudo", "zypper", "install", "-y"]), + ]: + if shutil.which(candidate): + pm = (candidate, update_cmd, install_cmd) + break + + if pm is None: + print("⚠️ 未检测到已知包管理器,请手动安装") + return [] + + pm_name, update_cmd, install_cmd = pm + pkgs = {"git": ["git"], "node": ["nodejs", "npm"]} + + if update_cmd: + try: + subprocess.run(update_cmd, check=True, timeout=120) + except Exception: + pass + + installed = [] + for tool in missing: + pkg_list = pkgs.get(tool, [tool]) + cmd = install_cmd + pkg_list + print(f"→ {pm_name} install {tool} ...") + try: + subprocess.run(cmd, check=True, timeout=300) + installed.append(tool) + print(f" ✓ {tool} 安装完成") + except Exception as e: + print(f" ⚠️ {tool} 安装失败: {e}") + return installed + + +def _print_manual_hint(missing): + """打印手动安装提示。""" + hints = { + "git": "Git: https://git-scm.com/downloads", + "node": "Node.js: https://nodejs.org/ (LTS 版本)", + } + print("→ 手动安装指引:") + for m in missing: + if m in hints: + print(f" {hints[m]}") + print("→ 安装完成后重新运行: python install.py") + + +def find_python(): + """跨平台检测 Python 3,排除 MS Store stub。""" + candidates = ["python3", "python", "py"] + for cand in candidates: + path = shutil.which(cand) + if path is None: + continue + if IS_WINDOWS and "WindowsApps" in path: + # MS Store stub,跳过 + continue + try: + out = subprocess.run([cand, "--version"], capture_output=True, text=True).stdout + except Exception: + continue + if out.startswith("Python 3"): + return cand + print("❌ 缺少 Python 3(python3 / python / py 均不可用或为 MS Store stub)") + sys.exit(1) + + +def backup_user_data(project_root): + """幂等部署:备份用户敏感数据。""" + backed = {} + if not os.path.isdir(project_root): + return backed + tmp = tempfile.mkdtemp(prefix="test-agent-backup-") + print(f"→ 检测到已有项目,备份用户数据到 {tmp}") + for f in PRESERVE_FILES: + src = os.path.join(project_root, f) + if os.path.isfile(src): + dst = os.path.join(tmp, f) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copy2(src, dst) + backed[f] = dst + print(f" 备份: {f}") + backed["__tmp__"] = tmp + return backed + + +def restore_user_data(project_root, backed): + """恢复用户数据并清理临时目录。""" + tmp = backed.pop("__tmp__", None) + if not backed: + return + print("→ 恢复用户数据...") + for f, src in backed.items(): + dst = os.path.join(project_root, f) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copy2(src, dst) + print(f" 恢复: {f}") + if tmp and os.path.isdir(tmp): + shutil.rmtree(tmp) + + +def create_dirs(project_root): + """创建项目目录结构。""" + print("→ 创建目录...") + dirs = [ + os.path.join(".claude", "agents"), + os.path.join(".claude", "skills"), + os.path.join(".github", "workflows"), + "utils", + "src", + os.path.join("workspace", "测试计划"), + os.path.join("workspace", "需求分析"), + os.path.join("workspace", "测试用例"), + os.path.join("workspace", "测试数据"), + os.path.join("workspace", "测试报告"), + os.path.join("workspace", "测试用例", "charters"), + os.path.join("workspace", "自动化脚本", "python", "pages"), + os.path.join("workspace", "自动化脚本", "python", "api"), + os.path.join("workspace", "自动化脚本", "python", "tests"), + os.path.join("workspace", "自动化脚本", "python", "scripts"), + os.path.join("workspace", "自动化脚本", "jmeter"), + os.path.join("workspace", "执行日志", "allure-results"), + os.path.join("workspace", "执行日志", "jmeter-results"), + os.path.join("workspace", "执行日志", "jmeter-report"), + os.path.join("workspace", "执行日志", "coverage-report"), + os.path.join("workspace", "执行日志", "baselines"), + os.path.join("workspace", "执行日志", "history"), + os.path.join("workspace", "执行日志", "截图"), + ] + for d in dirs: + os.makedirs(os.path.join(project_root, d), exist_ok=True) + + +def copy_agents(template_dir, project_root): + """拷贝 Agent 定义。""" + print("→ 拷贝 Agent 定义...") + agents_dir = os.path.join(template_dir, "agents") + dest_dir = os.path.join(project_root, ".claude", "agents") + os.makedirs(dest_dir, exist_ok=True) + count = 0 + for f in glob.glob(os.path.join(agents_dir, "[0-9]*.md")): + shutil.copy2(f, dest_dir) + count += 1 + print(f" 已部署 {count} 个 Agent") + + +def copy_skills(template_dir, project_root): + """拷贝 Skill 定义。""" + print("→ 拷贝 Skill 定义...") + skills_dir = os.path.join(template_dir, "skills") + dest_dir = os.path.join(project_root, ".claude", "skills") + os.makedirs(dest_dir, exist_ok=True) + + md_count = 0 + for f in glob.glob(os.path.join(skills_dir, "*.md")): + if os.path.basename(f) == "README.md": + continue + shutil.copy2(f, dest_dir) + md_count += 1 + + dir_count = 0 + for entry in os.listdir(skills_dir): + sub = os.path.join(skills_dir, entry) + if os.path.isdir(sub): + dst = os.path.join(dest_dir, entry) + if os.path.exists(dst): + shutil.rmtree(dst) + shutil.copytree(sub, dst) + dir_count += 1 + + print(f" 已部署 {md_count} 个业务 Skill + {dir_count} 个元 Skill 子目录") + + +def copy_config(template_dir, project_root): + """拷贝配置文件。""" + print("→ 拷贝配置文件...") + config_dir = os.path.join(template_dir, "config") + files = ["conftest.py", "pytest.ini", ".mcp.json", "requirements.txt"] + for f in files: + src = os.path.join(config_dir, f) + if os.path.isfile(src): + shutil.copy2(src, project_root) + + env_dst = os.path.join(project_root, ".env") + if not os.path.isfile(env_dst): + env_src = os.path.join(config_dir, ".env.example") + if os.path.isfile(env_src): + shutil.copy2(env_src, env_dst) + + +def copy_utils(template_dir, project_root): + """拷贝 utils 目录下所有 .py 文件。""" + print("→ 拷贝 utils...") + utils_src = os.path.join(template_dir, "utils") + utils_dst = os.path.join(project_root, "utils") + count = 0 + for root, _, files in os.walk(utils_src): + for f in files: + if f.endswith(".py"): + src = os.path.join(root, f) + rel = os.path.relpath(src, utils_src) + dst = os.path.join(utils_dst, rel) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copy2(src, dst) + count += 1 + print(f" ✓ {count} 个 .py 文件已拷贝") + + +def copy_ci(template_dir, project_root): + """拷贝 CI/CD 文件。""" + print("→ 拷贝 CI/CD...") + ci_dir = os.path.join(template_dir, "ci") + shutil.copy2( + os.path.join(ci_dir, "github-actions-test.yml"), + os.path.join(project_root, ".github", "workflows", "test.yml"), + ) + shutil.copy2( + os.path.join(ci_dir, "jenkins-pipeline.groovy"), + os.path.join(project_root, "Jenkinsfile"), + ) + + +def copy_top_level_docs(template_dir, project_root): + """拷贝顶层法律 / 治理 / 路线图文档。""" + print("→ 拷贝法律 / 治理 / 路线图文档...") + docs = [ + "LICENSE", "NOTICE.md", "SECURITY.md", "CONTRIBUTING.md", + "CODE_OF_CONDUCT.md", "ROADMAP.md", "README.md", "README.zh-CN.md", + "CHANGELOG.md", "VERSION", + ] + for f in docs: + src = os.path.join(template_dir, f) + if os.path.isfile(src): + shutil.copy2(src, project_root) + + +def setup_venv(python_bin, project_root): + """创建 Python 虚拟环境并安装依赖。""" + venv_dir = os.path.join(project_root, ".venv") + if not os.path.isdir(venv_dir): + print("→ 创建虚拟环境...") + subprocess.run([python_bin, "-m", "venv", venv_dir], check=True) + + if IS_WINDOWS: + pip_cmd = os.path.join(venv_dir, "Scripts", "pip") + else: + pip_cmd = os.path.join(venv_dir, "bin", "pip") + + # pip 升级 + subprocess.run([pip_cmd, "install", "--upgrade", "pip", "-q"], check=True) + + # CN 镜像检测 + pip_index_url = os.environ.get("PIP_INDEX_URL") + if not pip_index_url and os.environ.get("TEST_AGENT_NO_CN_MIRROR", "0") != "1": + tz = os.environ.get("TZ", "") + if any([ + os.environ.get("LANG", "").startswith(("zh", "CN", "GB")), + timezone_is_cn(), + ]): + print("→ 检测到 CN 环境, 用清华 PyPI 镜像加速") + pip_index_url = "https://pypi.tuna.tsinghua.edu.cn/simple" + pip_env = os.environ.copy() + pip_env["PIP_INDEX_URL"] = pip_index_url + pip_env["PIP_TRUSTED_HOST"] = "pypi.tuna.tsinghua.edu.cn" + else: + pip_env = os.environ.copy() + else: + pip_env = os.environ.copy() + if pip_index_url: + pip_env["PIP_INDEX_URL"] = pip_index_url + + print("→ 用 pip 装 Python 依赖 (首次约 5-15 min, CN 网已自动配清华镜像加速)...") + + req_file = os.path.join(project_root, "requirements.txt") + if IS_WINDOWS: + # Windows 跳过需 C 编译器的可选 image 包 + print("→ 检测到 Windows 环境, 跳过需 C 编译器的可选 image 包 (scikit-image / scikit-learn / opencv-python)") + print(" 如需视觉测试 (visual-test skill), 装 Visual Studio Build Tools 后手动: pip install scikit-image scikit-learn opencv-python") + with open(req_file, encoding="utf-8") as f: + lines = f.readlines() + filtered = [l for l in lines if not l.startswith(("scikit-image", "scikit-learn", "opencv-python", "opencv-contrib-python"))] + fd, tmp = tempfile.mkstemp(suffix=".txt", prefix="tagent-req-") + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.writelines(filtered) + subprocess.run([pip_cmd, "install", "-r", tmp], env=pip_env, check=True) + os.unlink(tmp) + else: + subprocess.run([pip_cmd, "install", "-r", req_file], env=pip_env, check=True) + + # Playwright + try: + subprocess.run(["playwright", "install", "chromium", "--with-deps"], check=True) + except Exception: + print("⚠️ Playwright deps 安装失败,UI 测试需手动 'playwright install chromium --with-deps'") + + +def timezone_is_cn(): + """检测时区是否为中国(+0800)。""" + import time + return time.timezone == -28800 + + +def finish(project_root): + """打印完成提示。""" + msg = f""" + +{'=' * 50} + ✅ 部署完成 + + 项目目录: {project_root} + + 下一步: + 1. 编辑 {project_root}/.env(最少 8 必填字段,详见 配置清单.md) + 2. 安装 Java JRE 17 + JMeter 5.6.3 + Allure CLI(详见 部署说明.md) + 3. claude /login # 首次登录 Claude Code + 4. cd {project_root} && claude # 启动 + 5. 在 Claude 提示符内: > /smoke-test # 第一次冒烟验证 + +{'=' * 50} +""" + print(msg) + + +def main(): + banner() + + # 1. 检查 + 自动安装前置工具 + ensure_prerequisites() + python_bin = find_python() + print(f"→ 使用 Python: {python_bin}") + + # 2. 幂等备份 + backed = backup_user_data(PROJECT_ROOT) + + template_dir_parent = tempfile.mkdtemp() + template_dir = os.path.join(template_dir_parent, "Test-Agent工作流搭建") + + try: + # 3. 获取模板 + local_src = os.environ.get("TEST_AGENT_LOCAL_SRC") + if local_src: + print(f"→ [dev mode] 复制本地源代码: {local_src} → {template_dir}") + shutil.copytree(local_src, template_dir) + else: + print("→ 克隆模板...") + subprocess.run( + ["git", "clone", "--depth", "1", "--branch", REPO_BRANCH, REPO_URL, template_dir], + check=True, + ) + + # 4. 安装 Claude Code + if shutil.which("claude") is None: + print("→ 安装 Claude Code...") + subprocess.run(["npm", "install", "-g", "@anthropic-ai/claude-code"], check=True) + + # 5. 创建目录结构 + create_dirs(PROJECT_ROOT) + + # 6. 拷贝文件 + copy_agents(template_dir, PROJECT_ROOT) + copy_skills(template_dir, PROJECT_ROOT) + copy_config(template_dir, PROJECT_ROOT) + copy_utils(template_dir, PROJECT_ROOT) + copy_ci(template_dir, PROJECT_ROOT) + copy_top_level_docs(template_dir, PROJECT_ROOT) + + # 7. Python 虚拟环境 + 依赖 + setup_venv(python_bin, PROJECT_ROOT) + + # 8. 恢复用户数据 + restore_user_data(PROJECT_ROOT, backed) + + finish(PROJECT_ROOT) + + finally: + # 清理临时目录 + if os.path.isdir(template_dir_parent): + shutil.rmtree(template_dir_parent) + tmp = backed.pop("__tmp__", None) + if tmp and os.path.isdir(tmp): + shutil.rmtree(tmp) + + +if __name__ == "__main__": + main() diff --git a/install.sh b/install.sh deleted file mode 100644 index c9ea438..0000000 --- a/install.sh +++ /dev/null @@ -1,247 +0,0 @@ -#!/bin/bash -# Test-Agent 工作流一键部署脚本 -# -# 安全提示:curl | bash 存在供应链风险。生产环境建议先 clone 仓库再本地执行: -# git clone --depth 1 --branch v1.42.0 https://github.com/Wool-xing/Test-Agent.git -# cd Test-Agent && bash install.sh /path/to/your-test-project -# -# 用法(远程一行,方便快速试用): -# curl -fsSL https://raw.githubusercontent.com/Wool-xing/Test-Agent/main/install.sh | bash -s -- /path/to/your-test-project -# 用法(本地): -# bash install.sh /path/to/your-test-project -set -euo pipefail - -# ===== 参数 ===== -PROJECT_ROOT="${1:-$(pwd)/test-project}" -REPO_URL="${TEST_AGENT_REPO_URL:-https://github.com/Wool-xing/Test-Agent.git}" -REPO_BRANCH="${TEST_AGENT_REPO_BRANCH:-main}" - -echo "==========================================" -echo " Test-Agent 工作流一键部署 V1.42.0" -echo " 仓库: $REPO_URL ($REPO_BRANCH)" -echo " 项目目录: $PROJECT_ROOT" -echo "==========================================" - -# ===== Idempotency:保留用户敏感数据 ===== -PRESERVE_FILES=(".env" "workspace/测试数据/test_data.json" - "workspace/执行日志/baselines/perf_baseline.json" - "workspace/regression_modules.yaml") -BACKUP_DIR="" -if [[ -d "$PROJECT_ROOT" ]]; then - BACKUP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/test-agent-backup-XXXXXXXX")" - echo "→ 检测到已有项目,备份用户数据到 $BACKUP_DIR" - for f in "${PRESERVE_FILES[@]}"; do - if [[ -f "$PROJECT_ROOT/$f" ]]; then - mkdir -p "$BACKUP_DIR/$(dirname "$f")" - cp "$PROJECT_ROOT/$f" "$BACKUP_DIR/$f" - echo " 备份: $f" - fi - done -fi - -# 完成时恢复用户数据 -restore_user_data() { - if [[ -n "$BACKUP_DIR" ]] && [[ -d "$BACKUP_DIR" ]]; then - echo "→ 恢复用户数据..." - for f in "${PRESERVE_FILES[@]}"; do - if [[ -f "$BACKUP_DIR/$f" ]]; then - cp "$BACKUP_DIR/$f" "$PROJECT_ROOT/$f" - echo " 恢复: $f" - fi - done - rm -rf "$BACKUP_DIR" - fi -} -trap 'restore_user_data; [[ -n "${TEMPLATE_DIR:-}" ]] && rm -rf "$(dirname "$TEMPLATE_DIR")" 2>/dev/null' EXIT - -# ===== 1. 检查工具 ===== -need() { command -v "$1" >/dev/null 2>&1 || { echo "❌ 缺少 $1"; exit 1; }; } -need git -need node -need npm - -# Python 3 检测:Windows 上 python3 可能是 MS Store stub(exit 49 不输出版本),逐个测真可用 -PYTHON_BIN="" -for cand in python3 python py; do - if command -v "$cand" >/dev/null 2>&1; then - ver_out="$("$cand" --version 2>&1 || true)" - if [[ "$ver_out" == Python\ 3* ]]; then - PYTHON_BIN="$cand" - break - fi - fi -done -if [[ -z "$PYTHON_BIN" ]]; then - echo "❌ 缺少 Python 3(python3 / python / py 均不可用)" - exit 1 -fi -echo "→ 使用 Python: $PYTHON_BIN ($("$PYTHON_BIN" --version 2>&1))" - -# ===== 2. 克隆模板到临时目录 ===== -TEMPLATE_DIR="$(mktemp -d)/Test-Agent工作流搭建" -# (restore_user_data trap 已在前置 idempotency 段统一处理) - -# CI dev mode: 设 TEST_AGENT_LOCAL_SRC= 用本地源代码,跳过 git clone -# (用于 GitHub Actions macos-real-install / linux-real-install job 验当前 PR 改动, -# 而非 fetch default branch)。Production 用户不设此 env, 走 git clone 路径。 -if [[ -n "${TEST_AGENT_LOCAL_SRC:-}" ]]; then - echo "→ [dev mode] 复制本地源代码: $TEST_AGENT_LOCAL_SRC → $TEMPLATE_DIR" - cp -R "$TEST_AGENT_LOCAL_SRC" "$TEMPLATE_DIR" -else - echo "→ 克隆模板..." - git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" "$TEMPLATE_DIR" -fi - -# ===== 3. 安装 Claude Code ===== -if ! command -v claude >/dev/null 2>&1; then - echo "→ 安装 Claude Code..." - npm install -g @anthropic-ai/claude-code -fi - -# ===== 4. 创建项目目录结构 ===== -echo "→ 创建目录..." -mkdir -p "$PROJECT_ROOT"/.claude/{agents,skills} -mkdir -p "$PROJECT_ROOT"/.github/workflows -mkdir -p "$PROJECT_ROOT"/utils -mkdir -p "$PROJECT_ROOT"/src -mkdir -p "$PROJECT_ROOT"/workspace/{测试计划,需求分析,测试用例,测试数据,测试报告} -mkdir -p "$PROJECT_ROOT"/workspace/测试用例/charters -mkdir -p "$PROJECT_ROOT"/workspace/自动化脚本/python/{pages,api,tests,scripts} -mkdir -p "$PROJECT_ROOT"/workspace/自动化脚本/jmeter -mkdir -p "$PROJECT_ROOT"/workspace/执行日志/{allure-results,jmeter-results,jmeter-report,coverage-report,baselines,history,截图} - -# ===== 5. 拷贝 Agent / Skill 定义 ===== -echo "→ 拷贝 Agent 定义..." -# Glob 全部 [0-9]*.md (业务 agent),自动覆盖未来新增 -find "$TEMPLATE_DIR/agents" -maxdepth 1 -name '[0-9]*.md' -exec cp {} "$PROJECT_ROOT/.claude/agents/" \; -agent_count=$(ls "$PROJECT_ROOT/.claude/agents/"[0-9]*.md 2>/dev/null | wc -l) -echo " 已部署 $agent_count 个 Agent" - -echo "→ 拷贝 Skill 定义..." -# Glob 顶层业务 skill (排除 README) -find "$TEMPLATE_DIR/skills" -maxdepth 1 -name '*.md' ! -name 'README.md' -exec cp {} "$PROJECT_ROOT/.claude/skills/" \; -# 上游派生子目录 (darwin / karpathy-guidelines / nuwa) -# 注: 用 "${subdir%/}" 去 trailing / — macOS BSD cp 上 `cp -r darwin-skill/ dest/` -# 会展开内容到 dest/, 而非把 darwin-skill 整目录拷过去 (与 GNU cp 行为不同)。 -# Linux GNU cp 上两种语法等价, 但 macOS 必须去 / 才能保证子目录结构。 -for subdir in "$TEMPLATE_DIR/skills"/*/; do - [[ -d "$subdir" ]] && cp -r "${subdir%/}" "$PROJECT_ROOT/.claude/skills/" -done -skill_md_count=$(ls "$PROJECT_ROOT/.claude/skills/"*.md 2>/dev/null | wc -l) -skill_dir_count=$(find "$PROJECT_ROOT/.claude/skills/" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l) -echo " 已部署 $skill_md_count 个业务 Skill + $skill_dir_count 个元 Skill 子目录" - -# ===== 6. 配置文件 ===== -echo "→ 拷贝配置文件..." -cp "$TEMPLATE_DIR/config/conftest.py" "$PROJECT_ROOT/" -cp "$TEMPLATE_DIR/config/pytest.ini" "$PROJECT_ROOT/" -cp "$TEMPLATE_DIR/config/.mcp.json" "$PROJECT_ROOT/" -cp "$TEMPLATE_DIR/config/requirements.txt" "$PROJECT_ROOT/" -[[ -f "$PROJECT_ROOT/.env" ]] || cp "$TEMPLATE_DIR/config/.env.example" "$PROJECT_ROOT/.env" - -# ===== 7. utils(自动扫描全部 .py 文件)===== -echo "→ 拷贝 utils..." -_count=0 -while IFS= read -r -d '' f; do - rel="${f#$TEMPLATE_DIR/utils/}" - dest="$PROJECT_ROOT/utils/$rel" - mkdir -p "$(dirname "$dest")" - cp "$f" "$dest" - _count=$((_count + 1)) -done < <(find "$TEMPLATE_DIR/utils" -name "*.py" -print0) -echo " ✓ $_count 个 .py 文件已拷贝" - -# ===== 8. CI/CD ===== -echo "→ 拷贝 CI/CD..." -cp "$TEMPLATE_DIR/ci/github-actions-test.yml" "$PROJECT_ROOT/.github/workflows/test.yml" -cp "$TEMPLATE_DIR/ci/jenkins-pipeline.groovy" "$PROJECT_ROOT/Jenkinsfile" - -# ===== 8.5 顶层法律 / 治理 / 路线图文档 ===== -echo "→ 拷贝法律 / 治理 / 路线图文档..." -for f in LICENSE NOTICE.md SECURITY.md CONTRIBUTING.md CODE_OF_CONDUCT.md ROADMAP.md README.md README.zh-CN.md CHANGELOG.md VERSION; do - [[ -f "$TEMPLATE_DIR/$f" ]] && cp "$TEMPLATE_DIR/$f" "$PROJECT_ROOT/" -done - -# ===== 9. Python 虚拟环境 + 依赖 ===== -cd "$PROJECT_ROOT" -if [[ ! -d ".venv" ]]; then - echo "→ 创建虚拟环境..." - "$PYTHON_BIN" -m venv .venv -fi -# Windows Git Bash venv 路径是 Scripts/activate;Linux/Mac 是 bin/activate -if [[ -f ".venv/Scripts/activate" ]]; then - # shellcheck disable=SC1091 - source .venv/Scripts/activate -else - # shellcheck disable=SC1091 - source .venv/bin/activate -fi - -# Windows GBK 默认编码读 UTF-8 requirements 会 UnicodeDecodeError;强制 UTF-8 mode -export PYTHONUTF8=1 -export PYTHONIOENCODING=utf-8 - -# ===== W7+ install 加速 (PR #62) ===== -# (A) CN 网络自动用清华 PyPI 镜像: 跨境 → 国内, 跨境路径提速 -# (E) 文档化预期时长 + verbose pip 输出 (用户首次见进度而非黑屏) -# (B uv 待 upstream 修: 实测 uv + Tsinghua 组合协同有 bug, 未达预期 10x) -if [[ -z "${PIP_INDEX_URL:-}" ]]; then - is_cn=0 - # 允许显式跳过 CN 镜像: TEST_AGENT_NO_CN_MIRROR=1 ./install.sh ... - if [[ "${TEST_AGENT_NO_CN_MIRROR:-0}" == "1" ]]; then - is_cn=0 - else - case "${LANG:-}" in zh*|*CN*|*GB*) is_cn=1 ;; esac - [[ "$(date +%z 2>/dev/null)" == "+0800" ]] && is_cn=1 - fi - if [[ $is_cn -eq 1 ]]; then - echo "→ 检测到 CN 环境, 用清华 PyPI 镜像加速 (export PIP_INDEX_URL=... 可覆盖)" - export PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple - export PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn - fi -fi - -python -m pip install --upgrade pip -q - -# 注: 实测 uv + Tsinghua mirror 组合**未带来** 10x 加速 (uv 与 mirror 协同有 bug). -# 改回 pip + 镜像. uv 待 upstream / 改用 PyPI 原站时再启. -INSTALLER="pip" -echo "→ 用 pip 装 Python 依赖 (首次约 5-15 min, CN 网已自动配清华镜像加速)..." - -# W4-5 实测 Windows 修: scikit-image / scikit-learn / opencv-python 等 image 包 -# 需 C/C++ compiler (Meson build) → Windows 无 MSVC 时 fail。 -# 检测 Windows (MINGW/CYGWIN/MSYS), 装时跳过这些可选 image 包, 留 warning。 -case "$(uname -s 2>/dev/null || echo unknown)" in - MINGW*|CYGWIN*|MSYS*) - echo "→ 检测到 Windows 环境, 跳过需 C 编译器的可选 image 包 (scikit-image / scikit-learn / opencv-python)" - echo " 如需视觉测试 (visual-test skill), 装 Visual Studio Build Tools 后手动: pip install scikit-image scikit-learn opencv-python" - # 临时 requirements 排除可选 image 包 - REQ_TMP="$(mktemp -t tagent-req-XXXXXX.txt)" - grep -vE '^(scikit-image|scikit-learn|opencv-python|opencv-contrib-python)([= ]|$)' requirements.txt > "$REQ_TMP" - $INSTALLER install -r "$REQ_TMP" - rm -f "$REQ_TMP" - ;; - *) - $INSTALLER install -r requirements.txt - ;; -esac - -playwright install chromium --with-deps 2>/dev/null || echo "⚠️ Playwright deps 安装失败,UI 测试需手动 'playwright install chromium --with-deps'" - -# ===== 10. 完成提示 ===== -cat < /smoke-test # 第一次冒烟验证 - -========================================== -EOF