dsh-plugin-wsl-env 0.7.5 → 0.8.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/CHANGELOG.md +278 -0
- package/CONTRIBUTING.md +129 -39
- package/README.md +20 -20
- package/docs/ARCHITECTURE.md +6 -3
- package/docs/LIMITATIONS.md +13 -0
- package/docs/README.zh.md +20 -20
- package/lib/agent-confined.d.ts +42 -0
- package/lib/agent-confined.js +25 -41
- package/lib/agent-errors.d.ts +31 -0
- package/lib/agent-errors.js +19 -16
- package/lib/agent-exec.d.ts +159 -0
- package/lib/agent-exec.js +151 -165
- package/lib/agent-protocol.d.ts +192 -0
- package/lib/agent-protocol.js +76 -100
- package/lib/agent-shared.d.ts +27 -0
- package/lib/agent-shared.js +12 -12
- package/lib/agent.d.ts +353 -0
- package/lib/agent.js +536 -552
- package/lib/auto-preset.d.ts +110 -0
- package/lib/auto-preset.js +157 -141
- package/lib/bwrap.d.ts +90 -0
- package/lib/bwrap.js +18 -18
- package/lib/file-reference-wsl.d.ts +160 -0
- package/lib/file-reference-wsl.js +192 -173
- package/lib/fs-decisions.d.ts +107 -0
- package/lib/fs-decisions.js +64 -60
- package/lib/fs-routing.d.ts +182 -0
- package/lib/fs-routing.js +173 -116
- package/lib/fs-substrate.d.ts +258 -0
- package/lib/fs-substrate.js +248 -248
- package/lib/fs.d.ts +10 -0
- package/lib/fs.js +5 -0
- package/lib/fsio-agent.d.ts +307 -0
- package/lib/fsio-agent.js +414 -410
- package/lib/fsio-text.d.ts +115 -0
- package/lib/fsio-text.js +104 -84
- package/lib/git-route.d.ts +100 -0
- package/lib/git-route.js +46 -47
- package/lib/index.d.ts +1003 -0
- package/lib/index.js +1101 -1003
- package/lib/listing.d.ts +114 -0
- package/lib/listing.js +68 -55
- package/lib/paths.d.ts +182 -0
- package/lib/paths.js +134 -109
- package/lib/picker.d.ts +266 -0
- package/lib/picker.js +371 -351
- package/lib/ports.d.ts +44 -0
- package/lib/ports.js +39 -29
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +40 -0
- package/lib/probe-cache.js +35 -29
- package/lib/sandbox-core.d.ts +79 -0
- package/lib/sandbox-core.js +71 -88
- package/lib/sandbox.d.ts +48 -0
- package/lib/sandbox.js +4 -4
- package/lib/search-exec.d.ts +96 -0
- package/lib/search-exec.js +52 -79
- package/lib/search-route.d.ts +75 -0
- package/lib/search-route.js +30 -31
- package/lib/shell-env.d.ts +88 -0
- package/lib/shell-env.js +78 -74
- package/lib/shell.d.ts +10 -0
- package/lib/shell.js +5 -0
- package/lib/subprocess.d.ts +313 -0
- package/lib/subprocess.js +267 -262
- package/lib/terminal-activity.d.ts +98 -0
- package/lib/terminal-activity.js +29 -39
- package/lib/terminal-route.d.ts +27 -0
- package/lib/terminal-route.js +16 -8
- package/lib/watcher.d.ts +77 -0
- package/lib/watcher.js +120 -126
- package/lib/workspace-files-route.d.ts +132 -0
- package/lib/workspace-files-route.js +73 -81
- package/lib/workspace-files-wsl.d.ts +257 -0
- package/lib/workspace-files-wsl.js +311 -251
- package/lib/wsl.d.ts +266 -0
- package/lib/wsl.js +144 -155
- package/package.json +68 -20
package/lib/wsl.js
CHANGED
|
@@ -6,15 +6,16 @@
|
|
|
6
6
|
* own Loader entry and must not drag the shell executor's and filesystem
|
|
7
7
|
* backend's peer dependencies into that row.
|
|
8
8
|
*
|
|
9
|
+
* This is a TypeScript source built to `lib/wsl.js`; edit THIS file and run
|
|
10
|
+
* `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
11
|
+
* fails when it drifts.
|
|
12
|
+
*
|
|
9
13
|
* @module dsh-plugin-wsl/wsl
|
|
10
14
|
*/
|
|
11
|
-
|
|
12
15
|
import { execFile } from "node:child_process";
|
|
13
16
|
import { posixToUnc } from "./paths.js";
|
|
14
|
-
|
|
15
17
|
/** Default executable name; resolution is left to PATH. */
|
|
16
18
|
export const DEFAULT_WSL_PATH = "wsl.exe";
|
|
17
|
-
|
|
18
19
|
/**
|
|
19
20
|
* The deadline every `wsl.exe` call runs under, when the caller names none.
|
|
20
21
|
*
|
|
@@ -26,7 +27,6 @@ export const DEFAULT_WSL_PATH = "wsl.exe";
|
|
|
26
27
|
* genuinely wants to wait forever.
|
|
27
28
|
*/
|
|
28
29
|
export const DEFAULT_WSL_DEADLINE_MS = 60_000;
|
|
29
|
-
|
|
30
30
|
/**
|
|
31
31
|
* Run one host command and capture UTF-8 output.
|
|
32
32
|
*
|
|
@@ -36,76 +36,78 @@ export const DEFAULT_WSL_DEADLINE_MS = 60_000;
|
|
|
36
36
|
*
|
|
37
37
|
* @param argv - executable plus arguments, spawned without a shell.
|
|
38
38
|
* @param signal - optional cancellation.
|
|
39
|
-
* @param deadlineMs - kill the call after this many milliseconds; the
|
|
40
|
-
* is {@link DEFAULT_WSL_DEADLINE_MS}, and `0` waits forever.
|
|
39
|
+
* @param deadlineMs - kill the call after this many milliseconds; the
|
|
40
|
+
* default is {@link DEFAULT_WSL_DEADLINE_MS}, and `0` waits forever.
|
|
41
41
|
* @returns captured stdout.
|
|
42
42
|
* @throws the execFile error, with a hint attached when the cause is the sandbox.
|
|
43
43
|
*/
|
|
44
44
|
export function runCapture(argv, signal, deadlineMs = DEFAULT_WSL_DEADLINE_MS) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
: ""
|
|
74
|
-
}`;
|
|
75
|
-
reject(error);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
resolve(String(stdout ?? "").replace(/\u0000/g, ""));
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
});
|
|
45
|
+
const [file, ...args] = argv;
|
|
46
|
+
if (file === undefined)
|
|
47
|
+
throw new Error("wsl: runCapture needs a program to run");
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
execFile(file, args, { env: { ...process.env, WSL_UTF8: "1" }, windowsHide: true, signal, encoding: "utf8", timeout: deadlineMs }, (error, stdout, stderr) => {
|
|
50
|
+
if (error) {
|
|
51
|
+
// A denied WSL call is the likeliest failure here and its own message
|
|
52
|
+
// is unhelpful, so name the cause when it is recognisable.
|
|
53
|
+
const detail = `${stdout ?? ""}${stderr ?? ""}`.replace(/\u0000/g, "").trim();
|
|
54
|
+
const denied = /E_ACCESSDENIED/i.test(detail) || error.code === "EACCES";
|
|
55
|
+
const missing = error.code === "ENOENT";
|
|
56
|
+
// The deadline kill arrives as a SIGTERM with no WSL diagnostic, so
|
|
57
|
+
// it must be named for what it is — not left looking like WSL's own
|
|
58
|
+
// failure. An abort is cancellation, not a deadline.
|
|
59
|
+
const stuck = error.killed === true && error.name !== "AbortError";
|
|
60
|
+
error.message = `${file} failed: ${detail || error.message}${denied
|
|
61
|
+
? " (WSL is unreachable from a sandboxed process: DSH's Windows ACL sandbox runs commands under a restricted low-integrity token. Compose the non-sandboxing providers.)"
|
|
62
|
+
: ""}${missing
|
|
63
|
+
? ` (the executable "${file}" could not be started: check \`wslPath\` in this row, and that WSL is installed)`
|
|
64
|
+
: ""}${stuck
|
|
65
|
+
? ` (no completion within ${deadlineMs}ms — the WSL service may be wedged; retry once, then \`wsl.exe --shutdown\` and reopen)`
|
|
66
|
+
: ""}`;
|
|
67
|
+
reject(error);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
resolve(String(stdout ?? "").replace(/\u0000/g, ""));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
82
73
|
}
|
|
83
|
-
|
|
84
74
|
/**
|
|
85
75
|
* List installed distros with `wsl.exe -l -q`.
|
|
76
|
+
*
|
|
86
77
|
* @param options - the `wsl.exe` path and optional cancellation.
|
|
87
78
|
* @returns distro names in WSL's own order, default distro first.
|
|
88
79
|
*/
|
|
89
80
|
export async function listDistros(options = {}) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
const stdout = await runCapture([options.wslPath ?? DEFAULT_WSL_PATH, "-l", "-q"], options.signal);
|
|
82
|
+
return parseDistroList(stdout);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse `wsl.exe -l -q` output into distro names. A pure seam so the CRLF and
|
|
86
|
+
* blank-line shapes of the real output are unit-testable on hosts without
|
|
87
|
+
* `wsl.exe`.
|
|
88
|
+
*
|
|
89
|
+
* @param stdout - the raw `wsl.exe -l -q` stdout.
|
|
90
|
+
* @returns distro names, default distro first.
|
|
91
|
+
*/
|
|
92
|
+
export function parseDistroList(stdout) {
|
|
93
|
+
return String(stdout)
|
|
94
|
+
.split(/\r?\n/)
|
|
95
|
+
.map((line) => line.trim())
|
|
96
|
+
.filter((line) => line.length > 0);
|
|
95
97
|
}
|
|
96
|
-
|
|
97
98
|
/**
|
|
98
99
|
* Resolve the distro to use when none is configured: WSL's own default.
|
|
100
|
+
*
|
|
99
101
|
* @param options - the `wsl.exe` path and optional cancellation.
|
|
100
102
|
* @returns the first entry of {@link listDistros}.
|
|
101
103
|
* @throws when no distro is installed.
|
|
102
104
|
*/
|
|
103
105
|
export async function defaultDistro(options = {}) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
const [first] = await listDistros(options);
|
|
107
|
+
if (first === undefined)
|
|
108
|
+
throw new Error("dsh-plugin-wsl: no WSL distro is installed (`wsl.exe -l -q` returned nothing)");
|
|
109
|
+
return first;
|
|
107
110
|
}
|
|
108
|
-
|
|
109
111
|
/**
|
|
110
112
|
* The Linux home directory of a distro's default user, as a Linux path. `$HOME`
|
|
111
113
|
* is set by `wsl.exe` itself from the distro's default user, so this needs no
|
|
@@ -117,17 +119,25 @@ export async function defaultDistro(options = {}) {
|
|
|
117
119
|
* @throws when the distro cannot be queried or reports no home.
|
|
118
120
|
*/
|
|
119
121
|
export async function linuxHomePath(distro, options = {}) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
const stdout = await runCapture([options.wslPath ?? DEFAULT_WSL_PATH, "-d", distro, "--exec", "sh", "-c", 'printf %s "$HOME"'], options.signal);
|
|
123
|
+
return parseHomePath(distro, stdout);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Validate the distro's reported $HOME. A pure seam: the failure shapes
|
|
127
|
+
* (empty output, a Windows path, mojibake) are unit-testable without
|
|
128
|
+
* `wsl.exe`.
|
|
129
|
+
*
|
|
130
|
+
* @param distro - the distro name, for the error message.
|
|
131
|
+
* @param stdout - the raw `printf %s "$HOME"` output.
|
|
132
|
+
* @returns the Linux home path.
|
|
133
|
+
*/
|
|
134
|
+
export function parseHomePath(distro, stdout) {
|
|
135
|
+
const home = String(stdout).trim();
|
|
136
|
+
if (home.length === 0 || !home.startsWith("/")) {
|
|
137
|
+
throw new Error(`dsh-plugin-wsl: distro "${distro}" reported no usable $HOME (got "${home}")`);
|
|
138
|
+
}
|
|
139
|
+
return home;
|
|
129
140
|
}
|
|
130
|
-
|
|
131
141
|
/**
|
|
132
142
|
* The same home in the world coordinate system, which is what a host-side
|
|
133
143
|
* consumer needs.
|
|
@@ -138,27 +148,8 @@ export async function linuxHomePath(distro, options = {}) {
|
|
|
138
148
|
* @throws when the distro cannot be queried or reports no home.
|
|
139
149
|
*/
|
|
140
150
|
export async function linuxHome(distro, options = {}) {
|
|
141
|
-
|
|
151
|
+
return posixToUnc(distro, await linuxHomePath(distro, options));
|
|
142
152
|
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Resolve a Linux path to its canonical form INSIDE the distro with
|
|
146
|
-
* `readlink -f`.
|
|
147
|
-
*
|
|
148
|
-
* This exists because the UNC share cannot traverse Linux symlinks: the share
|
|
149
|
-
* exposes one as a Windows reparse point whose relative POSIX target has no
|
|
150
|
-
* meaning to Windows path resolution, so `stat`/`realpath`/`read` on a path such
|
|
151
|
-
* as `/etc/os-release` (-> ../usr/lib/os-release) or `/lib` (-> usr/lib) fail
|
|
152
|
-
* with `ENOENT`, even though `lstat` reaches the link. Asking the distro for the
|
|
153
|
-
* real path is the only reliable answer, at one process per distinct path.
|
|
154
|
-
*
|
|
155
|
-
* @param distro - distro name.
|
|
156
|
-
* @param linuxPath - the Linux path to canonicalize.
|
|
157
|
-
* @param options - the `wsl.exe` path and optional cancellation.
|
|
158
|
-
* @returns the canonical absolute Linux path, or undefined when it cannot be
|
|
159
|
-
* determined (missing file, broken link, or a failed query).
|
|
160
|
-
*/
|
|
161
|
-
|
|
162
153
|
/**
|
|
163
154
|
* Shells known to accept the combined POSIX `-lc` login-plus-command form.
|
|
164
155
|
*
|
|
@@ -168,7 +159,6 @@ export async function linuxHome(distro, options = {}) {
|
|
|
168
159
|
* — it just does not get login-shell semantics.
|
|
169
160
|
*/
|
|
170
161
|
const POSIX_LOGIN_SHELLS = new Set(["sh", "bash", "dash", "zsh", "ksh", "ksh93", "mksh", "ash", "busybox"]);
|
|
171
|
-
|
|
172
162
|
/**
|
|
173
163
|
* The argv flags that make `shell` run `command`, with login semantics when the
|
|
174
164
|
* shell supports them.
|
|
@@ -177,6 +167,12 @@ const POSIX_LOGIN_SHELLS = new Set(["sh", "bash", "dash", "zsh", "ksh", "ksh93",
|
|
|
177
167
|
* @param login - whether login-shell semantics were requested.
|
|
178
168
|
* @returns the flags to place before the command string.
|
|
179
169
|
*/
|
|
170
|
+
export function shellArgs(shellPath, login) {
|
|
171
|
+
const name = String(shellPath).slice(String(shellPath).lastIndexOf("/") + 1).toLowerCase();
|
|
172
|
+
if (!POSIX_LOGIN_SHELLS.has(name))
|
|
173
|
+
return ["-c"];
|
|
174
|
+
return login ? ["-lc"] : ["-c"];
|
|
175
|
+
}
|
|
180
176
|
/**
|
|
181
177
|
* The working directory `wsl.exe` could not enter, when its relay said so.
|
|
182
178
|
*
|
|
@@ -190,22 +186,15 @@ const POSIX_LOGIN_SHELLS = new Set(["sh", "bash", "dash", "zsh", "ksh", "ksh93",
|
|
|
190
186
|
* run into an error. Verified against `wsl.exe -d <distro> --cd /missing --exec sh`:
|
|
191
187
|
* exit 0, `hi` on stdout, this shape on stderr.
|
|
192
188
|
*
|
|
193
|
-
* @param stdout - a command's captured stdout.
|
|
194
|
-
* @param stderr - its captured stderr.
|
|
189
|
+
* @param stdout - a command's captured stdout; absent reads as empty.
|
|
190
|
+
* @param stderr - its captured stderr; absent reads as empty.
|
|
195
191
|
* @returns the directory that could not be entered, or undefined.
|
|
196
192
|
*/
|
|
197
193
|
export function workdirFailure(stdout, stderr) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function shellArgs(shellPath, login) {
|
|
204
|
-
const name = String(shellPath).slice(String(shellPath).lastIndexOf("/") + 1).toLowerCase();
|
|
205
|
-
if (!POSIX_LOGIN_SHELLS.has(name)) return ["-c"];
|
|
206
|
-
return login ? ["-lc"] : ["-c"];
|
|
194
|
+
const text = `${stdout ?? ""}\n${stderr ?? ""}`.replace(/\0/g, "");
|
|
195
|
+
const match = /<3>WSL[^\n]*CreateProcessCommon[^\n]*chdir\(([^)]*)\) failed/.exec(text);
|
|
196
|
+
return match?.[1];
|
|
207
197
|
}
|
|
208
|
-
|
|
209
198
|
/**
|
|
210
199
|
* Shells whose `-l` flag means a login shell.
|
|
211
200
|
*
|
|
@@ -215,7 +204,6 @@ export function shellArgs(shellPath, login) {
|
|
|
215
204
|
* nushell/xonsh use long flags, so they deliberately get nothing.
|
|
216
205
|
*/
|
|
217
206
|
const LOGIN_FLAG_SHELLS = new Set([...POSIX_LOGIN_SHELLS, "fish"]);
|
|
218
|
-
|
|
219
207
|
/**
|
|
220
208
|
* The flags that make an interactive shell a login shell.
|
|
221
209
|
*
|
|
@@ -228,10 +216,9 @@ const LOGIN_FLAG_SHELLS = new Set([...POSIX_LOGIN_SHELLS, "fish"]);
|
|
|
228
216
|
* @returns `["-l"]`, or an empty array for a shell whose login flag differs.
|
|
229
217
|
*/
|
|
230
218
|
export function interactiveShellArgs(shellPath, login) {
|
|
231
|
-
|
|
232
|
-
|
|
219
|
+
const name = String(shellPath).slice(String(shellPath).lastIndexOf("/") + 1).toLowerCase();
|
|
220
|
+
return login && LOGIN_FLAG_SHELLS.has(name) ? ["-l"] : [];
|
|
233
221
|
}
|
|
234
|
-
|
|
235
222
|
/**
|
|
236
223
|
* The `wsl.exe` argv that opens one interactive shell inside a distro.
|
|
237
224
|
*
|
|
@@ -250,19 +237,17 @@ export function interactiveShellArgs(shellPath, login) {
|
|
|
250
237
|
* rejects an empty `--cd` argument whereas its own default starts in the
|
|
251
238
|
* distro user's home.
|
|
252
239
|
*
|
|
253
|
-
* @param options - the
|
|
240
|
+
* @param options - the launch description.
|
|
254
241
|
* @returns the argv to spawn on the host.
|
|
255
242
|
*/
|
|
256
243
|
export function wslTerminalArgv(options) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return [wslPath, ...(distro.length > 0 ? ["-d", distro] : []), ...directory, ...shell];
|
|
244
|
+
const { wslPath = DEFAULT_WSL_PATH, distro = "", shellPath, args = [], linuxCwd, login = true } = options;
|
|
245
|
+
const directory = typeof linuxCwd === "string" && linuxCwd.length > 0 ? ["--cd", linuxCwd] : [];
|
|
246
|
+
const shell = typeof shellPath === "string" && shellPath.length > 0
|
|
247
|
+
? ["--exec", shellPath, ...interactiveShellArgs(shellPath, login), ...args]
|
|
248
|
+
: [];
|
|
249
|
+
return [wslPath, ...(distro.length > 0 ? ["-d", distro] : []), ...directory, ...shell];
|
|
264
250
|
}
|
|
265
|
-
|
|
266
251
|
/**
|
|
267
252
|
* Managed facts of the `DSH_*` namespace that carry a Windows path. WSL's
|
|
268
253
|
* `WSLENV` `/p` flag translates them on the way into the distro, so a command
|
|
@@ -270,7 +255,6 @@ export function wslTerminalArgv(options) {
|
|
|
270
255
|
* absent: it is already a POSIX path, and translating it would corrupt it.
|
|
271
256
|
*/
|
|
272
257
|
const PATH_TRANSLATED_ENV = new Set(["DSH_HOME", "DSH_PROFILE_DIR"]);
|
|
273
|
-
|
|
274
258
|
/**
|
|
275
259
|
* Render WSLENV's value from the env names a call wants the distro to import.
|
|
276
260
|
*
|
|
@@ -281,21 +265,8 @@ const PATH_TRANSLATED_ENV = new Set(["DSH_HOME", "DSH_PROFILE_DIR"]);
|
|
|
281
265
|
* @returns the `:`-joined WSLENV value.
|
|
282
266
|
*/
|
|
283
267
|
export function wslEnvValue(names) {
|
|
284
|
-
|
|
268
|
+
return names.map((name) => (PATH_TRANSLATED_ENV.has(name) ? `${name}/p` : name)).join(":");
|
|
285
269
|
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Resolve the shell a distro's user actually gets, rather than assuming bash.
|
|
289
|
-
*
|
|
290
|
-
* The authoritative source is the login shell field of the user's passwd entry;
|
|
291
|
-
* `$SHELL` (which `wsl.exe` derives from it) is the fallback for distros without
|
|
292
|
-
* `getent`, and bash is the last resort. The passwd field is preferred because a
|
|
293
|
-
* user can export a different `SHELL` without changing their login shell.
|
|
294
|
-
*
|
|
295
|
-
* @param distro - distro name.
|
|
296
|
-
* @param options - the `wsl.exe` path and optional cancellation.
|
|
297
|
-
* @returns an absolute path to the shell inside the distro.
|
|
298
|
-
*/
|
|
299
270
|
/**
|
|
300
271
|
* The `WSL_E_*` code inside `wsl.exe`'s own output, when it reported one.
|
|
301
272
|
*
|
|
@@ -306,49 +277,67 @@ export function wslEnvValue(names) {
|
|
|
306
277
|
* `wsl.exe -d nosuchdistro --exec …` exits 255 with an empty stderr and
|
|
307
278
|
* `Wsl/Service/WSL_E_DISTRO_NOT_FOUND` on stdout.
|
|
308
279
|
*
|
|
309
|
-
* @param stdout - a command's captured stdout.
|
|
310
|
-
* @param stderr - its captured stderr.
|
|
280
|
+
* @param stdout - a command's captured stdout; absent reads as empty.
|
|
281
|
+
* @param stderr - its captured stderr; absent reads as empty.
|
|
311
282
|
* @returns the code, for example `WSL_E_DISTRO_NOT_FOUND`, or undefined.
|
|
312
283
|
*/
|
|
313
284
|
export function wslErrorCode(stdout, stderr) {
|
|
314
|
-
|
|
315
|
-
|
|
285
|
+
const text = `${stdout ?? ""}\n${stderr ?? ""}`.replace(/\0/g, "");
|
|
286
|
+
return /WSL_E_[A-Z0-9_]+/.exec(text)?.[0];
|
|
316
287
|
}
|
|
317
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Resolve the shell a distro's user actually gets, rather than assuming bash.
|
|
290
|
+
*
|
|
291
|
+
* The authoritative source is the login shell field of the user's passwd entry;
|
|
292
|
+
* `$SHELL` (which `wsl.exe` derives from it) is the fallback for distros without
|
|
293
|
+
* `getent`, and bash is the last resort. The passwd field is preferred because a
|
|
294
|
+
* user can export a different `SHELL` without changing their login shell.
|
|
295
|
+
*
|
|
296
|
+
* @param distro - distro name.
|
|
297
|
+
* @param options - the `wsl.exe` path and optional cancellation.
|
|
298
|
+
* @returns an absolute path to the shell inside the distro, or undefined when
|
|
299
|
+
* both probes failed.
|
|
300
|
+
*/
|
|
318
301
|
export async function defaultShell(distro, options = {}) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
302
|
+
const wslPath = options.wslPath ?? DEFAULT_WSL_PATH;
|
|
303
|
+
/**
|
|
304
|
+
* Ask the distro one `sh -c` question; only absolute paths count as answers.
|
|
305
|
+
*
|
|
306
|
+
* @param script - the shell snippet to run.
|
|
307
|
+
* @returns the trimmed answer, or undefined.
|
|
308
|
+
*/
|
|
309
|
+
const ask = async (script) => {
|
|
310
|
+
try {
|
|
311
|
+
const value = (await runCapture([wslPath, "-d", distro, "--exec", "sh", "-c", script], options.signal)).trim();
|
|
312
|
+
return value.startsWith("/") ? value : undefined;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
// No "bash" placeholder: a failed probe (a cold distro, a flaky first
|
|
319
|
+
// `wsl.exe` call) used to be pinned as the literal shell for the
|
|
320
|
+
// executor's lifetime. `undefined` tells the caller both answers failed;
|
|
321
|
+
// it may retry later or fall back itself.
|
|
322
|
+
return ((await ask('getent passwd "$(id -u)" | cut -d: -f7')) ??
|
|
323
|
+
(await ask('printf %s "$SHELL"')) ??
|
|
324
|
+
undefined);
|
|
337
325
|
}
|
|
338
|
-
|
|
339
326
|
/**
|
|
340
327
|
* Run one command inside a distro and return raw stdout. Used by callers that
|
|
341
328
|
* want Linux semantics instead of the UNC share's.
|
|
342
329
|
*
|
|
343
330
|
* @param distro - distro name.
|
|
344
331
|
* @param command - a shell command string.
|
|
345
|
-
* @param options - the `wsl.exe` path, an explicit `shell`, and optional
|
|
332
|
+
* @param options - the `wsl.exe` path, an explicit `shell`, and optional
|
|
333
|
+
* cancellation.
|
|
346
334
|
* @returns captured stdout.
|
|
335
|
+
* @throws when neither the caller nor the distro names a usable shell.
|
|
347
336
|
*/
|
|
348
337
|
export async function runInDistro(distro, command, options = {}) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
338
|
+
const shell = options.shell ?? (await defaultShell(distro, options));
|
|
339
|
+
if (shell === undefined) {
|
|
340
|
+
throw new Error(`dsh-plugin-wsl: distro "${distro}" named no usable shell (passwd probe and $SHELL both failed)`);
|
|
341
|
+
}
|
|
342
|
+
return runCapture([options.wslPath ?? DEFAULT_WSL_PATH, "-d", distro, "--exec", shell, ...shellArgs(shell, options.loginShell !== false), command], options.signal);
|
|
354
343
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wsl-env",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "让 DeepSeek Harness 以 WSL 子系统作为会话的执行环境:shell、文件读写与命令执行均在子系统内完成,目录选择器与 GUI 终端同样定位到子系统路径,并通过子系统内的 bubblewrap 对命令进行沙箱隔离",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -11,32 +11,72 @@
|
|
|
11
11
|
"windows"
|
|
12
12
|
],
|
|
13
13
|
"type": "module",
|
|
14
|
+
"types": "./lib/shell.d.ts",
|
|
14
15
|
"main": "lib/shell.js",
|
|
15
16
|
"exports": {
|
|
16
|
-
".":
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/shell.d.ts",
|
|
19
|
+
"default": "./lib/shell.js"
|
|
20
|
+
},
|
|
21
|
+
"./agent": {
|
|
22
|
+
"types": "./lib/agent.d.ts",
|
|
23
|
+
"default": "./lib/agent.js"
|
|
24
|
+
},
|
|
25
|
+
"./auto-preset": {
|
|
26
|
+
"types": "./lib/auto-preset.d.ts",
|
|
27
|
+
"default": "./lib/auto-preset.js"
|
|
28
|
+
},
|
|
19
29
|
"./auto-preset/locale/en.json": "./locale/auto-preset/en.json",
|
|
20
30
|
"./auto-preset/locale/zh.json": "./locale/auto-preset/zh.json",
|
|
21
|
-
"./file-reference-wsl":
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
"./file-reference-wsl": {
|
|
32
|
+
"types": "./lib/file-reference-wsl.d.ts",
|
|
33
|
+
"default": "./lib/file-reference-wsl.js"
|
|
34
|
+
},
|
|
35
|
+
"./fs": {
|
|
36
|
+
"types": "./lib/fs.d.ts",
|
|
37
|
+
"default": "./lib/fs.js"
|
|
38
|
+
},
|
|
39
|
+
"./fs-routing": {
|
|
40
|
+
"types": "./lib/fs-routing.d.ts",
|
|
41
|
+
"default": "./lib/fs-routing.js"
|
|
42
|
+
},
|
|
24
43
|
"./locale/en.json": "./locale/en.json",
|
|
25
44
|
"./locale/zh.json": "./locale/zh.json",
|
|
26
45
|
"./package.json": "./package.json",
|
|
27
|
-
"./paths":
|
|
28
|
-
|
|
46
|
+
"./paths": {
|
|
47
|
+
"types": "./lib/paths.d.ts",
|
|
48
|
+
"default": "./lib/paths.js"
|
|
49
|
+
},
|
|
50
|
+
"./picker": {
|
|
51
|
+
"types": "./lib/picker.d.ts",
|
|
52
|
+
"default": "./lib/picker.js"
|
|
53
|
+
},
|
|
29
54
|
"./picker/locale/en.json": "./locale/picker/en.json",
|
|
30
55
|
"./picker/locale/zh.json": "./locale/picker/zh.json",
|
|
31
|
-
"./shell":
|
|
32
|
-
|
|
56
|
+
"./shell": {
|
|
57
|
+
"types": "./lib/shell.d.ts",
|
|
58
|
+
"default": "./lib/shell.js"
|
|
59
|
+
},
|
|
60
|
+
"./shell-env": {
|
|
61
|
+
"types": "./lib/shell-env.d.ts",
|
|
62
|
+
"default": "./lib/shell-env.js"
|
|
63
|
+
},
|
|
33
64
|
"./shell-env/locale/en.json": "./locale/shell-env/en.json",
|
|
34
65
|
"./shell-env/locale/zh.json": "./locale/shell-env/zh.json",
|
|
35
|
-
"./subprocess":
|
|
66
|
+
"./subprocess": {
|
|
67
|
+
"types": "./lib/subprocess.d.ts",
|
|
68
|
+
"default": "./lib/subprocess.js"
|
|
69
|
+
},
|
|
36
70
|
"./subprocess/locale/en.json": "./locale/subprocess/en.json",
|
|
37
71
|
"./subprocess/locale/zh.json": "./locale/subprocess/zh.json",
|
|
38
|
-
"./workspace-files-wsl":
|
|
39
|
-
|
|
72
|
+
"./workspace-files-wsl": {
|
|
73
|
+
"types": "./lib/workspace-files-wsl.d.ts",
|
|
74
|
+
"default": "./lib/workspace-files-wsl.js"
|
|
75
|
+
},
|
|
76
|
+
"./wsl": {
|
|
77
|
+
"types": "./lib/wsl.d.ts",
|
|
78
|
+
"default": "./lib/wsl.js"
|
|
79
|
+
}
|
|
40
80
|
},
|
|
41
81
|
"dsh": {
|
|
42
82
|
"bundle": {
|
|
@@ -45,6 +85,7 @@
|
|
|
45
85
|
},
|
|
46
86
|
"files": [
|
|
47
87
|
"lib/**/*.js",
|
|
88
|
+
"lib/**/*.d.ts",
|
|
48
89
|
"agent/*.sh",
|
|
49
90
|
"locale/**/*.json",
|
|
50
91
|
"cordis.patch.yml",
|
|
@@ -66,12 +107,15 @@
|
|
|
66
107
|
"scripts/bootstrap.sh"
|
|
67
108
|
],
|
|
68
109
|
"scripts": {
|
|
69
|
-
"test": "
|
|
70
|
-
"
|
|
110
|
+
"test": "pnpm run lint:style && pnpm run test:syntax && pnpm run lint:build && pnpm run lint:types && pnpm run test:unit",
|
|
111
|
+
"lint:types": "tsc --noEmit",
|
|
112
|
+
"build": "node scripts/build.mjs",
|
|
113
|
+
"lint:build": "node test/build-freshness.mjs",
|
|
114
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 test/agent-exec.test.mjs test/agent-fs-script.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/fs-substrate.test.mjs test/fs.boot.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/fsio-agent.test.mjs test/fsio-text.test.mjs test/git-route.test.mjs test/listing.test.mjs test/paths.test.mjs test/ports.test.mjs test/preset-choice.test.mjs test/probe-cache.test.mjs test/sandbox.test.mjs test/search-exec.test.mjs test/search-route.test.mjs test/fs.boot.test.mjs test/shell.test.mjs test/terminal-activity.test.mjs test/terminal.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs",
|
|
71
115
|
"test:syntax": "node test/syntax.mjs",
|
|
72
|
-
"test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/file-reference-wsl.test.mjs test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/agent-fs-script.test.mjs test/fsio-agent.test.mjs test/fs-substrate.test.mjs test/agent-exec.test.mjs test/ports.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/git-route.test.mjs test/fsio-text.test.mjs test/search-route.test.mjs test/search-exec.test.mjs test/terminal-activity.test.mjs",
|
|
116
|
+
"test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/agent-fs-script.test.mjs test/fsio-agent.test.mjs test/fs-substrate.test.mjs test/agent-exec.test.mjs test/ports.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/git-route.test.mjs test/fsio-text.test.mjs test/search-route.test.mjs test/search-exec.test.mjs test/terminal-activity.test.mjs",
|
|
73
117
|
"lint:style": "node test/style.mjs",
|
|
74
|
-
"prepublishOnly": "
|
|
118
|
+
"prepublishOnly": "pnpm test",
|
|
75
119
|
"probe": "bash test/probe/run.sh",
|
|
76
120
|
"probe:missing-wsl": "bash test/probe/missing-wsl.sh",
|
|
77
121
|
"probe:mode": "bash test/probe/mode.sh",
|
|
@@ -87,8 +131,9 @@
|
|
|
87
131
|
"probe:exec": "bash test/probe/exec.sh",
|
|
88
132
|
"bootstrap": "bash scripts/bootstrap.sh"
|
|
89
133
|
},
|
|
134
|
+
"packageManager": "pnpm@11.7.0",
|
|
90
135
|
"engines": {
|
|
91
|
-
"node": ">=
|
|
136
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
92
137
|
},
|
|
93
138
|
"author": {
|
|
94
139
|
"name": "Andy Zhao",
|
|
@@ -122,7 +167,10 @@
|
|
|
122
167
|
"@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
|
|
123
168
|
"@deepseek-ai/dsh-subprocess-local": "0.2.0-rc.2",
|
|
124
169
|
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
125
|
-
"js-yaml": "4.
|
|
170
|
+
"@types/js-yaml": "^4.0.9",
|
|
171
|
+
"@types/node": "~22.20.5",
|
|
172
|
+
"js-yaml": "4.3.2",
|
|
173
|
+
"typescript": "~6.0.3"
|
|
126
174
|
},
|
|
127
175
|
"peerDependencies": {
|
|
128
176
|
"@deepseek-ai/cordis": "~4.0.4",
|