@tlbx-ai/midterm 8.2.1 → 8.2.4
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/README.md +2 -1
- package/bin/midterm.js +381 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,5 +31,6 @@ Notes:
|
|
|
31
31
|
|
|
32
32
|
- Default channel is `stable`
|
|
33
33
|
- If you do not pass `--bind`, the launcher forces `127.0.0.1`
|
|
34
|
-
- If you do not pass `--port`, the launcher
|
|
34
|
+
- If you do not pass `--port`, the launcher starts at `https://127.0.0.1:2000` and automatically moves to the next free port if `2000` is unavailable
|
|
35
35
|
- The launcher sets `MIDTERM_LAUNCH_MODE=npx` for the child process
|
|
36
|
+
- If you invoke `npx` from WSL but it resolves to Windows `node/npm`, the launcher detects the WSL working directory and runs the Linux MidTerm build inside that distro
|
package/bin/midterm.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const fsp = require('node:fs/promises');
|
|
7
7
|
const https = require('node:https');
|
|
8
|
+
const net = require('node:net');
|
|
8
9
|
const os = require('node:os');
|
|
9
10
|
const path = require('node:path');
|
|
10
11
|
const { spawn, spawnSync } = require('node:child_process');
|
|
11
12
|
|
|
12
13
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
13
14
|
const DEFAULT_PORT = 2000;
|
|
15
|
+
const MAX_PORT_SCAN_ATTEMPTS = 100;
|
|
14
16
|
const SERVER_READY_TIMEOUT_MS = 15000;
|
|
15
17
|
const SERVER_READY_INTERVAL_MS = 500;
|
|
16
18
|
const REPO_OWNER = 'tlbx-ai';
|
|
@@ -25,39 +27,55 @@ async function main() {
|
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const
|
|
30
|
+
const runtime = await detectRuntime();
|
|
31
|
+
const target = await getPlatformTarget(runtime);
|
|
29
32
|
const release = await resolveRelease(launcher.channel);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!fs.existsSync(mtPath) || !fs.existsSync(mthostPath)) {
|
|
35
|
-
throw new Error(`Downloaded release is incomplete: expected ${target.binaryName} and ${target.hostBinaryName}`);
|
|
36
|
-
}
|
|
33
|
+
const install = runtime.kind === 'wsl-interop'
|
|
34
|
+
? await ensureInstalledReleaseInWsl(release, target, runtime)
|
|
35
|
+
: await ensureInstalledRelease(release, target);
|
|
37
36
|
|
|
38
37
|
const childArgs = passthrough.slice();
|
|
38
|
+
const hasExplicitPort = hasArg(childArgs, '--port');
|
|
39
39
|
const explicitBind = getArgValue(childArgs, '--bind');
|
|
40
40
|
const explicitPort = parsePortArg(getArgValue(childArgs, '--port'));
|
|
41
|
+
const effectiveBind = explicitBind ?? '127.0.0.1';
|
|
42
|
+
const startsServer = shouldStartServer(childArgs);
|
|
43
|
+
|
|
44
|
+
let effectivePort = explicitPort ?? DEFAULT_PORT;
|
|
41
45
|
|
|
42
46
|
if (!explicitBind) {
|
|
43
47
|
childArgs.push('--bind', '127.0.0.1');
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
if (startsServer && !hasExplicitPort) {
|
|
51
|
+
effectivePort = await findAvailablePort(effectiveBind, DEFAULT_PORT);
|
|
52
|
+
childArgs.push('--port', String(effectivePort));
|
|
53
|
+
|
|
54
|
+
if (effectivePort !== DEFAULT_PORT) {
|
|
55
|
+
console.error(`@tlbx-ai/midterm: port ${DEFAULT_PORT} is unavailable, using ${effectivePort}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const browserUrl = startsServer
|
|
60
|
+
? buildBrowserUrl(effectiveBind, effectivePort)
|
|
61
|
+
: null;
|
|
47
62
|
const childEnv = {
|
|
48
63
|
...process.env,
|
|
49
64
|
MIDTERM_LAUNCH_MODE: 'npx',
|
|
50
65
|
MIDTERM_NPX: '1',
|
|
51
66
|
MIDTERM_NPX_CHANNEL: launcher.channel,
|
|
52
|
-
MIDTERM_NPX_PACKAGE_VERSION: PACKAGE_VERSION
|
|
67
|
+
MIDTERM_NPX_PACKAGE_VERSION: PACKAGE_VERSION,
|
|
68
|
+
MIDTERM_NPX_RUNTIME: runtime.kind
|
|
53
69
|
};
|
|
54
70
|
|
|
55
|
-
const child =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
const child = runtime.kind === 'wsl-interop'
|
|
72
|
+
? spawnMidTermInWsl(runtime, install.mtPath, childArgs, childEnv)
|
|
73
|
+
: spawn(install.mtPath, childArgs, {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
env: childEnv
|
|
76
|
+
});
|
|
59
77
|
|
|
60
|
-
if (launcher.openBrowser) {
|
|
78
|
+
if (launcher.openBrowser && browserUrl) {
|
|
61
79
|
void openBrowserWhenReady(browserUrl);
|
|
62
80
|
}
|
|
63
81
|
|
|
@@ -130,7 +148,105 @@ function printHelp() {
|
|
|
130
148
|
console.log('All other arguments are passed to mt.');
|
|
131
149
|
}
|
|
132
150
|
|
|
133
|
-
function
|
|
151
|
+
async function detectRuntime() {
|
|
152
|
+
const wslContext = await detectWslInteropContext();
|
|
153
|
+
if (wslContext) {
|
|
154
|
+
return wslContext;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
kind: 'native',
|
|
159
|
+
platform: process.platform,
|
|
160
|
+
arch: process.arch
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function detectWslInteropContext() {
|
|
165
|
+
if (process.platform !== 'win32') {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const parsed = findWslInteropPath();
|
|
170
|
+
if (!parsed) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const linuxHome = getWslCommandOutput(parsed.distroName, ['pwd'], '~');
|
|
175
|
+
if (!linuxHome.startsWith('/')) {
|
|
176
|
+
throw new Error(`Failed to determine WSL home directory for ${parsed.distroName}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const archRaw = getWslCommandOutput(parsed.distroName, ['uname', '-m'], '/');
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
kind: 'wsl-interop',
|
|
183
|
+
distroName: parsed.distroName,
|
|
184
|
+
linuxCwd: parsed.linuxPath,
|
|
185
|
+
linuxHome,
|
|
186
|
+
uncRoot: parsed.uncRoot,
|
|
187
|
+
arch: normalizeWslArchitecture(archRaw)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function findWslInteropPath() {
|
|
192
|
+
const candidates = [
|
|
193
|
+
process.cwd(),
|
|
194
|
+
process.env.INIT_CWD,
|
|
195
|
+
process.env.npm_config_local_prefix,
|
|
196
|
+
getPackageDirectory(process.env.npm_package_json)
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for (const candidate of candidates) {
|
|
200
|
+
const parsed = parseWslUncPath(candidate);
|
|
201
|
+
if (parsed) {
|
|
202
|
+
return parsed;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function getPackageDirectory(packageJsonPath) {
|
|
210
|
+
if (!packageJsonPath) {
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return path.win32.dirname(packageJsonPath);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseWslUncPath(value) {
|
|
218
|
+
const normalized = String(value || '');
|
|
219
|
+
const match = normalized.match(/^\\\\wsl(?:\.localhost|\$)\\([^\\]+)(\\.*)?$/i);
|
|
220
|
+
if (!match) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const distroName = match[1];
|
|
225
|
+
const suffix = match[2] || '';
|
|
226
|
+
const linuxPath = suffix
|
|
227
|
+
? suffix.replace(/\\/g, '/')
|
|
228
|
+
: '/';
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
distroName,
|
|
232
|
+
linuxPath,
|
|
233
|
+
uncRoot: `\\\\wsl.localhost\\${distroName}`
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function getPlatformTarget(runtime) {
|
|
238
|
+
if (runtime.kind === 'wsl-interop') {
|
|
239
|
+
if (runtime.arch === 'x64') {
|
|
240
|
+
return {
|
|
241
|
+
assetName: 'mt-linux-x64.tar.gz',
|
|
242
|
+
binaryName: 'mt',
|
|
243
|
+
hostBinaryName: 'mthost'
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
throw new Error(`Unsupported WSL platform: linux ${runtime.arch}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
134
250
|
if (process.platform === 'win32' && process.arch === 'x64') {
|
|
135
251
|
return {
|
|
136
252
|
assetName: 'mt-win-x64.zip',
|
|
@@ -208,8 +324,12 @@ async function ensureInstalledRelease(release, target) {
|
|
|
208
324
|
throw new Error(`Release ${release.tag} does not contain ${target.assetName}`);
|
|
209
325
|
}
|
|
210
326
|
|
|
327
|
+
const mtPath = path.join(versionDir, target.binaryName);
|
|
328
|
+
const mthostPath = path.join(versionDir, target.hostBinaryName);
|
|
329
|
+
|
|
211
330
|
if (fs.existsSync(completeMarker)) {
|
|
212
|
-
|
|
331
|
+
ensureInstalledFilesExist(mtPath, mthostPath, target);
|
|
332
|
+
return { mtPath, mthostPath };
|
|
213
333
|
}
|
|
214
334
|
|
|
215
335
|
await fsp.mkdir(cacheRoot, { recursive: true });
|
|
@@ -228,7 +348,8 @@ async function ensureInstalledRelease(release, target) {
|
|
|
228
348
|
await fsp.rm(versionDir, { recursive: true, force: true });
|
|
229
349
|
await fsp.rename(extractDir, versionDir);
|
|
230
350
|
await fsp.writeFile(completeMarker, `${release.tag}\n`, 'utf8');
|
|
231
|
-
|
|
351
|
+
ensureInstalledFilesExist(mtPath, mthostPath, target);
|
|
352
|
+
return { mtPath, mthostPath };
|
|
232
353
|
} catch (error) {
|
|
233
354
|
await fsp.rm(versionDir, { recursive: true, force: true }).catch(() => {});
|
|
234
355
|
throw error;
|
|
@@ -237,6 +358,58 @@ async function ensureInstalledRelease(release, target) {
|
|
|
237
358
|
}
|
|
238
359
|
}
|
|
239
360
|
|
|
361
|
+
async function ensureInstalledReleaseInWsl(release, target, runtime) {
|
|
362
|
+
const cacheRootLinux = path.posix.join(runtime.linuxHome, '.cache', 'midterm', 'npx-cache');
|
|
363
|
+
const cacheRootUnc = toWslUncPath(runtime, cacheRootLinux);
|
|
364
|
+
const versionDirLinux = path.posix.join(cacheRootLinux, sanitizeTag(release.tag));
|
|
365
|
+
const versionDirUnc = toWslUncPath(runtime, versionDirLinux);
|
|
366
|
+
const completeMarkerUnc = toWslUncPath(runtime, path.posix.join(versionDirLinux, '.complete'));
|
|
367
|
+
const targetAsset = release.assets.find((asset) => asset.name === target.assetName);
|
|
368
|
+
|
|
369
|
+
if (!targetAsset || !targetAsset.browser_download_url) {
|
|
370
|
+
throw new Error(`Release ${release.tag} does not contain ${target.assetName}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const mtPath = path.posix.join(versionDirLinux, target.binaryName);
|
|
374
|
+
const mthostPath = path.posix.join(versionDirLinux, target.hostBinaryName);
|
|
375
|
+
const mtPathUnc = toWslUncPath(runtime, mtPath);
|
|
376
|
+
const mthostPathUnc = toWslUncPath(runtime, mthostPath);
|
|
377
|
+
|
|
378
|
+
if (fs.existsSync(completeMarkerUnc)) {
|
|
379
|
+
ensureInstalledFilesExist(mtPathUnc, mthostPathUnc, target);
|
|
380
|
+
return { mtPath, mthostPath };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await fsp.mkdir(cacheRootUnc, { recursive: true });
|
|
384
|
+
|
|
385
|
+
const tempName = `staging-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`;
|
|
386
|
+
const tempRootLinux = path.posix.join(cacheRootLinux, tempName);
|
|
387
|
+
const tempRootUnc = toWslUncPath(runtime, tempRootLinux);
|
|
388
|
+
const archiveLinux = path.posix.join(tempRootLinux, target.assetName);
|
|
389
|
+
const archiveUnc = toWslUncPath(runtime, archiveLinux);
|
|
390
|
+
const extractLinux = path.posix.join(tempRootLinux, 'extract');
|
|
391
|
+
const extractUnc = toWslUncPath(runtime, extractLinux);
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
await fsp.mkdir(extractUnc, { recursive: true });
|
|
395
|
+
console.error(`MidTerm ${release.tag}: downloading ${target.assetName}`);
|
|
396
|
+
await downloadFile(targetAsset.browser_download_url, archiveUnc);
|
|
397
|
+
console.error(`MidTerm ${release.tag}: extracting`);
|
|
398
|
+
runWslCommand(runtime, ['tar', '-xzf', archiveLinux, '-C', extractLinux], '/');
|
|
399
|
+
runWslCommand(runtime, ['chmod', '755', path.posix.join(extractLinux, target.binaryName), path.posix.join(extractLinux, target.hostBinaryName)], '/');
|
|
400
|
+
await fsp.rm(versionDirUnc, { recursive: true, force: true });
|
|
401
|
+
await fsp.rename(extractUnc, versionDirUnc);
|
|
402
|
+
await fsp.writeFile(completeMarkerUnc, `${release.tag}\n`, 'utf8');
|
|
403
|
+
ensureInstalledFilesExist(mtPathUnc, mthostPathUnc, target);
|
|
404
|
+
return { mtPath, mthostPath };
|
|
405
|
+
} catch (error) {
|
|
406
|
+
await fsp.rm(versionDirUnc, { recursive: true, force: true }).catch(() => {});
|
|
407
|
+
throw error;
|
|
408
|
+
} finally {
|
|
409
|
+
await fsp.rm(tempRootUnc, { recursive: true, force: true }).catch(() => {});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
240
413
|
function getCacheRoot() {
|
|
241
414
|
if (process.platform === 'win32') {
|
|
242
415
|
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
@@ -286,14 +459,20 @@ function extractArchive(archivePath, destinationPath) {
|
|
|
286
459
|
'-Command',
|
|
287
460
|
`Expand-Archive -LiteralPath '${escapePowerShell(archivePath)}' -DestinationPath '${escapePowerShell(destinationPath)}' -Force`
|
|
288
461
|
];
|
|
289
|
-
const result = spawnSync('powershell', command, {
|
|
462
|
+
const result = spawnSync('powershell', command, {
|
|
463
|
+
stdio: 'inherit',
|
|
464
|
+
cwd: getWindowsSubprocessCwd()
|
|
465
|
+
});
|
|
290
466
|
if (result.status !== 0) {
|
|
291
467
|
throw new Error(`Failed to extract ${path.basename(archivePath)} with PowerShell`);
|
|
292
468
|
}
|
|
293
469
|
return;
|
|
294
470
|
}
|
|
295
471
|
|
|
296
|
-
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destinationPath], {
|
|
472
|
+
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destinationPath], {
|
|
473
|
+
stdio: 'inherit',
|
|
474
|
+
cwd: getWindowsSubprocessCwd()
|
|
475
|
+
});
|
|
297
476
|
if (result.status !== 0) {
|
|
298
477
|
throw new Error(`Failed to extract ${path.basename(archivePath)} with tar`);
|
|
299
478
|
}
|
|
@@ -341,6 +520,68 @@ function parsePortArg(value) {
|
|
|
341
520
|
return parsed;
|
|
342
521
|
}
|
|
343
522
|
|
|
523
|
+
function shouldStartServer(args) {
|
|
524
|
+
const nonServerFlags = [
|
|
525
|
+
'--check-update',
|
|
526
|
+
'--update',
|
|
527
|
+
'--apply-update',
|
|
528
|
+
'--version',
|
|
529
|
+
'-v',
|
|
530
|
+
'--help',
|
|
531
|
+
'-h',
|
|
532
|
+
'--hash-password',
|
|
533
|
+
'--write-secret',
|
|
534
|
+
'--generate-cert'
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
return !nonServerFlags.some((flag) => hasArg(args, flag));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function findAvailablePort(bindAddress, preferredPort) {
|
|
541
|
+
for (let offset = 0; offset < MAX_PORT_SCAN_ATTEMPTS; offset++) {
|
|
542
|
+
const port = preferredPort + offset;
|
|
543
|
+
if (port > 65535) {
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (await isPortAvailable(bindAddress, port)) {
|
|
548
|
+
return port;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
throw new Error(`Could not find a free port starting at ${preferredPort}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function isPortAvailable(bindAddress, port) {
|
|
556
|
+
const host = normalizeBindForNetProbe(bindAddress);
|
|
557
|
+
|
|
558
|
+
return new Promise((resolve) => {
|
|
559
|
+
const server = net.createServer();
|
|
560
|
+
|
|
561
|
+
server.once('error', (error) => {
|
|
562
|
+
if (error && (error.code === 'EADDRINUSE' || error.code === 'EACCES')) {
|
|
563
|
+
resolve(false);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
resolve(false);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
server.listen(port, host, () => {
|
|
571
|
+
server.close(() => resolve(true));
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function normalizeBindForNetProbe(bindAddress) {
|
|
577
|
+
const raw = String(bindAddress || '').trim();
|
|
578
|
+
if (!raw || raw === 'localhost') {
|
|
579
|
+
return '127.0.0.1';
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return raw.replace(/^\[(.*)\]$/, '$1');
|
|
583
|
+
}
|
|
584
|
+
|
|
344
585
|
function buildBrowserUrl(bindAddress, port) {
|
|
345
586
|
const normalized = normalizeHostForBrowser(bindAddress);
|
|
346
587
|
return `https://${normalized}:${port}`;
|
|
@@ -427,7 +668,8 @@ function openUrl(url) {
|
|
|
427
668
|
const result = spawn(command, args, {
|
|
428
669
|
detached: true,
|
|
429
670
|
stdio: 'ignore',
|
|
430
|
-
windowsHide: true
|
|
671
|
+
windowsHide: true,
|
|
672
|
+
cwd: getWindowsSubprocessCwd()
|
|
431
673
|
});
|
|
432
674
|
result.on('error', (error) => {
|
|
433
675
|
console.error(`@tlbx-ai/midterm: failed to open browser automatically: ${error.message}`);
|
|
@@ -435,6 +677,37 @@ function openUrl(url) {
|
|
|
435
677
|
result.unref();
|
|
436
678
|
}
|
|
437
679
|
|
|
680
|
+
function spawnMidTermInWsl(runtime, mtPath, childArgs, childEnv) {
|
|
681
|
+
const envArgs = ['env'];
|
|
682
|
+
const passthroughEnv = [
|
|
683
|
+
'MIDTERM_LAUNCH_MODE',
|
|
684
|
+
'MIDTERM_NPX',
|
|
685
|
+
'MIDTERM_NPX_CHANNEL',
|
|
686
|
+
'MIDTERM_NPX_PACKAGE_VERSION',
|
|
687
|
+
'MIDTERM_NPX_RUNTIME'
|
|
688
|
+
];
|
|
689
|
+
|
|
690
|
+
for (const key of passthroughEnv) {
|
|
691
|
+
if (childEnv[key]) {
|
|
692
|
+
envArgs.push(`${key}=${childEnv[key]}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
envArgs.push(mtPath, ...childArgs);
|
|
697
|
+
|
|
698
|
+
return spawn('wsl.exe', [
|
|
699
|
+
'--distribution',
|
|
700
|
+
runtime.distroName,
|
|
701
|
+
'--cd',
|
|
702
|
+
runtime.linuxCwd,
|
|
703
|
+
'--exec',
|
|
704
|
+
...envArgs
|
|
705
|
+
], {
|
|
706
|
+
stdio: 'inherit',
|
|
707
|
+
cwd: getWindowsSubprocessCwd()
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
438
711
|
function forwardSignal(child, signal) {
|
|
439
712
|
process.on(signal, () => {
|
|
440
713
|
if (!child.killed) {
|
|
@@ -447,6 +720,93 @@ function escapePowerShell(value) {
|
|
|
447
720
|
return value.replace(/'/g, "''");
|
|
448
721
|
}
|
|
449
722
|
|
|
723
|
+
function ensureInstalledFilesExist(mtPath, mthostPath, target) {
|
|
724
|
+
if (!fs.existsSync(mtPath) || !fs.existsSync(mthostPath)) {
|
|
725
|
+
throw new Error(`Downloaded release is incomplete: expected ${target.binaryName} and ${target.hostBinaryName}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function toWslUncPath(runtime, linuxPath) {
|
|
730
|
+
const normalized = String(linuxPath || '/');
|
|
731
|
+
const suffix = normalized === '/'
|
|
732
|
+
? ''
|
|
733
|
+
: normalized.replace(/\//g, '\\');
|
|
734
|
+
return `${runtime.uncRoot}${suffix}`;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function getWslCommandOutput(distroName, commandArgs, cwd) {
|
|
738
|
+
const result = spawnSync('wsl.exe', [
|
|
739
|
+
'--distribution',
|
|
740
|
+
distroName,
|
|
741
|
+
'--cd',
|
|
742
|
+
cwd,
|
|
743
|
+
'--exec',
|
|
744
|
+
...commandArgs
|
|
745
|
+
], {
|
|
746
|
+
encoding: 'utf8',
|
|
747
|
+
cwd: getWindowsSubprocessCwd()
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
if (result.error) {
|
|
751
|
+
throw result.error;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (result.status !== 0) {
|
|
755
|
+
const stderr = String(result.stderr || '').trim();
|
|
756
|
+
throw new Error(`WSL command failed: ${stderr || commandArgs.join(' ')}`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return String(result.stdout || '').trim();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function runWslCommand(runtime, commandArgs, cwd) {
|
|
763
|
+
const result = spawnSync('wsl.exe', [
|
|
764
|
+
'--distribution',
|
|
765
|
+
runtime.distroName,
|
|
766
|
+
'--cd',
|
|
767
|
+
cwd,
|
|
768
|
+
'--exec',
|
|
769
|
+
...commandArgs
|
|
770
|
+
], {
|
|
771
|
+
stdio: 'inherit',
|
|
772
|
+
cwd: getWindowsSubprocessCwd()
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
if (result.error) {
|
|
776
|
+
throw result.error;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (result.status !== 0) {
|
|
780
|
+
throw new Error(`WSL command failed: ${commandArgs.join(' ')}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function normalizeWslArchitecture(value) {
|
|
785
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
786
|
+
if (normalized === 'x86_64' || normalized === 'amd64') {
|
|
787
|
+
return 'x64';
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (normalized === 'aarch64' || normalized === 'arm64') {
|
|
791
|
+
return 'arm64';
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return normalized || process.arch;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function getWindowsSubprocessCwd() {
|
|
798
|
+
if (process.platform !== 'win32') {
|
|
799
|
+
return undefined;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const cwd = process.cwd();
|
|
803
|
+
if (/^\\\\/.test(cwd)) {
|
|
804
|
+
return process.env.SystemRoot || 'C:\\Windows';
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return undefined;
|
|
808
|
+
}
|
|
809
|
+
|
|
450
810
|
function compareVersions(leftTag, rightTag) {
|
|
451
811
|
const left = parseVersion(leftTag);
|
|
452
812
|
const right = parseVersion(rightTag);
|