-
Notifications
You must be signed in to change notification settings - Fork 667
Expand file tree
/
Copy pathts_executor.py
More file actions
308 lines (248 loc) · 9.52 KB
/
ts_executor.py
File metadata and controls
308 lines (248 loc) · 9.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#!/usr/bin/env python3
"""
TypeScript Executor - Execute TypeScript methods from Python
Supports calling TypeScript functions with complex parameters and async/await
"""
import json
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Any, Dict, List, Optional
class TypeScriptExecutor:
"""TypeScript method executor"""
def __init__(self, working_dir: Optional[str] = None):
"""Initialize TypeScript executor
Args:
working_dir: Working directory, defaults to current directory
"""
self.working_dir = working_dir or os.getcwd()
def call_method(self, ts_file_path: str, method_name: str, *args, **kwargs) -> Any:
"""Call TypeScript method
Args:
ts_file_path: TypeScript file path
method_name: Method name
*args: Positional arguments
**kwargs: Keyword arguments
Returns:
Execution result
"""
# Convert relative path to absolute path
if not os.path.isabs(ts_file_path):
ts_file_path = os.path.join(self.working_dir, ts_file_path)
# Ensure the target file exists
if not os.path.exists(ts_file_path):
raise FileNotFoundError(f"TypeScript file not found: {ts_file_path}")
# Get the directory of the target file
target_dir = os.path.dirname(ts_file_path)
# Create wrapper script
wrapper_code = self._create_wrapper_script(
ts_file_path, method_name, list(args), kwargs
)
# Create temporary file in the same directory as the target file
temp_fd, temp_file = tempfile.mkstemp(suffix=".ts", dir=target_dir)
try:
# Write wrapper script
with os.fdopen(temp_fd, "w", encoding="utf-8") as f:
f.write(wrapper_code)
# Execute TypeScript code using ts-node
# Use subprocess.Popen to capture output in real-time
process = subprocess.Popen(
["npx", "ts-node", temp_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=self.working_dir,
bufsize=1, # Line buffering
universal_newlines=True,
)
stdout_lines = []
stderr_lines = []
# Read output line by line and display console.log in real-time
while True:
output = process.stdout.readline()
if output == "" and process.poll() is not None:
break
if output:
line = output.strip()
stdout_lines.append(line)
# Try to parse as JSON to see if it's the final result
try:
json.loads(line)
# If it parses as JSON, it might be the final result, don't print it yet
except json.JSONDecodeError:
# If it's not JSON, it's likely a console.log, so print it
print(line)
# Get any remaining stderr
stderr_output = process.stderr.read()
if stderr_output:
stderr_lines.append(stderr_output.strip())
return_code = process.poll()
if return_code != 0:
error_msg = "\n".join(stderr_lines) if stderr_lines else "Unknown error"
raise RuntimeError(f"TypeScript execution failed: {error_msg}")
# Parse results from the last line that looks like JSON
for line in reversed(stdout_lines):
if line.strip():
try:
# Try to parse as JSON
return json.loads(line)
except json.JSONDecodeError:
continue
# If no JSON found, return the last non-empty line
for line in reversed(stdout_lines):
if line.strip():
return line
return None
except Exception as e:
raise RuntimeError(f"Execution error: {str(e)}")
finally:
# Clean up temporary file
os.unlink(temp_file)
def _create_wrapper_script(
self,
ts_file_path: str,
method_name: str,
args: List[Any],
kwargs: Dict[str, Any],
) -> str:
"""Create wrapper script
Args:
ts_file_path: TypeScript file path
method_name: Method name
args: Positional arguments
kwargs: Keyword arguments
Returns:
Wrapper script code
"""
# Use relative path for import, since temp file is in the same directory
ts_filename = os.path.basename(ts_file_path)
# Remove .ts extension, since import doesn't need it
if ts_filename.endswith(".ts"):
import_path = "./" + ts_filename[:-3]
else:
import_path = "./" + ts_filename
args_json = json.dumps(args)
kwargs_json = json.dumps(kwargs)
wrapper_code = f"""
import * as targetModule from '{import_path}';
async function executeMethod() {{
try {{
// Prepare arguments
const args: any[] = {args_json};
const kwargs: any = {kwargs_json};
// Get method
const method = (targetModule as any).{method_name};
if (typeof method !== 'function') {{
throw new Error(`Method '{method_name}' does not exist or is not a function`);
}}
// Call method
let result: any;
if (Object.keys(kwargs).length > 0) {{
// If there are keyword arguments, pass them as the last parameter
result = await method(...args, kwargs);
}} else {{
// Only positional arguments
result = await method(...args);
}}
// Output result
console.log(JSON.stringify(result));
}} catch (error: any) {{
console.error(JSON.stringify({{
error: (error as Error).message,
stack: (error as Error).stack
}}));
process.exit(1);
}}
}}
executeMethod();
"""
return wrapper_code
# Convenience function
def call_ts_method(
ts_file: str, method_name: str, *args, working_dir: Optional[str] = None, **kwargs
) -> Any:
"""Convenience function: Call TypeScript method
Args:
ts_file: TypeScript file path
method_name: Method name
*args: Positional arguments
working_dir: Working directory
**kwargs: Keyword arguments
Returns:
Execution result
"""
executor = TypeScriptExecutor(working_dir)
return executor.call_method(ts_file, method_name, *args, **kwargs)
# Usage example
if __name__ == "__main__":
# Create test TypeScript file
test_ts_content = """
export function add(a: number, b: number): number {
return a + b;
}
export function greet(name: string, options?: { formal?: boolean }): string {
const greeting = options?.formal ? "Hello" : "Hi";
return `${greeting}, ${name}!`;
}
export async function processData(data: any[]): Promise<{ count: number; items: any[] }> {
// Simulate async processing
await new Promise(resolve => setTimeout(resolve, 100));
return {
count: data.length,
items: data.map(item => ({ processed: true, original: item }))
};
}
export function complexFunction(
numbers: number[],
config: { multiplier: number; offset: number }
): { result: number[]; sum: number } {
const result = numbers.map(n => n * config.multiplier + config.offset);
const sum = result.reduce((a, b) => a + b, 0);
return { result, sum };
}
"""
# Write test file
with open("test_methods.ts", "w") as f:
f.write(test_ts_content)
try:
# Create executor
executor = TypeScriptExecutor()
print("=== TypeScript Method Execution Test ===")
# Test 1: Simple function
print("\n1. Testing simple addition function:")
result = executor.call_method("test_methods.ts", "add", 10, 20)
print(f" add(10, 20) = {result}")
# Test 2: Function with optional parameters
print("\n2. Testing greeting function:")
result1 = executor.call_method("test_methods.ts", "greet", "Alice")
print(f" greet('Alice') = {result1}")
result2 = executor.call_method(
"test_methods.ts", "greet", "Bob", {"formal": True}
)
print(f" greet('Bob', {{formal: true}}) = {result2}")
# Test 3: Async function
print("\n3. Testing async function:")
result = executor.call_method(
"test_methods.ts", "processData", [1, 2, 3, "hello"]
)
print(f" processData([1, 2, 3, 'hello']) = {result}")
# Test 4: Complex function
print("\n4. Testing complex function:")
result = executor.call_method(
"test_methods.ts",
"complexFunction",
[1, 2, 3, 4, 5],
{"multiplier": 2, "offset": 1},
)
print(f" complexFunction([1,2,3,4,5], {{multiplier:2, offset:1}}) = {result}")
# Test 5: Using convenience function
print("\n5. Testing convenience function:")
result = call_ts_method("test_methods.ts", "add", 100, 200)
print(f" call_ts_method('test_methods.ts', 'add', 100, 200) = {result}")
except Exception as e:
print(f"Error: {e}")
finally:
# Clean up test file
if os.path.exists("test_methods.ts"):
os.remove("test_methods.ts")