dsh-wsl-desktop 0.7.2 → 0.7.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.en.md +1 -0
- package/README.md +1 -0
- package/lib/client.js +21 -0
- package/lib/host/http.js +110 -0
- package/lib/host/presets.js +97 -0
- package/lib/host/state.js +58 -0
- package/lib/index.js +100 -276
- package/lib/wsl/distros.js +190 -0
- package/lib/wsl/preset.js +7 -0
- package/lib/wsl/proc.js +182 -0
- package/lib/wsl/shell.js +2 -1
- package/lib/wsl/subprocess.js +2 -1
- package/lib/wsl/world.js +4 -341
- package/package.json +1 -1
package/lib/wsl/world.js
CHANGED
|
@@ -9,23 +9,12 @@
|
|
|
9
9
|
* @module dsh-wsl-desktop/wsl/world
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { DISTRO_NAME, isWindowsPathShaped, LINUX_USER, joinWslUnc, mntToWindowsPath, parseWslUnc, shellQuote, windowsToMntPath } from './paths.js'
|
|
12
|
+
import { execFileSync, spawn } from 'node:child_process'
|
|
13
|
+
import { isWindowsPathShaped, LINUX_USER, joinWslUnc, mntToWindowsPath, parseWslUnc, shellQuote, windowsToMntPath } from './paths.js'
|
|
15
14
|
import { WslDomainError } from './errors.js'
|
|
15
|
+
import { ENV_OVERRIDES, WSL_CHILD_ENV, assertDistroName, decodeWslOutput, execFileAsync } from './proc.js'
|
|
16
|
+
import { defaultDistro } from './distros.js'
|
|
16
17
|
|
|
17
|
-
const execFileAsync = promisify(execFile)
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Refuse option values that cannot be safe single argv tokens for wsl.exe
|
|
21
|
-
* (-d/-u), independent of wsl.exe's external tokenization rules.
|
|
22
|
-
* @param {string} distro - the distribution name about to be spawned with.
|
|
23
|
-
*/
|
|
24
|
-
function assertDistroName(distro) {
|
|
25
|
-
if (typeof distro !== 'string' || !DISTRO_NAME.test(distro)) {
|
|
26
|
-
throw new Error(`wsl: 非法的发行版名 ${JSON.stringify(distro ?? null)}`)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
18
|
|
|
30
19
|
/**
|
|
31
20
|
* Refuse usernames that cannot be safe single argv tokens for wsl.exe -u.
|
|
@@ -37,335 +26,9 @@ function assertUserName(username) {
|
|
|
37
26
|
}
|
|
38
27
|
}
|
|
39
28
|
|
|
40
|
-
/** Short ceiling for the registry and `wsl.exe` discovery calls. */
|
|
41
|
-
const DISCOVERY_TIMEOUT_MS = 10_000
|
|
42
|
-
|
|
43
29
|
/** Per-stream capture ceiling; a runaway command must not buffer without bound. */
|
|
44
30
|
const MAX_STREAM_BYTES = 1024 * 1024
|
|
45
31
|
|
|
46
|
-
/** Windows registry key holding the WSL distribution registrations. */
|
|
47
|
-
const LXSS_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss'
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The environment every `wsl.exe` child must be started with, wherever it is
|
|
51
|
-
* spawned from.
|
|
52
|
-
*
|
|
53
|
-
* `WSL_UTF8` is load-bearing, not cosmetic: wsl.exe reads it from its OWN
|
|
54
|
-
* environment to decide the encoding of its diagnostics. Without it a missing
|
|
55
|
-
* distribution emits 258 bytes of UTF-16LE, which is what forced the decoder to
|
|
56
|
-
* guess between encodings — and every guess traded one failure class for another.
|
|
57
|
-
* With it the same message arrives as 223 bytes of UTF-8.
|
|
58
|
-
*
|
|
59
|
-
* It lives here, as one exported constant, because there are four spawn sites
|
|
60
|
-
* (the probe path, the shell executor, the subprocess provider's spawn and its
|
|
61
|
-
* terminal allocation) and a site that forgets it silently gets unreadable
|
|
62
|
-
* messages again. Linux-side output is unaffected; only wsl.exe's own text changes.
|
|
63
|
-
*/
|
|
64
|
-
export const WSL_CHILD_ENV = { WSL_UTF8: '1' }
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Environment overrides that keep command output model-readable.
|
|
68
|
-
*
|
|
69
|
-
* ONE owner, shared with the shell executor (which spreads it into every
|
|
70
|
-
* command it runs) — an override added to one copy and not the other used to be
|
|
71
|
-
* a silent divergence two modules could not see. Frozen so a shared object stays
|
|
72
|
-
* shared on purpose: an accidental write now throws instead of leaking into the
|
|
73
|
-
* other consumer's environment.
|
|
74
|
-
*/
|
|
75
|
-
export const ENV_OVERRIDES = Object.freeze({ ...WSL_CHILD_ENV, NO_COLOR: '1', TERM: 'dumb', PAGER: 'cat', GIT_PAGER: 'cat' })
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Decode `wsl.exe` output.
|
|
79
|
-
*
|
|
80
|
-
* Every `wsl.exe` this plugin spawns is asked for UTF-8 (`WSL_UTF8`, see
|
|
81
|
-
* {@link ENV_OVERRIDES}), so this is normally a plain UTF-8 decode — including the
|
|
82
|
-
* diagnostics wsl.exe prints for its OWN failures, which are UTF-16LE without that
|
|
83
|
-
* variable and were the whole reason this decoder used to guess between encodings.
|
|
84
|
-
*
|
|
85
|
-
* The fallback covers two things a WSL build that ignores the variable can send:
|
|
86
|
-
* an explicit UTF-16LE BOM, and UTF-16LE whose code units are below U+0100 (their
|
|
87
|
-
* high bytes are NUL — Latin-1 counts, not only ASCII).
|
|
88
|
-
*
|
|
89
|
-
* NUL-framed DATA is the awkward case, and a RATIO alone cannot settle it:
|
|
90
|
-
* measured, `find . -print0` scores 0.500, the real Chinese diagnostic 0.636, and
|
|
91
|
-
* `ls --zero` with single-character names reaches 1.000 — where the bytes ARE
|
|
92
|
-
* `Buffer.from('abc', 'utf16le')`, byte for byte. Hence the layering above: a
|
|
93
|
-
* partial interleave must also fail UTF-8 validity, which drops the framing
|
|
94
|
-
* payloads (valid UTF-8) and keeps the diagnostic (invalid); an all-odd
|
|
95
|
-
* interleave is honoured regardless, because that is the shape `listDistros`
|
|
96
|
-
* depends on when a build ignores the switch.
|
|
97
|
-
*
|
|
98
|
-
* The irreducible residue is exactly that byte-identical pair: no test can
|
|
99
|
-
* separate two readings of the same bytes. A consumer whose protocol frames with
|
|
100
|
-
* NUL must therefore opt out with `raw` — `listLinuxDir` does, and `execInWsl`,
|
|
101
|
-
* the only consumer of ARBITRARY command output, takes `raw` for the same reason.
|
|
102
|
-
*
|
|
103
|
-
* Three attempts at a smarter heuristic (interleave ratios, replacement-character
|
|
104
|
-
* densities, UTF-8 validity) each traded one failure class for another: every
|
|
105
|
-
* listing empty, then a >1 MiB CJK stream mojibake, then valid buffers flipped.
|
|
106
|
-
* Guessing between encodings was the wrong shape; asking for UTF-8 removed the
|
|
107
|
-
* need to guess, and this fallback covers the builds that predate the variable.
|
|
108
|
-
* @param {Buffer} buffer - raw captured stream.
|
|
109
|
-
* @returns {string} the decoded text.
|
|
110
|
-
*/
|
|
111
|
-
export function decodeWslOutput(buffer) {
|
|
112
|
-
return looksUtf16le(buffer) ? buffer.toString('utf16le') : buffer.toString('utf8')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Whether a buffer is UTF-16LE whose code units are mostly below U+0100 — the
|
|
117
|
-
* fallback shape.
|
|
118
|
-
*
|
|
119
|
-
* The class counted is NOT "ASCII": the pick is `oddNuls * 2 >= pairs`, i.e. at
|
|
120
|
-
* least half the CODE UNITS must have a NUL high byte, and EVERY code unit below
|
|
121
|
-
* U+0100 encodes with one — Latin-1 included. Measured: a UTF-16LE buffer of
|
|
122
|
-
* U+00E9 and U+00FF (e-acute, y-diaeresis) is counted exactly like ASCII and
|
|
123
|
-
* decodes as UTF-16LE. For a pure-ASCII text the two readings coincide, which is
|
|
124
|
-
* why this used to be described as an "ASCII share"; for a Latin-1 text they do
|
|
125
|
-
* not, and the rate below is the binomial tail of the share of code units BELOW
|
|
126
|
-
* U+0100 — 100% for a text entirely below it, about 60% at half, about 8% at 35%
|
|
127
|
-
* (two independent generators measured 62.2%/52.8% and 8.0%/6.8% on those same
|
|
128
|
-
* anchors, which is the point: quote the mechanism, not a rate). The real wsl.exe
|
|
129
|
-
* diagnostics captured here happen to be majority-ASCII (0.636-0.662) and are
|
|
130
|
-
* caught; a message that is mostly CJK is not, and no threshold fixes that
|
|
131
|
-
* without also admitting NUL-framed data. That is why the switch is requested at
|
|
132
|
-
* the source instead.
|
|
133
|
-
*
|
|
134
|
-
* The test is the INTERLEAVED NUL pattern, not "contains a NUL": a Linux-side
|
|
135
|
-
* producer may emit NUL bytes as DATA (`printf 'a\\0b'`), and a bare NUL test
|
|
136
|
-
* decoded that as UTF-16LE and silently dropped the byte after it. Three code
|
|
137
|
-
* units minimum, or the pattern is a coincidence — `'a\\0b'` is one pair and
|
|
138
|
-
* satisfies any ratio.
|
|
139
|
-
*
|
|
140
|
-
* This deliberately does NOT try to recognise UTF-16LE whose code units are
|
|
141
|
-
* ABOVE U+00FF (there the high byte is not NUL, so nothing distinguishes it from
|
|
142
|
-
* text) — with ONE exception, the explicit BOM below, which is a declaration
|
|
143
|
-
* rather than a statistic. Everything else is handled at the SOURCE instead: every spawn asks
|
|
144
|
-
* wsl.exe for UTF-8 (WSL_UTF8), so a Chinese-locale diagnostic arrives as UTF-8
|
|
145
|
-
* rather than as UTF-16LE whose only distinguishing feature is a
|
|
146
|
-
* replacement-character count. Three heuristics were written for that case and
|
|
147
|
-
* each traded one failure class for another.
|
|
148
|
-
* @param {Buffer} buffer - raw captured stream.
|
|
149
|
-
* @returns {boolean} true when the buffer looks like ASCII-heavy UTF-16LE.
|
|
150
|
-
*/
|
|
151
|
-
function looksUtf16le(buffer) {
|
|
152
|
-
// An explicit BOM is a DECLARATION, not a guess: a file saved by a Windows
|
|
153
|
-
// editor starts FF FE, and decoding it as UTF-8 is mojibake for certain. It is
|
|
154
|
-
// checked BEFORE the parity guard on purpose, so a truncated BOM'd buffer
|
|
155
|
-
// (FF FE 41) is still read as UTF-16LE and loses its trailing byte, rather than
|
|
156
|
-
// being read as UTF-8 — the declaration wins over the shape.
|
|
157
|
-
if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) return true
|
|
158
|
-
// A UTF-16LE stream is always an EVEN number of bytes, so an odd-length buffer
|
|
159
|
-
// cannot be one — and `toString('utf16le')` would silently DROP the trailing byte
|
|
160
|
-
// (measured: `printf 'a\\0b\\0c\\0d'` is 7 bytes and decoded to "abc").
|
|
161
|
-
if (buffer.length % 2 !== 0) return false
|
|
162
|
-
const pairs = Math.floor(buffer.length / 2)
|
|
163
|
-
if (pairs < 3) return false
|
|
164
|
-
let oddNuls = 0
|
|
165
|
-
let evenNuls = 0
|
|
166
|
-
for (let index = 0; index < pairs * 2; index += 2) {
|
|
167
|
-
if (buffer[index] === 0) evenNuls += 1
|
|
168
|
-
if (buffer[index + 1] === 0) oddNuls += 1
|
|
169
|
-
}
|
|
170
|
-
// The predicate is EXACTLY `oddNuls > evenNuls && oddNuls * 2 >= pairs`. An
|
|
171
|
-
// earlier version of this comment called it "a majority of the odd positions and
|
|
172
|
-
// nothing else", which was wrong twice: the first conjunct is real — a buffer
|
|
173
|
-
// whose code units are mostly U+xx00 has as many even NULs as odd ones and is
|
|
174
|
-
// rejected, e.g. Buffer.from([0,0,0,0,0,1,0,2,0x61,0,0x62,0]) — and where
|
|
175
|
-
// evenNuls is 0 it fires at EXACTLY one half, not a majority.
|
|
176
|
-
//
|
|
177
|
-
// The populations OVERLAP, so this is a heuristic and not a proof — measured:
|
|
178
|
-
// real UTF-16LE diagnostics 0.636 - 0.662 -> fires, correctly
|
|
179
|
-
// real 'wsl.exe -l -q' UTF-16LE 1.000 -> fires (listDistros needs it)
|
|
180
|
-
// real 'find . -print0', long names 0.026 - 0.415 -> does not fire, correctly
|
|
181
|
-
// one-char entries './a\0./b\0./c\0' 0.500 EXACTLY -> FIRES, WRONGLY
|
|
182
|
-
// 'ls --zero', one-char names 1.000 -> fires; byte-identical
|
|
183
|
-
// to UTF-16LE, inherent
|
|
184
|
-
// (A real capture scored 0.529 on that one-char shape only because UTF-16LE text
|
|
185
|
-
// was mixed into the stream; the pure listing is 0.5000 and still fires.)
|
|
186
|
-
// That shape is the one that breaks: it puts a NUL on most odd byte positions, and
|
|
187
|
-
// no threshold separates it from a diagnostic at 0.636 without dropping the
|
|
188
|
-
// diagnostics that score below it. Three refinements
|
|
189
|
-
// were tried and ALL were falsified, so none is kept:
|
|
190
|
-
// - a 0.75 threshold excluded the real diagnostics themselves;
|
|
191
|
-
// - "a partial interleave must also fail UTF-8 validity" excluded UTF-16LE whose
|
|
192
|
-
// bytes ARE valid UTF-8 (Buffer.from('abc不','utf16le')) while still admitting
|
|
193
|
-
// NUL-framed bytes that are not (printf 'a\\0b\\0c\\xff');
|
|
194
|
-
// - a 0.6 threshold fits exactly the sample above — which is the sample it was
|
|
195
|
-
// measured on, while a diagnostic that is mostly non-ASCII scores lower.
|
|
196
|
-
// A ratio cannot prove an encoding. The OPT-OUTS can, and they are the answer:
|
|
197
|
-
// listLinuxDir and execInWsl(raw:true) never reach this test at all.
|
|
198
|
-
return oddNuls > evenNuls && oddNuls * 2 >= pairs
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* List the installed WSL distributions.
|
|
203
|
-
*
|
|
204
|
-
* The configured path is spawned here too: this call is a probe like any other,
|
|
205
|
-
* and a deployment that set the path because `wsl.exe` is not on PATH would get
|
|
206
|
-
* an empty discovery while its real spawns worked.
|
|
207
|
-
* @param {{ wslPath?: string }} [options] - the configured `wsl.exe` path.
|
|
208
|
-
* @returns {Promise<string[]>} distribution names in `wsl.exe` order.
|
|
209
|
-
*/
|
|
210
|
-
export async function listDistros({ wslPath = 'wsl.exe' } = {}) {
|
|
211
|
-
let stdout
|
|
212
|
-
try {
|
|
213
|
-
({ stdout } = await execFileAsync(wslPath, ['-l', '-q'], {
|
|
214
|
-
encoding: 'buffer',
|
|
215
|
-
timeout: DISCOVERY_TIMEOUT_MS,
|
|
216
|
-
// The discovery call is a wsl.exe spawn like any other: without this its
|
|
217
|
-
// output is UTF-16LE and only the fallback probe reads it.
|
|
218
|
-
env: { ...process.env, ...WSL_CHILD_ENV },
|
|
219
|
-
}))
|
|
220
|
-
} catch (error) {
|
|
221
|
-
// The spawn code is the only thing that distinguishes "wsl.exe is not
|
|
222
|
-
// installed" (ENOENT) from a plugin fault; dropping it would turn the most
|
|
223
|
-
// common first-run state into `internal`. The message stays host-side.
|
|
224
|
-
const failure = new Error(`无法列出 WSL 发行版:${error instanceof Error ? error.message : String(error)}`, { cause: error })
|
|
225
|
-
if (typeof error?.code === 'string') failure.code = error.code
|
|
226
|
-
throw failure
|
|
227
|
-
}
|
|
228
|
-
return decodeWslOutput(stdout)
|
|
229
|
-
.split(/\r?\n/)
|
|
230
|
-
.map((line) => line.replace(/\0/g, '').trim())
|
|
231
|
-
.filter((line) => line.length > 0)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* The WSL major version of one distribution, from `wsl.exe -l -v`.
|
|
236
|
-
*
|
|
237
|
-
* WSL1 is explicitly unsupported (its syscall translation layer has no mount
|
|
238
|
-
* namespace), but until now nothing in the product asked the host which version a
|
|
239
|
-
* distribution runs. The output is a table:
|
|
240
|
-
*
|
|
241
|
-
* ` NAME STATE VERSION`
|
|
242
|
-
* `* debian Running 2`
|
|
243
|
-
*
|
|
244
|
-
* The active marker is optional; the version is the last column. A table that
|
|
245
|
-
* cannot be parsed answers `null` — an unmeasured fact is not a failure.
|
|
246
|
-
* @param {string} distro - the distribution name.
|
|
247
|
-
* @param {{ wslPath?: string }} [options] - the configured `wsl.exe` path.
|
|
248
|
-
* @returns {Promise<'1'|'2'|null>} the major version, or null when unreadable.
|
|
249
|
-
*/
|
|
250
|
-
export async function wslMajorVersion(distro, { wslPath = 'wsl.exe' } = {}) {
|
|
251
|
-
let stdout
|
|
252
|
-
try {
|
|
253
|
-
({ stdout } = await execFileAsync(wslPath, ['-l', '-v'], {
|
|
254
|
-
encoding: 'buffer',
|
|
255
|
-
timeout: DISCOVERY_TIMEOUT_MS,
|
|
256
|
-
env: { ...process.env, ...WSL_CHILD_ENV },
|
|
257
|
-
}))
|
|
258
|
-
} catch {
|
|
259
|
-
return null
|
|
260
|
-
}
|
|
261
|
-
return parseWslVersionTable(decodeWslOutput(stdout), distro)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Pick one distribution's major version out of a `wsl.exe -l -v` table.
|
|
266
|
-
*
|
|
267
|
-
* Kept pure and exported so the table's spacing variants are testable without a
|
|
268
|
-
* real WSL host. The active marker (`*`) is optional; the last column is VERSION.
|
|
269
|
-
* @param {unknown} text - the decoded table.
|
|
270
|
-
* @param {string} distro - the distribution name.
|
|
271
|
-
* @returns {'1'|'2'|null} the major version, or null when the row is absent.
|
|
272
|
-
*/
|
|
273
|
-
export function parseWslVersionTable(text, distro) {
|
|
274
|
-
const lines = String(text ?? '').split(/\r?\n/)
|
|
275
|
-
for (const raw of lines) {
|
|
276
|
-
const line = raw.replace(/\0/g, '').trim()
|
|
277
|
-
if (line === '') continue
|
|
278
|
-
const cells = line.replace(/^\*\s*/, '').split(/\s{2,}|\t/).filter((cell) => cell !== '')
|
|
279
|
-
if (cells.length < 2) continue
|
|
280
|
-
if (cells[0] !== distro) continue
|
|
281
|
-
const version = cells[cells.length - 1]
|
|
282
|
-
return version === '1' || version === '2' ? version : null
|
|
283
|
-
}
|
|
284
|
-
return null
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* The Lxss registry values and the two REG_SZ lines the default-distribution read needs.
|
|
289
|
-
*
|
|
290
|
-
* The patterns live HERE, once, because the async and the sync reader each carried their
|
|
291
|
-
* own copy: a registry-format change had two homes, and only one of them was exercised by
|
|
292
|
-
* whichever caller the operator happened to hit.
|
|
293
|
-
*/
|
|
294
|
-
const DEFAULT_DISTRIBUTION_VALUE = 'DefaultDistribution'
|
|
295
|
-
const DISTRIBUTION_NAME_VALUE = 'DistributionName'
|
|
296
|
-
const GUID_PATTERN = /DefaultDistribution\s+REG_SZ\s+(\{[0-9a-fA-F-]+\})/iu
|
|
297
|
-
const NAME_PATTERN = /DistributionName\s+REG_SZ\s+(.+)/iu
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* The distribution a `DistributionName` line names.
|
|
301
|
-
* @param {unknown} output - the registry query's stdout.
|
|
302
|
-
* @returns {string | undefined} the name, or undefined when the line names none.
|
|
303
|
-
*/
|
|
304
|
-
function distroFromRegistryOutput(output) {
|
|
305
|
-
const distro = NAME_PATTERN.exec(String(output))?.[1]?.trim()
|
|
306
|
-
return distro === undefined || distro === '' ? undefined : distro
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* The Lxss subkey a `DefaultDistribution` line names.
|
|
311
|
-
* @param {unknown} output - the registry query's stdout.
|
|
312
|
-
* @returns {string | undefined} the GUID, or undefined when the line names none.
|
|
313
|
-
*/
|
|
314
|
-
function guidFromRegistryOutput(output) {
|
|
315
|
-
return GUID_PATTERN.exec(String(output))?.[1]
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Read the user's default distribution from the Lxss registry. Non-fatal:
|
|
320
|
-
* `undefined` when the value is absent or unreadable.
|
|
321
|
-
* @returns {Promise<string | undefined>} the default distribution name.
|
|
322
|
-
*/
|
|
323
|
-
export async function defaultDistro() {
|
|
324
|
-
try {
|
|
325
|
-
const value = await execFileAsync('reg.exe', ['query', LXSS_KEY, '/v', DEFAULT_DISTRIBUTION_VALUE], {
|
|
326
|
-
timeout: DISCOVERY_TIMEOUT_MS,
|
|
327
|
-
})
|
|
328
|
-
const guid = guidFromRegistryOutput(value.stdout)
|
|
329
|
-
if (guid === undefined) return undefined
|
|
330
|
-
const name = await execFileAsync('reg.exe', ['query', `${LXSS_KEY}\\${guid}`, '/v', DISTRIBUTION_NAME_VALUE], {
|
|
331
|
-
timeout: DISCOVERY_TIMEOUT_MS,
|
|
332
|
-
})
|
|
333
|
-
return distroFromRegistryOutput(name.stdout)
|
|
334
|
-
} catch {
|
|
335
|
-
// A missing or unreadable registry value leaves the caller its own fallback.
|
|
336
|
-
return undefined
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Synchronous default-distribution read for plan steps that cannot await.
|
|
342
|
-
*
|
|
343
|
-
* Cached after the first read: a shell plan is built synchronously, and the
|
|
344
|
-
* registry does not change while the process runs.
|
|
345
|
-
* @returns {string | undefined} the default distribution name.
|
|
346
|
-
*/
|
|
347
|
-
let syncDefaultResolved = false
|
|
348
|
-
let syncDefault
|
|
349
|
-
export function defaultDistroSync() {
|
|
350
|
-
if (syncDefaultResolved) return syncDefault
|
|
351
|
-
syncDefaultResolved = true
|
|
352
|
-
try {
|
|
353
|
-
const value = execFileSync('reg.exe', ['query', LXSS_KEY, '/v', DEFAULT_DISTRIBUTION_VALUE], {
|
|
354
|
-
timeout: DISCOVERY_TIMEOUT_MS,
|
|
355
|
-
})
|
|
356
|
-
const guid = guidFromRegistryOutput(value)
|
|
357
|
-
if (guid === undefined) return undefined
|
|
358
|
-
const name = execFileSync('reg.exe', ['query', `${LXSS_KEY}\\${guid}`, '/v', DISTRIBUTION_NAME_VALUE], {
|
|
359
|
-
timeout: DISCOVERY_TIMEOUT_MS,
|
|
360
|
-
})
|
|
361
|
-
syncDefault = distroFromRegistryOutput(name)
|
|
362
|
-
} catch {
|
|
363
|
-
// An unreadable registry leaves the caller its own fail-loud path.
|
|
364
|
-
syncDefault = undefined
|
|
365
|
-
}
|
|
366
|
-
return syncDefault
|
|
367
|
-
}
|
|
368
|
-
|
|
369
32
|
/**
|
|
370
33
|
* Add `WSLENV` flags to an explicit environment map.
|
|
371
34
|
*
|