tywrap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +263 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/index.d.ts +41 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +234 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/core/analyzer.d.ts +63 -0
  12. package/dist/core/analyzer.d.ts.map +1 -0
  13. package/dist/core/analyzer.js +676 -0
  14. package/dist/core/analyzer.js.map +1 -0
  15. package/dist/core/discovery.d.ts +85 -0
  16. package/dist/core/discovery.d.ts.map +1 -0
  17. package/dist/core/discovery.js +344 -0
  18. package/dist/core/discovery.js.map +1 -0
  19. package/dist/core/generator.d.ts +37 -0
  20. package/dist/core/generator.d.ts.map +1 -0
  21. package/dist/core/generator.js +368 -0
  22. package/dist/core/generator.js.map +1 -0
  23. package/dist/core/mapper.d.ts +40 -0
  24. package/dist/core/mapper.d.ts.map +1 -0
  25. package/dist/core/mapper.js +401 -0
  26. package/dist/core/mapper.js.map +1 -0
  27. package/dist/core/validation.d.ts +79 -0
  28. package/dist/core/validation.d.ts.map +1 -0
  29. package/dist/core/validation.js +486 -0
  30. package/dist/core/validation.js.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/base.d.ts +12 -0
  36. package/dist/runtime/base.d.ts.map +1 -0
  37. package/dist/runtime/base.js +6 -0
  38. package/dist/runtime/base.js.map +1 -0
  39. package/dist/runtime/errors.d.ts +17 -0
  40. package/dist/runtime/errors.d.ts.map +1 -0
  41. package/dist/runtime/errors.js +21 -0
  42. package/dist/runtime/errors.js.map +1 -0
  43. package/dist/runtime/http.d.ts +22 -0
  44. package/dist/runtime/http.d.ts.map +1 -0
  45. package/dist/runtime/http.js +79 -0
  46. package/dist/runtime/http.js.map +1 -0
  47. package/dist/runtime/index.d.ts +8 -0
  48. package/dist/runtime/index.d.ts.map +1 -0
  49. package/dist/runtime/index.js +17 -0
  50. package/dist/runtime/index.js.map +1 -0
  51. package/dist/runtime/node.d.ts +48 -0
  52. package/dist/runtime/node.d.ts.map +1 -0
  53. package/dist/runtime/node.js +328 -0
  54. package/dist/runtime/node.js.map +1 -0
  55. package/dist/runtime/optimized-node.d.ts +131 -0
  56. package/dist/runtime/optimized-node.d.ts.map +1 -0
  57. package/dist/runtime/optimized-node.js +622 -0
  58. package/dist/runtime/optimized-node.js.map +1 -0
  59. package/dist/runtime/protocol.d.ts +3 -0
  60. package/dist/runtime/protocol.d.ts.map +1 -0
  61. package/dist/runtime/protocol.js +3 -0
  62. package/dist/runtime/protocol.js.map +1 -0
  63. package/dist/runtime/pyodide.d.ts +27 -0
  64. package/dist/runtime/pyodide.d.ts.map +1 -0
  65. package/dist/runtime/pyodide.js +191 -0
  66. package/dist/runtime/pyodide.js.map +1 -0
  67. package/dist/tools/python_suite.js +173 -0
  68. package/dist/tools/python_suite.js.map +1 -0
  69. package/dist/types/index.d.ts +299 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +5 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/tywrap.d.ts +29 -0
  74. package/dist/tywrap.d.ts.map +1 -0
  75. package/dist/tywrap.js +589 -0
  76. package/dist/tywrap.js.map +1 -0
  77. package/dist/utils/bundle-optimizer.d.ts +182 -0
  78. package/dist/utils/bundle-optimizer.d.ts.map +1 -0
  79. package/dist/utils/bundle-optimizer.js +680 -0
  80. package/dist/utils/bundle-optimizer.js.map +1 -0
  81. package/dist/utils/cache.d.ts +149 -0
  82. package/dist/utils/cache.d.ts.map +1 -0
  83. package/dist/utils/cache.js +492 -0
  84. package/dist/utils/cache.js.map +1 -0
  85. package/dist/utils/codec.d.ts +94 -0
  86. package/dist/utils/codec.d.ts.map +1 -0
  87. package/dist/utils/codec.js +150 -0
  88. package/dist/utils/codec.js.map +1 -0
  89. package/dist/utils/logger.d.ts +42 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +140 -0
  92. package/dist/utils/logger.js.map +1 -0
  93. package/dist/utils/memory-profiler.d.ts +123 -0
  94. package/dist/utils/memory-profiler.d.ts.map +1 -0
  95. package/dist/utils/memory-profiler.js +426 -0
  96. package/dist/utils/memory-profiler.js.map +1 -0
  97. package/dist/utils/parallel-processor.d.ts +146 -0
  98. package/dist/utils/parallel-processor.d.ts.map +1 -0
  99. package/dist/utils/parallel-processor.js +636 -0
  100. package/dist/utils/parallel-processor.js.map +1 -0
  101. package/dist/utils/python.d.ts +8 -0
  102. package/dist/utils/python.d.ts.map +1 -0
  103. package/dist/utils/python.js +56 -0
  104. package/dist/utils/python.js.map +1 -0
  105. package/dist/utils/runtime.d.ts +153 -0
  106. package/dist/utils/runtime.d.ts.map +1 -0
  107. package/dist/utils/runtime.js +519 -0
  108. package/dist/utils/runtime.js.map +1 -0
  109. package/package.json +136 -0
  110. package/runtime/python_bridge.py +476 -0
  111. package/src/cli.ts +331 -0
  112. package/src/config/index.ts +289 -0
  113. package/src/core/analyzer.ts +788 -0
  114. package/src/core/discovery.ts +415 -0
  115. package/src/core/generator.ts +447 -0
  116. package/src/core/mapper.ts +504 -0
  117. package/src/core/validation.ts +693 -0
  118. package/src/index.ts +83 -0
  119. package/src/runtime/base.ts +32 -0
  120. package/src/runtime/errors.ts +19 -0
  121. package/src/runtime/http.ts +132 -0
  122. package/src/runtime/index.ts +24 -0
  123. package/src/runtime/node.ts +431 -0
  124. package/src/runtime/optimized-node.ts +838 -0
  125. package/src/runtime/protocol.ts +2 -0
  126. package/src/runtime/pyodide.ts +228 -0
  127. package/src/types/global.d.ts +60 -0
  128. package/src/types/index.ts +410 -0
  129. package/src/tywrap.ts +669 -0
  130. package/src/utils/bundle-optimizer.ts +876 -0
  131. package/src/utils/cache.ts +634 -0
  132. package/src/utils/codec.ts +275 -0
  133. package/src/utils/logger.ts +201 -0
  134. package/src/utils/memory-profiler.ts +582 -0
  135. package/src/utils/parallel-processor.ts +879 -0
  136. package/src/utils/python.ts +79 -0
  137. package/src/utils/runtime.ts +616 -0
@@ -0,0 +1,79 @@
1
+ import {
2
+ detectRuntime,
3
+ pathUtils,
4
+ isWindows,
5
+ isAbsolutePath,
6
+ getPythonExecutableName,
7
+ getVenvBinDir,
8
+ getVenvPythonExe,
9
+ } from './runtime.js';
10
+
11
+ export interface PythonResolveOptions {
12
+ pythonPath?: string;
13
+ virtualEnv?: string;
14
+ cwd?: string;
15
+ }
16
+
17
+ type PathModule = typeof import('node:path');
18
+
19
+ let nodePathModule: PathModule | null | undefined;
20
+
21
+ async function loadNodePathModule(): Promise<PathModule | null> {
22
+ if (nodePathModule !== undefined) {
23
+ return nodePathModule;
24
+ }
25
+ const runtime = detectRuntime();
26
+ if (runtime.name === 'node' || runtime.name === 'bun') {
27
+ try {
28
+ nodePathModule = await import('node:path');
29
+ return nodePathModule;
30
+ } catch {
31
+ try {
32
+ nodePathModule = await import('path');
33
+ return nodePathModule;
34
+ } catch {
35
+ nodePathModule = null;
36
+ return nodePathModule;
37
+ }
38
+ }
39
+ }
40
+ nodePathModule = null;
41
+ return nodePathModule;
42
+ }
43
+
44
+ export function getDefaultPythonPath(): string {
45
+ return getPythonExecutableName();
46
+ }
47
+
48
+ export async function resolvePythonExecutable(options: PythonResolveOptions = {}): Promise<string> {
49
+ const pythonPath = options.pythonPath?.trim();
50
+ const virtualEnv = options.virtualEnv?.trim();
51
+
52
+ if (virtualEnv) {
53
+ const usesDefaultPython = !pythonPath || pythonPath === 'python3' || pythonPath === 'python';
54
+ if (usesDefaultPython) {
55
+ const cwd =
56
+ options.cwd ??
57
+ (typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : '.');
58
+ const binDir = getVenvBinDir();
59
+ const exe = getVenvPythonExe();
60
+ const pathMod = await loadNodePathModule();
61
+
62
+ if (pathMod) {
63
+ const venvRoot = pathMod.resolve(cwd, virtualEnv);
64
+ return pathMod.join(venvRoot, binDir, exe);
65
+ }
66
+
67
+ const venvRoot = isAbsolutePath(virtualEnv)
68
+ ? pathUtils.join(virtualEnv)
69
+ : pathUtils.join(cwd, virtualEnv);
70
+ return pathUtils.join(venvRoot, binDir, exe);
71
+ }
72
+ }
73
+
74
+ if (pythonPath) {
75
+ return pythonPath;
76
+ }
77
+
78
+ return getDefaultPythonPath();
79
+ }
@@ -0,0 +1,616 @@
1
+ /**
2
+ * Runtime detection utilities for Node.js, Deno, Bun, and Browser environments
3
+ */
4
+
5
+ /// <reference path="../types/global.d.ts" />
6
+
7
+ export type Runtime = 'node' | 'deno' | 'bun' | 'browser' | 'unknown';
8
+
9
+ interface RuntimeInfo {
10
+ name: Runtime;
11
+ version?: string;
12
+ capabilities: RuntimeCapabilities;
13
+ }
14
+
15
+ interface RuntimeCapabilities {
16
+ filesystem: boolean;
17
+ subprocess: boolean;
18
+ webassembly: boolean;
19
+ webworkers: boolean;
20
+ sharedArrayBuffer: boolean;
21
+ fetch: boolean;
22
+ }
23
+
24
+ // Cache for runtime detection to avoid repeated environment checks
25
+ let runtimeCache: RuntimeInfo | null = null;
26
+
27
+ /**
28
+ * Clear runtime cache (for testing purposes only)
29
+ * @internal
30
+ */
31
+ export function clearRuntimeCache(): void {
32
+ runtimeCache = null;
33
+ }
34
+
35
+ /**
36
+ * Detect the current JavaScript runtime environment
37
+ * Results are cached and frozen to prevent external mutation
38
+ */
39
+ export function detectRuntime(): RuntimeInfo {
40
+ if (runtimeCache) {
41
+ return runtimeCache;
42
+ }
43
+ // Deno detection (must come before Node.js check)
44
+ if (typeof Deno !== 'undefined' && Deno !== null) {
45
+ const capabilities = {
46
+ filesystem: true,
47
+ subprocess: true,
48
+ webassembly: true,
49
+ webworkers: true,
50
+ sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
51
+ fetch: true,
52
+ };
53
+ const result: RuntimeInfo = {
54
+ name: 'deno',
55
+ version: Deno.version?.deno,
56
+ capabilities: Object.freeze(capabilities),
57
+ };
58
+ runtimeCache = Object.freeze(result) as RuntimeInfo;
59
+ return runtimeCache;
60
+ }
61
+
62
+ // Bun detection
63
+ if (typeof Bun !== 'undefined' && Bun !== null) {
64
+ const capabilities = {
65
+ filesystem: true,
66
+ subprocess: true,
67
+ webassembly: true,
68
+ webworkers: true,
69
+ sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
70
+ fetch: true,
71
+ };
72
+ const result: RuntimeInfo = {
73
+ name: 'bun',
74
+ version: Bun.version,
75
+ capabilities: Object.freeze(capabilities),
76
+ };
77
+ runtimeCache = Object.freeze(result) as RuntimeInfo;
78
+ return runtimeCache;
79
+ }
80
+
81
+ // Node.js detection
82
+ if (typeof process !== 'undefined' && process.versions?.node) {
83
+ const capabilities = {
84
+ filesystem: true,
85
+ subprocess: true,
86
+ webassembly: typeof WebAssembly !== 'undefined',
87
+ webworkers: false, // Node.js worker_threads are different
88
+ sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
89
+ fetch: typeof fetch !== 'undefined', // Available in Node.js 18+
90
+ };
91
+ const result: RuntimeInfo = {
92
+ name: 'node',
93
+ version: process.versions.node,
94
+ capabilities: Object.freeze(capabilities),
95
+ };
96
+ runtimeCache = Object.freeze(result) as RuntimeInfo;
97
+ return runtimeCache;
98
+ }
99
+
100
+ // Browser detection
101
+ if (typeof window !== 'undefined' || typeof self !== 'undefined') {
102
+ const isSecureContext =
103
+ typeof window !== 'undefined'
104
+ ? window.isSecureContext
105
+ : typeof self !== 'undefined'
106
+ ? self.isSecureContext
107
+ : false;
108
+
109
+ const capabilities = {
110
+ filesystem: false,
111
+ subprocess: false,
112
+ webassembly: typeof WebAssembly !== 'undefined',
113
+ webworkers: typeof Worker !== 'undefined',
114
+ sharedArrayBuffer: isSecureContext && typeof SharedArrayBuffer !== 'undefined',
115
+ fetch: typeof fetch !== 'undefined',
116
+ };
117
+ const result: RuntimeInfo = {
118
+ name: 'browser',
119
+ capabilities: Object.freeze(capabilities),
120
+ };
121
+ runtimeCache = Object.freeze(result) as RuntimeInfo;
122
+ return runtimeCache;
123
+ }
124
+
125
+ const capabilities = {
126
+ filesystem: false,
127
+ subprocess: false,
128
+ webassembly: false,
129
+ webworkers: false,
130
+ sharedArrayBuffer: false,
131
+ fetch: false,
132
+ };
133
+ const result: RuntimeInfo = {
134
+ name: 'unknown',
135
+ capabilities: Object.freeze(capabilities),
136
+ };
137
+
138
+ // Cache and freeze the result to prevent external mutation
139
+ runtimeCache = Object.freeze(result) as RuntimeInfo;
140
+ return runtimeCache;
141
+ }
142
+
143
+ /**
144
+ * Check if running in Node.js
145
+ */
146
+ export function isNodejs(): boolean {
147
+ return detectRuntime().name === 'node';
148
+ }
149
+
150
+ /**
151
+ * Check if running in Deno
152
+ */
153
+ export function isDeno(): boolean {
154
+ return detectRuntime().name === 'deno';
155
+ }
156
+
157
+ /**
158
+ * Check if running in Bun
159
+ */
160
+ export function isBun(): boolean {
161
+ return detectRuntime().name === 'bun';
162
+ }
163
+
164
+ /**
165
+ * Check if running in browser
166
+ */
167
+ export function isBrowser(): boolean {
168
+ return detectRuntime().name === 'browser';
169
+ }
170
+
171
+ /**
172
+ * Get runtime capabilities
173
+ */
174
+ export function getRuntimeCapabilities(): RuntimeCapabilities {
175
+ return detectRuntime().capabilities;
176
+ }
177
+
178
+ /**
179
+ * Check if a specific capability is supported
180
+ */
181
+ export function hasCapability(capability: keyof RuntimeCapabilities): boolean {
182
+ // eslint-disable-next-line security/detect-object-injection
183
+ return getRuntimeCapabilities()[capability];
184
+ }
185
+
186
+ /**
187
+ * Get the best runtime strategy for Python execution
188
+ */
189
+ export function getBestPythonRuntime(): 'node' | 'pyodide' | 'http' {
190
+ const runtime = detectRuntime();
191
+
192
+ if (runtime.name === 'browser') {
193
+ return 'pyodide';
194
+ }
195
+
196
+ if (runtime.capabilities.subprocess) {
197
+ return 'node'; // Works for Node.js, Deno, and Bun
198
+ }
199
+
200
+ // Fallback to HTTP bridge
201
+ return 'http';
202
+ }
203
+
204
+ type PathModule = typeof import('node:path');
205
+
206
+ // Cache for lazy-loaded path module
207
+ let pathModule: PathModule | null = null;
208
+
209
+ /**
210
+ * Lazy load Node.js path module on demand
211
+ */
212
+ async function loadPathModule(): Promise<PathModule | null> {
213
+ if (pathModule) {
214
+ return pathModule;
215
+ }
216
+
217
+ const runtime = detectRuntime();
218
+ if (runtime.name === 'node') {
219
+ try {
220
+ pathModule = await import('node:path');
221
+ return pathModule;
222
+ } catch {
223
+ // Fallback for older Node.js versions
224
+ pathModule = await import('path');
225
+ return pathModule;
226
+ }
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Normalize path by stripping '.' and resolving '..' components
234
+ */
235
+ function normalizePath(path: string): string {
236
+ const isAbsolute = path.startsWith('/');
237
+ const segments = path.split('/');
238
+ const normalized: string[] = [];
239
+
240
+ for (const segment of segments) {
241
+ if (segment === '.' || segment === '') {
242
+ continue; // Skip current directory and empty segments
243
+ } else if (segment === '..') {
244
+ if (normalized.length > 0 && normalized[normalized.length - 1] !== '..') {
245
+ normalized.pop(); // Go up one directory
246
+ } else if (!isAbsolute) {
247
+ normalized.push(segment); // Keep '..' if not absolute and at root
248
+ }
249
+ } else {
250
+ normalized.push(segment);
251
+ }
252
+ }
253
+
254
+ const result = normalized.join('/');
255
+ return isAbsolute ? `/${result}` : result;
256
+ }
257
+
258
+ /**
259
+ * Runtime-specific path utilities
260
+ */
261
+ export const pathUtils = {
262
+ /**
263
+ * Join paths in a cross-runtime way
264
+ */
265
+ join(...segments: string[]): string {
266
+ const joined = segments
267
+ .filter(Boolean)
268
+ .join('/')
269
+ .replace(/\/+/g, '/') // Replace multiple slashes with single slash
270
+ .replace(/\\/g, '/'); // Normalize backslashes to forward slashes
271
+
272
+ return normalizePath(joined);
273
+ },
274
+
275
+ /**
276
+ * Join paths asynchronously with enhanced Node.js support
277
+ */
278
+ async joinAsync(...segments: string[]): Promise<string> {
279
+ const runtime = detectRuntime();
280
+
281
+ // For Node.js, use the real path module when available
282
+ if (runtime.name === 'node') {
283
+ const pathMod = await loadPathModule();
284
+ if (pathMod?.posix) {
285
+ return pathMod.posix.join(...segments);
286
+ }
287
+ }
288
+
289
+ // Fallback implementation with normalization
290
+ const joined = segments
291
+ .filter(Boolean)
292
+ .join('/')
293
+ .replace(/\/+/g, '/') // Replace multiple slashes with single slash
294
+ .replace(/\\/g, '/'); // Normalize backslashes to forward slashes
295
+
296
+ return normalizePath(joined);
297
+ },
298
+
299
+ /**
300
+ * Resolve absolute path in a cross-runtime way (synchronous)
301
+ */
302
+ resolve(path: string): string {
303
+ const runtime = detectRuntime();
304
+
305
+ // Simple synchronous resolution for testing
306
+ if (path.startsWith('/')) {
307
+ return path; // Already absolute
308
+ }
309
+
310
+ if (runtime.name === 'node' && typeof process !== 'undefined' && process.cwd) {
311
+ return normalizePath(`${process.cwd()}/${path}`);
312
+ }
313
+
314
+ if (runtime.name === 'browser' && typeof location !== 'undefined') {
315
+ return new URL(path, location.href).href;
316
+ }
317
+
318
+ // Fallback: normalize and return as-is for relative paths
319
+ return normalizePath(path);
320
+ },
321
+
322
+ /**
323
+ * Resolve absolute path in a cross-runtime way (asynchronous)
324
+ */
325
+ async resolveAsync(path: string): Promise<string> {
326
+ const runtime = detectRuntime();
327
+
328
+ if (runtime.name === 'node') {
329
+ const pathMod = await loadPathModule();
330
+ if (pathMod) {
331
+ return pathMod.resolve(path);
332
+ }
333
+ }
334
+
335
+ if (runtime.name === 'browser') {
336
+ return new URL(path, location.href).href;
337
+ }
338
+
339
+ // Fallback: normalize and return as-is
340
+ return normalizePath(path);
341
+ },
342
+ };
343
+
344
+ /**
345
+ * Cross-runtime file system operations
346
+ */
347
+ export const fsUtils = {
348
+ /**
349
+ * Check if file system operations are available
350
+ */
351
+ isAvailable(): boolean {
352
+ return hasCapability('filesystem');
353
+ },
354
+
355
+ /**
356
+ * Read file in a cross-runtime way
357
+ */
358
+ async readFile(path: string): Promise<string> {
359
+ const runtime = detectRuntime();
360
+
361
+ if (!runtime.capabilities.filesystem) {
362
+ throw new Error('File system operations not available in this runtime');
363
+ }
364
+
365
+ if (runtime.name === 'deno' && Deno) {
366
+ return await Deno.readTextFile(path);
367
+ }
368
+
369
+ if (runtime.name === 'bun' && Bun) {
370
+ const file = Bun.file(path);
371
+ return await file.text();
372
+ }
373
+
374
+ if (runtime.name === 'node') {
375
+ // Dynamic import to maintain ESM compatibility
376
+ const { readFile } = await import('fs/promises');
377
+ return await readFile(path, 'utf-8');
378
+ }
379
+
380
+ throw new Error(`File system operations not implemented for ${runtime.name}`);
381
+ },
382
+
383
+ /**
384
+ * Write file in a cross-runtime way
385
+ */
386
+ async writeFile(path: string, content: string): Promise<void> {
387
+ const runtime = detectRuntime();
388
+
389
+ if (!runtime.capabilities.filesystem) {
390
+ throw new Error('File system operations not available in this runtime');
391
+ }
392
+
393
+ if (runtime.name === 'deno' && Deno) {
394
+ await Deno.writeTextFile(path, content);
395
+ return;
396
+ }
397
+
398
+ if (runtime.name === 'bun' && Bun) {
399
+ await Bun.write(path, content);
400
+ return;
401
+ }
402
+
403
+ if (runtime.name === 'node') {
404
+ const { writeFile, mkdir } = await import('fs/promises');
405
+ const parts = path.split('/');
406
+ parts.pop();
407
+ const dir = parts.join('/') || '.';
408
+ try {
409
+ await mkdir(dir, { recursive: true });
410
+ } catch {}
411
+ await writeFile(path, content, 'utf-8');
412
+ return;
413
+ }
414
+
415
+ throw new Error(`File system operations not implemented for ${runtime.name}`);
416
+ },
417
+ };
418
+
419
+ /**
420
+ * Cross-runtime subprocess execution
421
+ */
422
+ export const processUtils = {
423
+ /**
424
+ * Check if subprocess operations are available
425
+ */
426
+ isAvailable(): boolean {
427
+ return hasCapability('subprocess');
428
+ },
429
+
430
+ /**
431
+ * Execute command in a cross-runtime way
432
+ */
433
+ async exec(
434
+ command: string,
435
+ args: string[] = []
436
+ ): Promise<{ stdout: string; stderr: string; code: number }> {
437
+ const runtime = detectRuntime();
438
+
439
+ if (!runtime.capabilities.subprocess) {
440
+ throw new Error('Subprocess operations not available in this runtime');
441
+ }
442
+
443
+ if (runtime.name === 'deno' && Deno) {
444
+ const cmd = new Deno.Command(command, { args });
445
+ const { code, stdout, stderr } = await cmd.output();
446
+
447
+ return {
448
+ code,
449
+ stdout: new TextDecoder().decode(stdout),
450
+ stderr: new TextDecoder().decode(stderr),
451
+ };
452
+ }
453
+
454
+ if (runtime.name === 'bun' && Bun) {
455
+ const proc = Bun.spawn([command, ...args], {
456
+ stdout: 'pipe',
457
+ stderr: 'pipe',
458
+ });
459
+
460
+ const [stdout, stderr] = await Promise.all([
461
+ new Response(proc.stdout).text(),
462
+ new Response(proc.stderr).text(),
463
+ ]);
464
+
465
+ await proc.exited;
466
+
467
+ return {
468
+ code: proc.exitCode ?? 0,
469
+ stdout,
470
+ stderr,
471
+ };
472
+ }
473
+
474
+ if (runtime.name === 'node') {
475
+ const { spawn } = await import('child_process');
476
+ const { delimiter } = await import('node:path');
477
+
478
+ return new Promise((resolve, reject) => {
479
+ const extraPyPath = pathUtils.join(process.cwd(), 'tywrap_ir');
480
+ const existingPyPath = process.env.PYTHONPATH;
481
+ const env = {
482
+ ...process.env,
483
+ PYTHONPATH: existingPyPath ? `${extraPyPath}${delimiter}${existingPyPath}` : extraPyPath,
484
+ };
485
+ const child = spawn(command, args, { env });
486
+ let stdout = '';
487
+ let stderr = '';
488
+
489
+ child.stdout?.on('data', data => {
490
+ stdout += data.toString();
491
+ });
492
+
493
+ child.stderr?.on('data', data => {
494
+ stderr += data.toString();
495
+ });
496
+
497
+ child.on('close', code => {
498
+ resolve({ code: code ?? 0, stdout, stderr });
499
+ });
500
+
501
+ child.on('error', reject);
502
+ });
503
+ }
504
+
505
+ throw new Error(`Subprocess operations not implemented for ${runtime.name}`);
506
+ },
507
+ };
508
+
509
+ /**
510
+ * Platform detection utilities
511
+ */
512
+
513
+ /**
514
+ * Check if running on Windows
515
+ */
516
+ export function isWindows(): boolean {
517
+ if (typeof process !== 'undefined' && process.platform) {
518
+ return process.platform === 'win32';
519
+ }
520
+ const deno = (globalThis as unknown as { Deno?: { build?: { os?: string } } }).Deno;
521
+ return deno?.build?.os === 'windows';
522
+ }
523
+
524
+ /**
525
+ * Check if running on macOS
526
+ */
527
+ export function isMacOS(): boolean {
528
+ if (typeof process !== 'undefined' && process.platform) {
529
+ return process.platform === 'darwin';
530
+ }
531
+ const deno = (globalThis as unknown as { Deno?: { build?: { os?: string } } }).Deno;
532
+ return deno?.build?.os === 'darwin';
533
+ }
534
+
535
+ /**
536
+ * Check if running on Linux
537
+ */
538
+ export function isLinux(): boolean {
539
+ if (typeof process !== 'undefined' && process.platform) {
540
+ return process.platform === 'linux';
541
+ }
542
+ const deno = (globalThis as unknown as { Deno?: { build?: { os?: string } } }).Deno;
543
+ return deno?.build?.os === 'linux';
544
+ }
545
+
546
+ /**
547
+ * Check if a path is absolute
548
+ */
549
+ export function isAbsolutePath(path: string): boolean {
550
+ // Unix absolute paths
551
+ if (path.startsWith('/')) {
552
+ return true;
553
+ }
554
+ // Windows absolute paths (e.g., C:\, D:/)
555
+ return /^[A-Za-z]:[\\/]/.test(path);
556
+ }
557
+
558
+ /**
559
+ * Get the path separator for the current platform
560
+ */
561
+ export function getPathSeparator(): string {
562
+ return isWindows() ? '\\' : '/';
563
+ }
564
+
565
+ /**
566
+ * Get the default Python executable name for the current platform
567
+ */
568
+ export function getPythonExecutableName(): string {
569
+ return isWindows() ? 'python' : 'python3';
570
+ }
571
+
572
+ /**
573
+ * Get the virtual environment bin directory name
574
+ */
575
+ export function getVenvBinDir(): string {
576
+ return isWindows() ? 'Scripts' : 'bin';
577
+ }
578
+
579
+ /**
580
+ * Get the Python executable filename in a virtual environment
581
+ */
582
+ export function getVenvPythonExe(): string {
583
+ return isWindows() ? 'python.exe' : 'python';
584
+ }
585
+
586
+ /**
587
+ * Cross-runtime hashing and stable stringify utilities
588
+ */
589
+ export const hashUtils = {
590
+ async sha256Hex(text: string): Promise<string> {
591
+ const runtime = detectRuntime();
592
+ // Node path
593
+ if (runtime.name === 'node') {
594
+ try {
595
+ const crypto = await import('node:crypto');
596
+ return crypto.createHash('sha256').update(text).digest('hex');
597
+ } catch {
598
+ // fallthrough to web crypto
599
+ }
600
+ }
601
+ // Web/Deno/Bun path via SubtleCrypto
602
+ if (typeof globalThis.crypto?.subtle !== 'undefined') {
603
+ const data = new TextEncoder().encode(text);
604
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', data);
605
+ const bytes = Array.from(new Uint8Array(digest));
606
+ return bytes.map(b => b.toString(16).padStart(2, '0')).join('');
607
+ }
608
+ // Fallback to DJB2 (non-crypto) for unknown runtimes
609
+ let hash = 5381;
610
+ for (let i = 0; i < text.length; i++) {
611
+ hash = (hash << 5) + hash + text.charCodeAt(i);
612
+ hash |= 0;
613
+ }
614
+ return Math.abs(hash).toString(16);
615
+ },
616
+ };