From a79d4146956446e56d3db707f17d6d05f98fe527 Mon Sep 17 00:00:00 2001 From: Willem Jiang Date: Fri, 13 Mar 2026 21:33:12 +0800 Subject: [PATCH] fix: make check/config cross-platform for Windows (#1080) (#1093) * fix: make check/config cross-platform for Windows (#1080) - replace shell-based check/config recipes with Python entrypoints - add a cross-platform dependency checker script - add a cross-platform config bootstrap script - route make targets through a Python variable for consistent invocation - preserve existing config-abort behavior when config file already exists * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Makefile | 12 ++-- scripts/check.py | 132 +++++++++++++++++++++++++++++++++++++++++++ scripts/configure.py | 58 +++++++++++++++++++ 3 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 scripts/check.py create mode 100644 scripts/configure.py diff --git a/Makefile b/Makefile index 99f36e7..9e390ee 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ .PHONY: help config check install dev dev-daemon start stop up down clean docker-init docker-start docker-stop docker-logs docker-logs-frontend docker-logs-gateway +PYTHON ?= python + help: @echo "DeerFlow Development Commands:" @echo " make config - Generate local config files (aborts if config already exists)" @@ -27,17 +29,11 @@ help: @echo " make docker-logs-gateway - View Docker gateway logs" config: - @if [ -f config.yaml ] || [ -f config.yml ] || [ -f configure.yml ]; then \ - echo "Error: configuration file already exists (config.yaml/config.yml/configure.yml). Aborting."; \ - exit 1; \ - fi - @cp config.example.yaml config.yaml - @test -f .env || cp .env.example .env - @test -f frontend/.env || cp frontend/.env.example frontend/.env + @$(PYTHON) ./scripts/configure.py # Check required tools check: - @./scripts/check.sh + @$(PYTHON) ./scripts/check.py # Install all dependencies install: diff --git a/scripts/check.py b/scripts/check.py new file mode 100644 index 0000000..3187dad --- /dev/null +++ b/scripts/check.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Cross-platform dependency checker for DeerFlow.""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from typing import Optional + + +def run_command(command: list[str]) -> Optional[str]: + """Run a command and return trimmed stdout, or None on failure.""" + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + except (OSError, subprocess.CalledProcessError): + return None + return result.stdout.strip() or result.stderr.strip() + + +def parse_node_major(version_text: str) -> Optional[int]: + version = version_text.strip() + if version.startswith("v"): + version = version[1:] + major_str = version.split(".", 1)[0] + if not major_str.isdigit(): + return None + return int(major_str) + + +def main() -> int: + print("==========================================") + print(" Checking Required Dependencies") + print("==========================================") + print() + + failed = False + + print("Checking Node.js...") + node_path = shutil.which("node") + if node_path: + node_version = run_command(["node", "-v"]) + if node_version: + major = parse_node_major(node_version) + if major is not None and major >= 22: + print(f" ✓ Node.js {node_version.lstrip('v')} (>= 22 required)") + else: + print( + f" ✗ Node.js {node_version.lstrip('v')} found, but version 22+ is required" + ) + print(" Install from: https://nodejs.org/") + failed = True + else: + print(" ✗ Unable to determine Node.js version") + print(" Install from: https://nodejs.org/") + failed = True + else: + print(" ✗ Node.js not found (version 22+ required)") + print(" Install from: https://nodejs.org/") + failed = True + + print() + print("Checking pnpm...") + if shutil.which("pnpm"): + pnpm_version = run_command(["pnpm", "-v"]) + if pnpm_version: + print(f" ✓ pnpm {pnpm_version}") + else: + print(" ✗ Unable to determine pnpm version") + failed = True + else: + print(" ✗ pnpm not found") + print(" Install: npm install -g pnpm") + print(" Or visit: https://pnpm.io/installation") + failed = True + + print() + print("Checking uv...") + if shutil.which("uv"): + uv_version_text = run_command(["uv", "--version"]) + if uv_version_text: + uv_version = uv_version_text.split()[-1] + print(f" ✓ uv {uv_version}") + else: + print(" ✗ Unable to determine uv version") + failed = True + else: + print(" ✗ uv not found") + print(" Visit the official installation guide for your platform:") + print(" https://docs.astral.sh/uv/getting-started/installation/") + failed = True + + print() + print("Checking nginx...") + if shutil.which("nginx"): + nginx_version_text = run_command(["nginx", "-v"]) + if nginx_version_text and "/" in nginx_version_text: + nginx_version = nginx_version_text.split("/", 1)[1] + print(f" ✓ nginx {nginx_version}") + else: + print(" ✓ nginx (version unknown)") + else: + print(" ✗ nginx not found") + print(" macOS: brew install nginx") + print(" Ubuntu: sudo apt install nginx") + print(" Windows: use WSL for local mode or use Docker mode") + print(" Or visit: https://nginx.org/en/download.html") + failed = True + + print() + if not failed: + print("==========================================") + print(" ✓ All dependencies are installed!") + print("==========================================") + print() + print("You can now run:") + print(" make install - Install project dependencies") + print(" make config - Generate local config files") + print(" make dev - Start development server") + print(" make start - Start production server") + return 0 + + print("==========================================") + print(" ✗ Some dependencies are missing") + print("==========================================") + print() + print("Please install the missing tools and run 'make check' again.") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/configure.py b/scripts/configure.py new file mode 100644 index 0000000..444c996 --- /dev/null +++ b/scripts/configure.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Cross-platform config bootstrap script for DeerFlow.""" + +from __future__ import annotations + +import shutil +import sys +from pathlib import Path + + +def copy_if_missing(src: Path, dst: Path) -> None: + if dst.exists(): + return + if not src.exists(): + raise FileNotFoundError(f"Missing template file: {src}") + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src, dst) + + +def main() -> int: + project_root = Path(__file__).resolve().parent.parent + + existing_config = [ + project_root / "config.yaml", + project_root / "config.yml", + project_root / "configure.yml", + ] + + if any(path.exists() for path in existing_config): + print( + "Error: configuration file already exists " + "(config.yaml/config.yml/configure.yml). Aborting." + ) + return 1 + + try: + copy_if_missing(project_root / "config.example.yaml", project_root / "config.yaml") + copy_if_missing(project_root / ".env.example", project_root / ".env") + copy_if_missing( + project_root / "frontend" / ".env.example", + project_root / "frontend" / ".env", + ) + except (FileNotFoundError, OSError) as exc: + print("Error while generating configuration files:") + print(f" {exc}") + if isinstance(exc, PermissionError): + print( + "Hint: Check file permissions and ensure the files are not " + "read-only or locked by another process." + ) + return 1 + + print("✓ Configuration files generated") + return 0 + + +if __name__ == "__main__": + sys.exit(main())