|
6 | 6 | """ |
7 | 7 |
|
8 | 8 | import os |
9 | | -import platform |
10 | 9 | import shutil |
11 | 10 | import subprocess |
12 | 11 | import sys |
13 | | -from typing import NoReturn, Union |
| 12 | +from typing import NoReturn |
14 | 13 |
|
15 | 14 |
|
16 | 15 | def check_node_installed() -> bool: |
@@ -40,72 +39,39 @@ def main() -> NoReturn: |
40 | 39 | """ |
41 | 40 | Main entry point for the promptfoo CLI wrapper. |
42 | 41 |
|
43 | | - Tries to use globally installed promptfoo first, falls back to npx. |
| 42 | + Executes `npx promptfoo@latest <args>` and passes through all arguments. |
44 | 43 | Exits with the same exit code as the underlying promptfoo command. |
45 | 44 | """ |
46 | 45 | # Check for Node.js installation |
47 | 46 | if not check_node_installed(): |
48 | 47 | print_installation_help() |
49 | 48 | sys.exit(1) |
50 | 49 |
|
51 | | - is_windows = platform.system() == "Windows" |
52 | | - |
53 | | - # Try to find a globally installed promptfoo first |
54 | | - promptfoo_path = shutil.which("promptfoo") |
55 | | - |
56 | | - # Build the command |
57 | | - # On Windows: use shell=True for .cmd file compatibility |
58 | | - # On Unix: use shell=False for security |
59 | | - cmd: Union[str, list[str]] |
60 | | - use_shell: bool |
61 | | - |
62 | | - if is_windows: |
63 | | - # Windows requires shell=True or cmd.exe to run .cmd files |
64 | | - # Use subprocess list form which is safer than string form |
65 | | - import shlex |
66 | | - |
67 | | - if promptfoo_path: |
68 | | - args = ["promptfoo"] + sys.argv[1:] |
69 | | - else: |
70 | | - # Fall back to npx |
71 | | - if not shutil.which("npx"): |
72 | | - print("ERROR: promptfoo is not installed and npx is not available.", file=sys.stderr) |
73 | | - print("Please install promptfoo globally: npm install -g promptfoo", file=sys.stderr) |
74 | | - sys.exit(1) |
75 | | - args = ["npx", "--yes", "promptfoo@latest"] + sys.argv[1:] |
76 | | - |
77 | | - # On Windows, use shell=True with properly quoted arguments |
78 | | - cmd = " ".join(shlex.quote(arg) for arg in args) |
79 | | - use_shell = True |
80 | | - else: |
81 | | - # Unix: use shell=False for security |
82 | | - if promptfoo_path: |
83 | | - cmd = [promptfoo_path] + sys.argv[1:] |
84 | | - else: |
85 | | - npx_path = shutil.which("npx") |
86 | | - if not npx_path: |
87 | | - print("ERROR: promptfoo is not installed and npx is not available.", file=sys.stderr) |
88 | | - print("Please install promptfoo globally: npm install -g promptfoo", file=sys.stderr) |
89 | | - sys.exit(1) |
90 | | - cmd = [npx_path, "--yes", "promptfoo@latest"] + sys.argv[1:] |
91 | | - use_shell = False |
| 50 | + # Get the full path to npx |
| 51 | + # This is crucial for Windows where npx is actually npx.cmd |
| 52 | + # Using the full path works cross-platform with shell=False |
| 53 | + npx_path = shutil.which("npx") |
| 54 | + if not npx_path: |
| 55 | + print("ERROR: npx is not available. Please ensure Node.js is properly installed.", file=sys.stderr) |
| 56 | + sys.exit(1) |
| 57 | + |
| 58 | + # Build the command: npx promptfoo@latest <args> |
| 59 | + # Use the full path to npx and keep shell=False for security and reliability |
| 60 | + cmd = [npx_path, "--yes", "promptfoo@latest"] + sys.argv[1:] |
92 | 61 |
|
93 | 62 | try: |
94 | | - # Execute the command |
| 63 | + # Execute the command and inherit stdio |
95 | 64 | result = subprocess.run( |
96 | 65 | cmd, |
97 | 66 | env=os.environ.copy(), |
98 | 67 | check=False, # Don't raise exception on non-zero exit |
99 | | - shell=use_shell, |
| 68 | + shell=False, # Keep shell=False for security - works on all platforms with full path |
100 | 69 | ) |
101 | 70 | sys.exit(result.returncode) |
102 | 71 | except KeyboardInterrupt: |
103 | 72 | # Handle Ctrl+C gracefully |
104 | 73 | print("\nInterrupted by user", file=sys.stderr) |
105 | 74 | sys.exit(130) |
106 | | - except subprocess.TimeoutExpired: |
107 | | - print("ERROR: Command timed out after waiting too long", file=sys.stderr) |
108 | | - sys.exit(1) |
109 | 75 | except Exception as e: |
110 | 76 | print(f"ERROR: Failed to execute promptfoo: {e}", file=sys.stderr) |
111 | 77 | sys.exit(1) |
|
0 commit comments