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.
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +263 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +41 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +234 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/analyzer.d.ts +63 -0
- package/dist/core/analyzer.d.ts.map +1 -0
- package/dist/core/analyzer.js +676 -0
- package/dist/core/analyzer.js.map +1 -0
- package/dist/core/discovery.d.ts +85 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +344 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/generator.d.ts +37 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/generator.js +368 -0
- package/dist/core/generator.js.map +1 -0
- package/dist/core/mapper.d.ts +40 -0
- package/dist/core/mapper.d.ts.map +1 -0
- package/dist/core/mapper.js +401 -0
- package/dist/core/mapper.js.map +1 -0
- package/dist/core/validation.d.ts +79 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +486 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/base.d.ts +12 -0
- package/dist/runtime/base.d.ts.map +1 -0
- package/dist/runtime/base.js +6 -0
- package/dist/runtime/base.js.map +1 -0
- package/dist/runtime/errors.d.ts +17 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +21 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/http.d.ts +22 -0
- package/dist/runtime/http.d.ts.map +1 -0
- package/dist/runtime/http.js +79 -0
- package/dist/runtime/http.js.map +1 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/node.d.ts +48 -0
- package/dist/runtime/node.d.ts.map +1 -0
- package/dist/runtime/node.js +328 -0
- package/dist/runtime/node.js.map +1 -0
- package/dist/runtime/optimized-node.d.ts +131 -0
- package/dist/runtime/optimized-node.d.ts.map +1 -0
- package/dist/runtime/optimized-node.js +622 -0
- package/dist/runtime/optimized-node.js.map +1 -0
- package/dist/runtime/protocol.d.ts +3 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +3 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/pyodide.d.ts +27 -0
- package/dist/runtime/pyodide.d.ts.map +1 -0
- package/dist/runtime/pyodide.js +191 -0
- package/dist/runtime/pyodide.js.map +1 -0
- package/dist/tools/python_suite.js +173 -0
- package/dist/tools/python_suite.js.map +1 -0
- package/dist/types/index.d.ts +299 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/tywrap.d.ts +29 -0
- package/dist/tywrap.d.ts.map +1 -0
- package/dist/tywrap.js +589 -0
- package/dist/tywrap.js.map +1 -0
- package/dist/utils/bundle-optimizer.d.ts +182 -0
- package/dist/utils/bundle-optimizer.d.ts.map +1 -0
- package/dist/utils/bundle-optimizer.js +680 -0
- package/dist/utils/bundle-optimizer.js.map +1 -0
- package/dist/utils/cache.d.ts +149 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +492 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/codec.d.ts +94 -0
- package/dist/utils/codec.d.ts.map +1 -0
- package/dist/utils/codec.js +150 -0
- package/dist/utils/codec.js.map +1 -0
- package/dist/utils/logger.d.ts +42 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +140 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/memory-profiler.d.ts +123 -0
- package/dist/utils/memory-profiler.d.ts.map +1 -0
- package/dist/utils/memory-profiler.js +426 -0
- package/dist/utils/memory-profiler.js.map +1 -0
- package/dist/utils/parallel-processor.d.ts +146 -0
- package/dist/utils/parallel-processor.d.ts.map +1 -0
- package/dist/utils/parallel-processor.js +636 -0
- package/dist/utils/parallel-processor.js.map +1 -0
- package/dist/utils/python.d.ts +8 -0
- package/dist/utils/python.d.ts.map +1 -0
- package/dist/utils/python.js +56 -0
- package/dist/utils/python.js.map +1 -0
- package/dist/utils/runtime.d.ts +153 -0
- package/dist/utils/runtime.d.ts.map +1 -0
- package/dist/utils/runtime.js +519 -0
- package/dist/utils/runtime.js.map +1 -0
- package/package.json +136 -0
- package/runtime/python_bridge.py +476 -0
- package/src/cli.ts +331 -0
- package/src/config/index.ts +289 -0
- package/src/core/analyzer.ts +788 -0
- package/src/core/discovery.ts +415 -0
- package/src/core/generator.ts +447 -0
- package/src/core/mapper.ts +504 -0
- package/src/core/validation.ts +693 -0
- package/src/index.ts +83 -0
- package/src/runtime/base.ts +32 -0
- package/src/runtime/errors.ts +19 -0
- package/src/runtime/http.ts +132 -0
- package/src/runtime/index.ts +24 -0
- package/src/runtime/node.ts +431 -0
- package/src/runtime/optimized-node.ts +838 -0
- package/src/runtime/protocol.ts +2 -0
- package/src/runtime/pyodide.ts +228 -0
- package/src/types/global.d.ts +60 -0
- package/src/types/index.ts +410 -0
- package/src/tywrap.ts +669 -0
- package/src/utils/bundle-optimizer.ts +876 -0
- package/src/utils/cache.ts +634 -0
- package/src/utils/codec.ts +275 -0
- package/src/utils/logger.ts +201 -0
- package/src/utils/memory-profiler.ts +582 -0
- package/src/utils/parallel-processor.ts +879 -0
- package/src/utils/python.ts +79 -0
- 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
|
+
};
|