@tlbx-ai/midterm 8.2.1 → 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 +1 -0
- package/bin/midterm.js +273 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,3 +33,4 @@ Notes:
|
|
|
33
33
|
- If you do not pass `--bind`, the launcher forces `127.0.0.1`
|
|
34
34
|
- If you do not pass `--port`, the launcher opens `https://127.0.0.1:2000`
|
|
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
|
@@ -25,15 +25,12 @@ async function main() {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const runtime = await detectRuntime();
|
|
29
|
+
const target = await getPlatformTarget(runtime);
|
|
29
30
|
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
|
-
}
|
|
31
|
+
const install = runtime.kind === 'wsl-interop'
|
|
32
|
+
? await ensureInstalledReleaseInWsl(release, target, runtime)
|
|
33
|
+
: await ensureInstalledRelease(release, target);
|
|
37
34
|
|
|
38
35
|
const childArgs = passthrough.slice();
|
|
39
36
|
const explicitBind = getArgValue(childArgs, '--bind');
|
|
@@ -49,13 +46,16 @@ async function main() {
|
|
|
49
46
|
MIDTERM_LAUNCH_MODE: 'npx',
|
|
50
47
|
MIDTERM_NPX: '1',
|
|
51
48
|
MIDTERM_NPX_CHANNEL: launcher.channel,
|
|
52
|
-
MIDTERM_NPX_PACKAGE_VERSION: PACKAGE_VERSION
|
|
49
|
+
MIDTERM_NPX_PACKAGE_VERSION: PACKAGE_VERSION,
|
|
50
|
+
MIDTERM_NPX_RUNTIME: runtime.kind
|
|
53
51
|
};
|
|
54
52
|
|
|
55
|
-
const child =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
59
|
|
|
60
60
|
if (launcher.openBrowser) {
|
|
61
61
|
void openBrowserWhenReady(browserUrl);
|
|
@@ -130,7 +130,79 @@ function printHelp() {
|
|
|
130
130
|
console.log('All other arguments are passed to mt.');
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
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
|
+
|
|
134
206
|
if (process.platform === 'win32' && process.arch === 'x64') {
|
|
135
207
|
return {
|
|
136
208
|
assetName: 'mt-win-x64.zip',
|
|
@@ -208,8 +280,12 @@ async function ensureInstalledRelease(release, target) {
|
|
|
208
280
|
throw new Error(`Release ${release.tag} does not contain ${target.assetName}`);
|
|
209
281
|
}
|
|
210
282
|
|
|
283
|
+
const mtPath = path.join(versionDir, target.binaryName);
|
|
284
|
+
const mthostPath = path.join(versionDir, target.hostBinaryName);
|
|
285
|
+
|
|
211
286
|
if (fs.existsSync(completeMarker)) {
|
|
212
|
-
|
|
287
|
+
ensureInstalledFilesExist(mtPath, mthostPath, target);
|
|
288
|
+
return { mtPath, mthostPath };
|
|
213
289
|
}
|
|
214
290
|
|
|
215
291
|
await fsp.mkdir(cacheRoot, { recursive: true });
|
|
@@ -228,7 +304,8 @@ async function ensureInstalledRelease(release, target) {
|
|
|
228
304
|
await fsp.rm(versionDir, { recursive: true, force: true });
|
|
229
305
|
await fsp.rename(extractDir, versionDir);
|
|
230
306
|
await fsp.writeFile(completeMarker, `${release.tag}\n`, 'utf8');
|
|
231
|
-
|
|
307
|
+
ensureInstalledFilesExist(mtPath, mthostPath, target);
|
|
308
|
+
return { mtPath, mthostPath };
|
|
232
309
|
} catch (error) {
|
|
233
310
|
await fsp.rm(versionDir, { recursive: true, force: true }).catch(() => {});
|
|
234
311
|
throw error;
|
|
@@ -237,6 +314,58 @@ async function ensureInstalledRelease(release, target) {
|
|
|
237
314
|
}
|
|
238
315
|
}
|
|
239
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
|
+
|
|
240
369
|
function getCacheRoot() {
|
|
241
370
|
if (process.platform === 'win32') {
|
|
242
371
|
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
@@ -286,14 +415,20 @@ function extractArchive(archivePath, destinationPath) {
|
|
|
286
415
|
'-Command',
|
|
287
416
|
`Expand-Archive -LiteralPath '${escapePowerShell(archivePath)}' -DestinationPath '${escapePowerShell(destinationPath)}' -Force`
|
|
288
417
|
];
|
|
289
|
-
const result = spawnSync('powershell', command, {
|
|
418
|
+
const result = spawnSync('powershell', command, {
|
|
419
|
+
stdio: 'inherit',
|
|
420
|
+
cwd: getWindowsSubprocessCwd()
|
|
421
|
+
});
|
|
290
422
|
if (result.status !== 0) {
|
|
291
423
|
throw new Error(`Failed to extract ${path.basename(archivePath)} with PowerShell`);
|
|
292
424
|
}
|
|
293
425
|
return;
|
|
294
426
|
}
|
|
295
427
|
|
|
296
|
-
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destinationPath], {
|
|
428
|
+
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destinationPath], {
|
|
429
|
+
stdio: 'inherit',
|
|
430
|
+
cwd: getWindowsSubprocessCwd()
|
|
431
|
+
});
|
|
297
432
|
if (result.status !== 0) {
|
|
298
433
|
throw new Error(`Failed to extract ${path.basename(archivePath)} with tar`);
|
|
299
434
|
}
|
|
@@ -427,7 +562,8 @@ function openUrl(url) {
|
|
|
427
562
|
const result = spawn(command, args, {
|
|
428
563
|
detached: true,
|
|
429
564
|
stdio: 'ignore',
|
|
430
|
-
windowsHide: true
|
|
565
|
+
windowsHide: true,
|
|
566
|
+
cwd: getWindowsSubprocessCwd()
|
|
431
567
|
});
|
|
432
568
|
result.on('error', (error) => {
|
|
433
569
|
console.error(`@tlbx-ai/midterm: failed to open browser automatically: ${error.message}`);
|
|
@@ -435,6 +571,37 @@ function openUrl(url) {
|
|
|
435
571
|
result.unref();
|
|
436
572
|
}
|
|
437
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
|
+
|
|
438
605
|
function forwardSignal(child, signal) {
|
|
439
606
|
process.on(signal, () => {
|
|
440
607
|
if (!child.killed) {
|
|
@@ -447,6 +614,93 @@ function escapePowerShell(value) {
|
|
|
447
614
|
return value.replace(/'/g, "''");
|
|
448
615
|
}
|
|
449
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
|
+
|
|
450
704
|
function compareVersions(leftTag, rightTag) {
|
|
451
705
|
const left = parseVersion(leftTag);
|
|
452
706
|
const right = parseVersion(rightTag);
|