@dadigua/web-terminal 0.1.3 → 0.1.5
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/API.md +4 -0
- package/README.md +20 -4
- package/config.example.yaml +2 -0
- package/dist/assets/index-Cdq-0BxC.js +28 -0
- package/dist/assets/index-DIMl7N0D.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/{app-TCUYGJ4K.js → app-FFT46HD7.js} +148 -22
- package/dist-server/{chunk-R33XRLBM.js → chunk-2APXDIFJ.js} +5 -3
- package/dist-server/cli.js +15 -5
- package/dist-server/vscodeInstaller-AIKL2DRR.js +210 -0
- package/package.json +5 -3
- package/release/web-terminal.vsix +0 -0
- package/dist/assets/index-Bv4iPZiS.js +0 -27
- package/dist/assets/index-DAKKO3a9.css +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import {
|
|
2
|
+
serviceVersion
|
|
3
|
+
} from "./chunk-2APXDIFJ.js";
|
|
4
|
+
|
|
5
|
+
// src/vscodeInstaller.ts
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { access, readFile, readdir, stat } from "node:fs/promises";
|
|
8
|
+
import { constants } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { delimiter, join, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { promisify } from "node:util";
|
|
13
|
+
var execute = promisify(execFile);
|
|
14
|
+
var extensionId = "dadigua.web-terminal";
|
|
15
|
+
var psQuote = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
16
|
+
async function exists(path) {
|
|
17
|
+
try {
|
|
18
|
+
await access(path);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function findServerLauncher(root) {
|
|
25
|
+
const cliRoot = join(root, "cli/servers");
|
|
26
|
+
let recent = [];
|
|
27
|
+
try {
|
|
28
|
+
const value = JSON.parse(await readFile(join(cliRoot, "lru.json"), "utf8"));
|
|
29
|
+
if (Array.isArray(value)) recent = value.filter((item) => typeof item === "string" && !item.includes("/") && !item.includes("\\"));
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
const candidates = [
|
|
33
|
+
...(await readdir(cliRoot).catch(() => [])).map((name) => ({ name, path: join(cliRoot, name, "server") })),
|
|
34
|
+
...(await readdir(join(root, "bin")).catch(() => [])).map((name) => ({ name, path: join(root, "bin", name) }))
|
|
35
|
+
];
|
|
36
|
+
const available = [];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
const entry = join(candidate.path, "out/server-main.js");
|
|
39
|
+
if (!await exists(entry)) continue;
|
|
40
|
+
available.push({ ...candidate, entry, mtime: (await stat(candidate.path)).mtimeMs });
|
|
41
|
+
}
|
|
42
|
+
available.sort((a, b) => {
|
|
43
|
+
const rank = (name) => recent.includes(name) ? recent.indexOf(name) : Infinity;
|
|
44
|
+
return rank(a.name) - rank(b.name) || b.mtime - a.mtime;
|
|
45
|
+
});
|
|
46
|
+
const server = available[0];
|
|
47
|
+
if (!server) return void 0;
|
|
48
|
+
const node = join(server.path, "node");
|
|
49
|
+
return { file: await exists(node) ? node : process.execPath, args: [server.entry, "--extensions-dir", join(root, "extensions")] };
|
|
50
|
+
}
|
|
51
|
+
async function findCommand(command) {
|
|
52
|
+
for (const directory of (process.env.PATH || "").split(delimiter)) {
|
|
53
|
+
const candidate = resolve(directory, command);
|
|
54
|
+
try {
|
|
55
|
+
await access(candidate, constants.X_OK);
|
|
56
|
+
return candidate;
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
async function isWsl() {
|
|
63
|
+
if (process.platform !== "linux") return false;
|
|
64
|
+
if (process.env.WSL_DISTRO_NAME) return true;
|
|
65
|
+
return (await readFile("/proc/version", "utf8").catch(() => "")).toLowerCase().includes("microsoft");
|
|
66
|
+
}
|
|
67
|
+
async function localLaunchers(options, wsl) {
|
|
68
|
+
const launchers = [];
|
|
69
|
+
for (const [channel, command, override, folder] of [
|
|
70
|
+
["Stable", "code", options.codeCommand, ".vscode-server"],
|
|
71
|
+
["Insiders", "code-insiders", options.codeInsidersCommand, ".vscode-server-insiders"]
|
|
72
|
+
]) {
|
|
73
|
+
if (override) {
|
|
74
|
+
launchers.push({ label: `\u672C\u673A ${channel}`, file: override, args: [] });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (process.platform === "linux") {
|
|
78
|
+
const defaultRoot = join(homedir(), folder);
|
|
79
|
+
const agentRoot = process.env.VSCODE_AGENT_FOLDER;
|
|
80
|
+
const root = agentRoot && agentRoot.includes("insiders") === (channel === "Insiders") ? agentRoot : defaultRoot;
|
|
81
|
+
const server = await findServerLauncher(root);
|
|
82
|
+
if (server) {
|
|
83
|
+
launchers.push({ label: `${wsl ? "WSL" : "Remote"} ${channel}`, ...server });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const file = await findCommand(command);
|
|
88
|
+
if (file && !(wsl && (file.startsWith("/mnt/") || file.includes("/remote-cli/")))) {
|
|
89
|
+
launchers.push({ label: `\u672C\u673A ${channel}`, file, args: [] });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return launchers;
|
|
93
|
+
}
|
|
94
|
+
async function invoke(launcher, args) {
|
|
95
|
+
const { stdout } = await execute(launcher.file, [...launcher.args, ...args], {
|
|
96
|
+
timeout: 6e4,
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
maxBuffer: 4 * 1024 * 1024
|
|
99
|
+
});
|
|
100
|
+
if (stdout.includes("Command is only available in WSL or inside a Visual Studio Code terminal")) {
|
|
101
|
+
throw new Error(stdout.trim());
|
|
102
|
+
}
|
|
103
|
+
return stdout;
|
|
104
|
+
}
|
|
105
|
+
function verifyExtension(output, version) {
|
|
106
|
+
const extension = output.split(/\r?\n/).map((line) => line.trim()).find((line) => line.toLowerCase().startsWith(`${extensionId}@`));
|
|
107
|
+
if (!extension || version && extension !== `${extensionId}@${version}`) {
|
|
108
|
+
throw new Error(`\u5B89\u88C5\u540E\u672A\u627E\u5230 ${extensionId}${version ? `@${version}` : ""}\uFF0C\u6269\u5C55\u5217\u8868\uFF1A
|
|
109
|
+
${output.trim()}`);
|
|
110
|
+
}
|
|
111
|
+
return extension;
|
|
112
|
+
}
|
|
113
|
+
async function installWindows(vsix, wsl, version) {
|
|
114
|
+
const source = wsl ? (await execute("wslpath", ["-w", vsix])).stdout.trim() : vsix;
|
|
115
|
+
const script = `
|
|
116
|
+
$ErrorActionPreference = 'Stop'
|
|
117
|
+
$temporary = $null
|
|
118
|
+
try {
|
|
119
|
+
Set-Location $env:SystemDrive\\
|
|
120
|
+
$vsix = ${psQuote(source)}
|
|
121
|
+
if (${wsl ? "$true" : "$false"}) {
|
|
122
|
+
$temporary = Join-Path $env:TEMP ('web-terminal-' + [guid]::NewGuid() + '.vsix')
|
|
123
|
+
Copy-Item -LiteralPath $vsix -Destination $temporary
|
|
124
|
+
$vsix = $temporary
|
|
125
|
+
}
|
|
126
|
+
$results = @()
|
|
127
|
+
$failures = @()
|
|
128
|
+
foreach ($channel in @(
|
|
129
|
+
@{ Name = 'Stable'; Command = 'code.cmd'; Folder = 'Microsoft VS Code' },
|
|
130
|
+
@{ Name = 'Insiders'; Command = 'code-insiders.cmd'; Folder = 'Microsoft VS Code Insiders' }
|
|
131
|
+
)) {
|
|
132
|
+
$command = (Get-Command $channel.Command -ErrorAction SilentlyContinue | Select-Object -First 1).Source
|
|
133
|
+
if (-not $command) {
|
|
134
|
+
foreach ($base in @((Join-Path $env:LOCALAPPDATA 'Programs'), $env:ProgramFiles, [Environment]::GetEnvironmentVariable('ProgramFiles(x86)'))) {
|
|
135
|
+
if (-not $base) { continue }
|
|
136
|
+
$candidate = Join-Path $base ($channel.Folder + '\\bin\\' + $channel.Command)
|
|
137
|
+
if (Test-Path -LiteralPath $candidate) { $command = $candidate; break }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (-not $command) { continue }
|
|
141
|
+
try {
|
|
142
|
+
& $command --install-extension $vsix --force | Out-Host
|
|
143
|
+
if ($LASTEXITCODE -ne 0) { throw ('\u5B89\u88C5\u547D\u4EE4\u9000\u51FA\u7801\uFF1A' + $LASTEXITCODE) }
|
|
144
|
+
$extensions = & $command --list-extensions --show-versions
|
|
145
|
+
if ($LASTEXITCODE -ne 0) { throw ('\u6269\u5C55\u5217\u8868\u547D\u4EE4\u9000\u51FA\u7801\uFF1A' + $LASTEXITCODE) }
|
|
146
|
+
$match = $extensions | Where-Object { $_ -like '${extensionId}@*' } | Select-Object -First 1
|
|
147
|
+
if (-not $match${version ? ` -or $match -ne ${psQuote(`${extensionId}@${version}`)}` : ""}) { throw '\u5B89\u88C5\u540E\u672A\u627E\u5230\u6307\u5B9A\u7248\u672C\u7684 Web Terminal \u6269\u5C55' }
|
|
148
|
+
$results += @{ target = ('Windows ' + $channel.Name); extension = $match }
|
|
149
|
+
} catch { $failures += ('Windows ' + $channel.Name + ': ' + $_.Exception.Message) }
|
|
150
|
+
}
|
|
151
|
+
Write-Output ('WEB_TERMINAL_INSTALL::' + (ConvertTo-Json -Compress -Depth 4 -InputObject @{ installed = @($results); failures = @($failures) }))
|
|
152
|
+
} catch { Write-Error $_ -ErrorAction Continue; exit 1 }
|
|
153
|
+
finally { if ($temporary) { Remove-Item -LiteralPath $temporary -Force -ErrorAction SilentlyContinue } }
|
|
154
|
+
`;
|
|
155
|
+
const powershell = wsl ? "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" : "powershell.exe";
|
|
156
|
+
const { stdout } = await execute(powershell, ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], {
|
|
157
|
+
timeout: 18e4,
|
|
158
|
+
windowsHide: true,
|
|
159
|
+
maxBuffer: 4 * 1024 * 1024
|
|
160
|
+
});
|
|
161
|
+
const marker = stdout.split(/\r?\n/).find((line) => line.startsWith("WEB_TERMINAL_INSTALL::"));
|
|
162
|
+
if (!marker) throw new Error(`Windows \u5B89\u88C5\u6CA1\u6709\u8FD4\u56DE\u9A8C\u8BC1\u7ED3\u679C\uFF1A
|
|
163
|
+
${stdout.trim()}`);
|
|
164
|
+
const result = JSON.parse(marker.slice("WEB_TERMINAL_INSTALL::".length));
|
|
165
|
+
for (const installed of result.installed) verifyExtension(installed.extension, version);
|
|
166
|
+
if (result.failures.length) throw new Error([...result.installed.map((item) => `${item.target} \u5DF2\u5B89\u88C5 ${item.extension}`), ...result.failures].join("\n"));
|
|
167
|
+
return result.installed;
|
|
168
|
+
}
|
|
169
|
+
async function installVSCodeExtension(options = {}) {
|
|
170
|
+
const path = options.vsixPath ? resolve(options.vsixPath) : fileURLToPath(new URL("../release/web-terminal.vsix", import.meta.url));
|
|
171
|
+
if (!await exists(path)) throw new Error(`\u672A\u627E\u5230 VSIX\uFF1A${path}\u3002\u672C\u5730\u5F00\u53D1\u8BF7\u8FD0\u884C pnpm package:vscode\uFF0C\u6216\u7528 --vsix \u6307\u5B9A\u6587\u4EF6\u3002`);
|
|
172
|
+
const version = options.vsixPath ? void 0 : serviceVersion;
|
|
173
|
+
const wsl = await isWsl();
|
|
174
|
+
const installed = [];
|
|
175
|
+
const failures = [];
|
|
176
|
+
const record = (result) => {
|
|
177
|
+
installed.push(result);
|
|
178
|
+
options.onInstalled?.(result);
|
|
179
|
+
};
|
|
180
|
+
if (process.platform !== "win32") {
|
|
181
|
+
for (const launcher of await localLaunchers(options, wsl)) {
|
|
182
|
+
try {
|
|
183
|
+
await invoke(launcher, ["--install-extension", path, "--force"]);
|
|
184
|
+
const extension = verifyExtension(await invoke(launcher, ["--list-extensions", "--show-versions"]), version);
|
|
185
|
+
record({ target: launcher.label, extension });
|
|
186
|
+
} catch (error) {
|
|
187
|
+
failures.push(`${launcher.label}\uFF1A${errorDetail(error)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (process.platform === "win32" || (options.installWindowsHost ?? wsl)) {
|
|
192
|
+
try {
|
|
193
|
+
for (const result of await installWindows(path, wsl, version)) record(result);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
failures.push(errorDetail(error));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (failures.length) throw new Error(`\u63D2\u4EF6\u5B89\u88C5\u672A\u5168\u90E8\u5B8C\u6210\uFF1A
|
|
199
|
+
${failures.join("\n")}`);
|
|
200
|
+
if (!installed.length) throw new Error("\u672A\u627E\u5230\u53EF\u7528\u7684 VS Code\u3002\u8BF7\u5B89\u88C5 VS Code \u5E76\u5C06 code \u52A0\u5165 PATH\uFF1BWSL \u6269\u5C55\u5BBF\u4E3B\u9700\u5148\u7528 Remote WSL \u8FDE\u63A5\u4E00\u6B21\u3002");
|
|
201
|
+
return installed;
|
|
202
|
+
}
|
|
203
|
+
function errorDetail(error) {
|
|
204
|
+
const failure = error;
|
|
205
|
+
return [error instanceof Error ? error.message : String(error), failure.stderr?.trim(), failure.stdout?.trim()].filter(Boolean).join("\n");
|
|
206
|
+
}
|
|
207
|
+
export {
|
|
208
|
+
findServerLauncher,
|
|
209
|
+
installVSCodeExtension
|
|
210
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dadigua/web-terminal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "tsx src/server/index.ts --dev",
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"check": "tsc --noEmit",
|
|
11
11
|
"build:clients": "tsx scripts/build-clients.ts",
|
|
12
12
|
"package:vscode": "pnpm build:clients && tsx scripts/package-vscode.ts",
|
|
13
|
+
"install:vscode": "pnpm package:vscode && tsx src/cli.ts install-vscode",
|
|
13
14
|
"electron": "pnpm build:clients && electron dist-clients/electron",
|
|
14
15
|
"package:electron:win": "pnpm build:clients && tsx scripts/package-electron.ts --win",
|
|
15
16
|
"package:electron:linux": "pnpm build:clients && tsx scripts/package-electron.ts --linux",
|
|
16
|
-
"prepack": "pnpm build"
|
|
17
|
+
"prepack": "pnpm build && pnpm package:vscode"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
20
|
"@xterm/addon-fit": "^0.11.0",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=22"
|
|
51
52
|
},
|
|
52
|
-
"packageManager": "pnpm@11.
|
|
53
|
+
"packageManager": "pnpm@11.28.2",
|
|
53
54
|
"bin": {
|
|
54
55
|
"web-terminal": "bin/web-terminal.mjs"
|
|
55
56
|
},
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"bin/",
|
|
58
59
|
"dist-server/",
|
|
59
60
|
"dist/",
|
|
61
|
+
"release/web-terminal.vsix",
|
|
60
62
|
"config.example.yaml",
|
|
61
63
|
"README.md",
|
|
62
64
|
"API.md"
|
|
Binary file
|