@tlbx-ai/midterm 0.1.0 → 8.2.3
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 +4 -1
- package/bin/midterm.js +415 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Launch MidTerm through `npx`.
|
|
|
6
6
|
npx @tlbx-ai/midterm
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
The launcher downloads the native MidTerm release for your platform, caches it in your user profile,
|
|
9
|
+
The launcher downloads the native MidTerm release for your platform, caches it in your user profile, runs it locally, and opens MidTerm in your default browser.
|
|
10
10
|
|
|
11
11
|
Supported platforms:
|
|
12
12
|
|
|
@@ -24,10 +24,13 @@ npx @tlbx-ai/midterm -- --port 2001 --bind 127.0.0.1
|
|
|
24
24
|
Launcher-only options:
|
|
25
25
|
|
|
26
26
|
- `--channel stable|dev`
|
|
27
|
+
- `--no-browser`
|
|
27
28
|
- `--help-launcher`
|
|
28
29
|
|
|
29
30
|
Notes:
|
|
30
31
|
|
|
31
32
|
- Default channel is `stable`
|
|
32
33
|
- If you do not pass `--bind`, the launcher forces `127.0.0.1`
|
|
34
|
+
- If you do not pass `--port`, the launcher opens `https://127.0.0.1:2000`
|
|
33
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
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const fsp = require('node:fs/promises');
|
|
7
|
+
const https = require('node:https');
|
|
7
8
|
const os = require('node:os');
|
|
8
9
|
const path = require('node:path');
|
|
9
10
|
const { spawn, spawnSync } = require('node:child_process');
|
|
10
11
|
|
|
11
|
-
const PACKAGE_VERSION = '
|
|
12
|
+
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
13
|
+
const DEFAULT_PORT = 2000;
|
|
14
|
+
const SERVER_READY_TIMEOUT_MS = 15000;
|
|
15
|
+
const SERVER_READY_INTERVAL_MS = 500;
|
|
12
16
|
const REPO_OWNER = 'tlbx-ai';
|
|
13
17
|
const REPO_NAME = 'MidTerm';
|
|
14
18
|
const GITHUB_API = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}`;
|
|
@@ -21,33 +25,41 @@ async function main() {
|
|
|
21
25
|
return;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
const
|
|
28
|
+
const runtime = await detectRuntime();
|
|
29
|
+
const target = await getPlatformTarget(runtime);
|
|
25
30
|
const release = await resolveRelease(launcher.channel);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!fs.existsSync(mtPath) || !fs.existsSync(mthostPath)) {
|
|
31
|
-
throw new Error(`Downloaded release is incomplete: expected ${target.binaryName} and ${target.hostBinaryName}`);
|
|
32
|
-
}
|
|
31
|
+
const install = runtime.kind === 'wsl-interop'
|
|
32
|
+
? await ensureInstalledReleaseInWsl(release, target, runtime)
|
|
33
|
+
: await ensureInstalledRelease(release, target);
|
|
33
34
|
|
|
34
35
|
const childArgs = passthrough.slice();
|
|
35
|
-
|
|
36
|
+
const explicitBind = getArgValue(childArgs, '--bind');
|
|
37
|
+
const explicitPort = parsePortArg(getArgValue(childArgs, '--port'));
|
|
38
|
+
|
|
39
|
+
if (!explicitBind) {
|
|
36
40
|
childArgs.push('--bind', '127.0.0.1');
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
const browserUrl = buildBrowserUrl(explicitBind ?? '127.0.0.1', explicitPort ?? DEFAULT_PORT);
|
|
39
44
|
const childEnv = {
|
|
40
45
|
...process.env,
|
|
41
46
|
MIDTERM_LAUNCH_MODE: 'npx',
|
|
42
47
|
MIDTERM_NPX: '1',
|
|
43
48
|
MIDTERM_NPX_CHANNEL: launcher.channel,
|
|
44
|
-
MIDTERM_NPX_PACKAGE_VERSION: PACKAGE_VERSION
|
|
49
|
+
MIDTERM_NPX_PACKAGE_VERSION: PACKAGE_VERSION,
|
|
50
|
+
MIDTERM_NPX_RUNTIME: runtime.kind
|
|
45
51
|
};
|
|
46
52
|
|
|
47
|
-
const child =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
const child = runtime.kind === 'wsl-interop'
|
|
54
|
+
? spawnMidTermInWsl(runtime, install.mtPath, childArgs, childEnv)
|
|
55
|
+
: spawn(install.mtPath, childArgs, {
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
env: childEnv
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (launcher.openBrowser) {
|
|
61
|
+
void openBrowserWhenReady(browserUrl);
|
|
62
|
+
}
|
|
51
63
|
|
|
52
64
|
forwardSignal(child, 'SIGINT');
|
|
53
65
|
forwardSignal(child, 'SIGTERM');
|
|
@@ -66,7 +78,8 @@ async function main() {
|
|
|
66
78
|
function parseArgs(args) {
|
|
67
79
|
const launcher = {
|
|
68
80
|
help: false,
|
|
69
|
-
channel: 'stable'
|
|
81
|
+
channel: 'stable',
|
|
82
|
+
openBrowser: true
|
|
70
83
|
};
|
|
71
84
|
const passthrough = [];
|
|
72
85
|
|
|
@@ -83,6 +96,11 @@ function parseArgs(args) {
|
|
|
83
96
|
continue;
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
if (arg === '--no-browser') {
|
|
100
|
+
launcher.openBrowser = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
86
104
|
if (arg === '--channel') {
|
|
87
105
|
const value = args[i + 1];
|
|
88
106
|
if (value !== 'stable' && value !== 'dev') {
|
|
@@ -106,12 +124,85 @@ function printHelp() {
|
|
|
106
124
|
console.log('');
|
|
107
125
|
console.log('Launcher options:');
|
|
108
126
|
console.log(' --channel stable|dev Choose the release channel (default: stable)');
|
|
127
|
+
console.log(' --no-browser Do not auto-open MidTerm in the default browser');
|
|
109
128
|
console.log(' --help-launcher Show launcher help');
|
|
110
129
|
console.log('');
|
|
111
130
|
console.log('All other arguments are passed to mt.');
|
|
112
131
|
}
|
|
113
132
|
|
|
114
|
-
function
|
|
133
|
+
async function detectRuntime() {
|
|
134
|
+
const wslContext = await detectWslInteropContext();
|
|
135
|
+
if (wslContext) {
|
|
136
|
+
return wslContext;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
kind: 'native',
|
|
141
|
+
platform: process.platform,
|
|
142
|
+
arch: process.arch
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function detectWslInteropContext() {
|
|
147
|
+
if (process.platform !== 'win32') {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const parsed = parseWslUncPath(process.cwd());
|
|
152
|
+
if (!parsed) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const linuxHome = getWslCommandOutput(parsed.distroName, ['pwd'], '~');
|
|
157
|
+
if (!linuxHome.startsWith('/')) {
|
|
158
|
+
throw new Error(`Failed to determine WSL home directory for ${parsed.distroName}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const archRaw = getWslCommandOutput(parsed.distroName, ['uname', '-m'], '/');
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
kind: 'wsl-interop',
|
|
165
|
+
distroName: parsed.distroName,
|
|
166
|
+
linuxCwd: parsed.linuxPath,
|
|
167
|
+
linuxHome,
|
|
168
|
+
uncRoot: parsed.uncRoot,
|
|
169
|
+
arch: normalizeWslArchitecture(archRaw)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseWslUncPath(value) {
|
|
174
|
+
const normalized = String(value || '');
|
|
175
|
+
const match = normalized.match(/^\\\\wsl(?:\.localhost|\$)\\([^\\]+)(\\.*)?$/i);
|
|
176
|
+
if (!match) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const distroName = match[1];
|
|
181
|
+
const suffix = match[2] || '';
|
|
182
|
+
const linuxPath = suffix
|
|
183
|
+
? suffix.replace(/\\/g, '/')
|
|
184
|
+
: '/';
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
distroName,
|
|
188
|
+
linuxPath,
|
|
189
|
+
uncRoot: `\\\\wsl.localhost\\${distroName}`
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function getPlatformTarget(runtime) {
|
|
194
|
+
if (runtime.kind === 'wsl-interop') {
|
|
195
|
+
if (runtime.arch === 'x64') {
|
|
196
|
+
return {
|
|
197
|
+
assetName: 'mt-linux-x64.tar.gz',
|
|
198
|
+
binaryName: 'mt',
|
|
199
|
+
hostBinaryName: 'mthost'
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw new Error(`Unsupported WSL platform: linux ${runtime.arch}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
115
206
|
if (process.platform === 'win32' && process.arch === 'x64') {
|
|
116
207
|
return {
|
|
117
208
|
assetName: 'mt-win-x64.zip',
|
|
@@ -189,8 +280,12 @@ async function ensureInstalledRelease(release, target) {
|
|
|
189
280
|
throw new Error(`Release ${release.tag} does not contain ${target.assetName}`);
|
|
190
281
|
}
|
|
191
282
|
|
|
283
|
+
const mtPath = path.join(versionDir, target.binaryName);
|
|
284
|
+
const mthostPath = path.join(versionDir, target.hostBinaryName);
|
|
285
|
+
|
|
192
286
|
if (fs.existsSync(completeMarker)) {
|
|
193
|
-
|
|
287
|
+
ensureInstalledFilesExist(mtPath, mthostPath, target);
|
|
288
|
+
return { mtPath, mthostPath };
|
|
194
289
|
}
|
|
195
290
|
|
|
196
291
|
await fsp.mkdir(cacheRoot, { recursive: true });
|
|
@@ -209,7 +304,8 @@ async function ensureInstalledRelease(release, target) {
|
|
|
209
304
|
await fsp.rm(versionDir, { recursive: true, force: true });
|
|
210
305
|
await fsp.rename(extractDir, versionDir);
|
|
211
306
|
await fsp.writeFile(completeMarker, `${release.tag}\n`, 'utf8');
|
|
212
|
-
|
|
307
|
+
ensureInstalledFilesExist(mtPath, mthostPath, target);
|
|
308
|
+
return { mtPath, mthostPath };
|
|
213
309
|
} catch (error) {
|
|
214
310
|
await fsp.rm(versionDir, { recursive: true, force: true }).catch(() => {});
|
|
215
311
|
throw error;
|
|
@@ -218,6 +314,58 @@ async function ensureInstalledRelease(release, target) {
|
|
|
218
314
|
}
|
|
219
315
|
}
|
|
220
316
|
|
|
317
|
+
async function ensureInstalledReleaseInWsl(release, target, runtime) {
|
|
318
|
+
const cacheRootLinux = path.posix.join(runtime.linuxHome, '.cache', 'midterm', 'npx-cache');
|
|
319
|
+
const cacheRootUnc = toWslUncPath(runtime, cacheRootLinux);
|
|
320
|
+
const versionDirLinux = path.posix.join(cacheRootLinux, sanitizeTag(release.tag));
|
|
321
|
+
const versionDirUnc = toWslUncPath(runtime, versionDirLinux);
|
|
322
|
+
const completeMarkerUnc = toWslUncPath(runtime, path.posix.join(versionDirLinux, '.complete'));
|
|
323
|
+
const targetAsset = release.assets.find((asset) => asset.name === target.assetName);
|
|
324
|
+
|
|
325
|
+
if (!targetAsset || !targetAsset.browser_download_url) {
|
|
326
|
+
throw new Error(`Release ${release.tag} does not contain ${target.assetName}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const mtPath = path.posix.join(versionDirLinux, target.binaryName);
|
|
330
|
+
const mthostPath = path.posix.join(versionDirLinux, target.hostBinaryName);
|
|
331
|
+
const mtPathUnc = toWslUncPath(runtime, mtPath);
|
|
332
|
+
const mthostPathUnc = toWslUncPath(runtime, mthostPath);
|
|
333
|
+
|
|
334
|
+
if (fs.existsSync(completeMarkerUnc)) {
|
|
335
|
+
ensureInstalledFilesExist(mtPathUnc, mthostPathUnc, target);
|
|
336
|
+
return { mtPath, mthostPath };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await fsp.mkdir(cacheRootUnc, { recursive: true });
|
|
340
|
+
|
|
341
|
+
const tempName = `staging-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`;
|
|
342
|
+
const tempRootLinux = path.posix.join(cacheRootLinux, tempName);
|
|
343
|
+
const tempRootUnc = toWslUncPath(runtime, tempRootLinux);
|
|
344
|
+
const archiveLinux = path.posix.join(tempRootLinux, target.assetName);
|
|
345
|
+
const archiveUnc = toWslUncPath(runtime, archiveLinux);
|
|
346
|
+
const extractLinux = path.posix.join(tempRootLinux, 'extract');
|
|
347
|
+
const extractUnc = toWslUncPath(runtime, extractLinux);
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
await fsp.mkdir(extractUnc, { recursive: true });
|
|
351
|
+
console.error(`MidTerm ${release.tag}: downloading ${target.assetName}`);
|
|
352
|
+
await downloadFile(targetAsset.browser_download_url, archiveUnc);
|
|
353
|
+
console.error(`MidTerm ${release.tag}: extracting`);
|
|
354
|
+
runWslCommand(runtime, ['tar', '-xzf', archiveLinux, '-C', extractLinux], '/');
|
|
355
|
+
runWslCommand(runtime, ['chmod', '755', path.posix.join(extractLinux, target.binaryName), path.posix.join(extractLinux, target.hostBinaryName)], '/');
|
|
356
|
+
await fsp.rm(versionDirUnc, { recursive: true, force: true });
|
|
357
|
+
await fsp.rename(extractUnc, versionDirUnc);
|
|
358
|
+
await fsp.writeFile(completeMarkerUnc, `${release.tag}\n`, 'utf8');
|
|
359
|
+
ensureInstalledFilesExist(mtPathUnc, mthostPathUnc, target);
|
|
360
|
+
return { mtPath, mthostPath };
|
|
361
|
+
} catch (error) {
|
|
362
|
+
await fsp.rm(versionDirUnc, { recursive: true, force: true }).catch(() => {});
|
|
363
|
+
throw error;
|
|
364
|
+
} finally {
|
|
365
|
+
await fsp.rm(tempRootUnc, { recursive: true, force: true }).catch(() => {});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
221
369
|
function getCacheRoot() {
|
|
222
370
|
if (process.platform === 'win32') {
|
|
223
371
|
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
@@ -267,14 +415,20 @@ function extractArchive(archivePath, destinationPath) {
|
|
|
267
415
|
'-Command',
|
|
268
416
|
`Expand-Archive -LiteralPath '${escapePowerShell(archivePath)}' -DestinationPath '${escapePowerShell(destinationPath)}' -Force`
|
|
269
417
|
];
|
|
270
|
-
const result = spawnSync('powershell', command, {
|
|
418
|
+
const result = spawnSync('powershell', command, {
|
|
419
|
+
stdio: 'inherit',
|
|
420
|
+
cwd: getWindowsSubprocessCwd()
|
|
421
|
+
});
|
|
271
422
|
if (result.status !== 0) {
|
|
272
423
|
throw new Error(`Failed to extract ${path.basename(archivePath)} with PowerShell`);
|
|
273
424
|
}
|
|
274
425
|
return;
|
|
275
426
|
}
|
|
276
427
|
|
|
277
|
-
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destinationPath], {
|
|
428
|
+
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destinationPath], {
|
|
429
|
+
stdio: 'inherit',
|
|
430
|
+
cwd: getWindowsSubprocessCwd()
|
|
431
|
+
});
|
|
278
432
|
if (result.status !== 0) {
|
|
279
433
|
throw new Error(`Failed to extract ${path.basename(archivePath)} with tar`);
|
|
280
434
|
}
|
|
@@ -295,6 +449,159 @@ function hasArg(args, name) {
|
|
|
295
449
|
return args.some((arg) => arg === name || arg.startsWith(`${name}=`));
|
|
296
450
|
}
|
|
297
451
|
|
|
452
|
+
function getArgValue(args, name) {
|
|
453
|
+
for (let i = 0; i < args.length; i++) {
|
|
454
|
+
const arg = args[i];
|
|
455
|
+
if (arg === name) {
|
|
456
|
+
return args[i + 1];
|
|
457
|
+
}
|
|
458
|
+
if (arg.startsWith(`${name}=`)) {
|
|
459
|
+
return arg.slice(name.length + 1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function parsePortArg(value) {
|
|
467
|
+
if (!value) {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const parsed = Number.parseInt(value, 10);
|
|
472
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return parsed;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function buildBrowserUrl(bindAddress, port) {
|
|
480
|
+
const normalized = normalizeHostForBrowser(bindAddress);
|
|
481
|
+
return `https://${normalized}:${port}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function normalizeHostForBrowser(bindAddress) {
|
|
485
|
+
const raw = String(bindAddress || '').trim();
|
|
486
|
+
if (!raw || raw === '0.0.0.0' || raw === '::' || raw === '[::]') {
|
|
487
|
+
return '127.0.0.1';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const host = raw.replace(/^\[(.*)\]$/, '$1');
|
|
491
|
+
if (host.includes(':')) {
|
|
492
|
+
return `[${host}]`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return host;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function openBrowserWhenReady(url) {
|
|
499
|
+
const ready = await waitForServer(url, SERVER_READY_TIMEOUT_MS);
|
|
500
|
+
if (!ready) {
|
|
501
|
+
console.error(`@tlbx-ai/midterm: server did not become ready within ${SERVER_READY_TIMEOUT_MS}ms, opening browser anyway`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
openUrl(url);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function waitForServer(url, timeoutMs) {
|
|
508
|
+
const deadline = Date.now() + timeoutMs;
|
|
509
|
+
|
|
510
|
+
while (Date.now() < deadline) {
|
|
511
|
+
if (await probeUrl(url)) {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
await sleep(SERVER_READY_INTERVAL_MS);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function probeUrl(url) {
|
|
522
|
+
return new Promise((resolve) => {
|
|
523
|
+
const request = https.request(url, {
|
|
524
|
+
method: 'GET',
|
|
525
|
+
rejectUnauthorized: false,
|
|
526
|
+
timeout: SERVER_READY_INTERVAL_MS
|
|
527
|
+
}, (response) => {
|
|
528
|
+
response.resume();
|
|
529
|
+
resolve(true);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
request.on('error', () => resolve(false));
|
|
533
|
+
request.on('timeout', () => {
|
|
534
|
+
request.destroy();
|
|
535
|
+
resolve(false);
|
|
536
|
+
});
|
|
537
|
+
request.end();
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function sleep(ms) {
|
|
542
|
+
return new Promise((resolve) => {
|
|
543
|
+
setTimeout(resolve, ms);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function openUrl(url) {
|
|
548
|
+
let command;
|
|
549
|
+
let args;
|
|
550
|
+
|
|
551
|
+
if (process.platform === 'win32') {
|
|
552
|
+
command = 'cmd';
|
|
553
|
+
args = ['/c', 'start', '', url];
|
|
554
|
+
} else if (process.platform === 'darwin') {
|
|
555
|
+
command = 'open';
|
|
556
|
+
args = [url];
|
|
557
|
+
} else {
|
|
558
|
+
command = 'xdg-open';
|
|
559
|
+
args = [url];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const result = spawn(command, args, {
|
|
563
|
+
detached: true,
|
|
564
|
+
stdio: 'ignore',
|
|
565
|
+
windowsHide: true,
|
|
566
|
+
cwd: getWindowsSubprocessCwd()
|
|
567
|
+
});
|
|
568
|
+
result.on('error', (error) => {
|
|
569
|
+
console.error(`@tlbx-ai/midterm: failed to open browser automatically: ${error.message}`);
|
|
570
|
+
});
|
|
571
|
+
result.unref();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function spawnMidTermInWsl(runtime, mtPath, childArgs, childEnv) {
|
|
575
|
+
const envArgs = ['env'];
|
|
576
|
+
const passthroughEnv = [
|
|
577
|
+
'MIDTERM_LAUNCH_MODE',
|
|
578
|
+
'MIDTERM_NPX',
|
|
579
|
+
'MIDTERM_NPX_CHANNEL',
|
|
580
|
+
'MIDTERM_NPX_PACKAGE_VERSION',
|
|
581
|
+
'MIDTERM_NPX_RUNTIME'
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
for (const key of passthroughEnv) {
|
|
585
|
+
if (childEnv[key]) {
|
|
586
|
+
envArgs.push(`${key}=${childEnv[key]}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
envArgs.push(mtPath, ...childArgs);
|
|
591
|
+
|
|
592
|
+
return spawn('wsl.exe', [
|
|
593
|
+
'--distribution',
|
|
594
|
+
runtime.distroName,
|
|
595
|
+
'--cd',
|
|
596
|
+
runtime.linuxCwd,
|
|
597
|
+
'--exec',
|
|
598
|
+
...envArgs
|
|
599
|
+
], {
|
|
600
|
+
stdio: 'inherit',
|
|
601
|
+
cwd: getWindowsSubprocessCwd()
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
298
605
|
function forwardSignal(child, signal) {
|
|
299
606
|
process.on(signal, () => {
|
|
300
607
|
if (!child.killed) {
|
|
@@ -307,6 +614,93 @@ function escapePowerShell(value) {
|
|
|
307
614
|
return value.replace(/'/g, "''");
|
|
308
615
|
}
|
|
309
616
|
|
|
617
|
+
function ensureInstalledFilesExist(mtPath, mthostPath, target) {
|
|
618
|
+
if (!fs.existsSync(mtPath) || !fs.existsSync(mthostPath)) {
|
|
619
|
+
throw new Error(`Downloaded release is incomplete: expected ${target.binaryName} and ${target.hostBinaryName}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function toWslUncPath(runtime, linuxPath) {
|
|
624
|
+
const normalized = String(linuxPath || '/');
|
|
625
|
+
const suffix = normalized === '/'
|
|
626
|
+
? ''
|
|
627
|
+
: normalized.replace(/\//g, '\\');
|
|
628
|
+
return `${runtime.uncRoot}${suffix}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function getWslCommandOutput(distroName, commandArgs, cwd) {
|
|
632
|
+
const result = spawnSync('wsl.exe', [
|
|
633
|
+
'--distribution',
|
|
634
|
+
distroName,
|
|
635
|
+
'--cd',
|
|
636
|
+
cwd,
|
|
637
|
+
'--exec',
|
|
638
|
+
...commandArgs
|
|
639
|
+
], {
|
|
640
|
+
encoding: 'utf8',
|
|
641
|
+
cwd: getWindowsSubprocessCwd()
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
if (result.error) {
|
|
645
|
+
throw result.error;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (result.status !== 0) {
|
|
649
|
+
const stderr = String(result.stderr || '').trim();
|
|
650
|
+
throw new Error(`WSL command failed: ${stderr || commandArgs.join(' ')}`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return String(result.stdout || '').trim();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function runWslCommand(runtime, commandArgs, cwd) {
|
|
657
|
+
const result = spawnSync('wsl.exe', [
|
|
658
|
+
'--distribution',
|
|
659
|
+
runtime.distroName,
|
|
660
|
+
'--cd',
|
|
661
|
+
cwd,
|
|
662
|
+
'--exec',
|
|
663
|
+
...commandArgs
|
|
664
|
+
], {
|
|
665
|
+
stdio: 'inherit',
|
|
666
|
+
cwd: getWindowsSubprocessCwd()
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
if (result.error) {
|
|
670
|
+
throw result.error;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (result.status !== 0) {
|
|
674
|
+
throw new Error(`WSL command failed: ${commandArgs.join(' ')}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function normalizeWslArchitecture(value) {
|
|
679
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
680
|
+
if (normalized === 'x86_64' || normalized === 'amd64') {
|
|
681
|
+
return 'x64';
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (normalized === 'aarch64' || normalized === 'arm64') {
|
|
685
|
+
return 'arm64';
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return normalized || process.arch;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function getWindowsSubprocessCwd() {
|
|
692
|
+
if (process.platform !== 'win32') {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const cwd = process.cwd();
|
|
697
|
+
if (/^\\\\/.test(cwd)) {
|
|
698
|
+
return process.env.SystemRoot || 'C:\\Windows';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
|
|
310
704
|
function compareVersions(leftTag, rightTag) {
|
|
311
705
|
const left = parseVersion(leftTag);
|
|
312
706
|
const right = parseVersion(rightTag);
|