@namzu/sandbox 24.0.0 → 26.0.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 +284 -0
- package/dist/backends/http-worker-client.d.ts +3 -1
- package/dist/backends/http-worker-client.d.ts.map +1 -1
- package/dist/backends/http-worker-client.js +15 -4
- package/dist/backends/http-worker-client.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/local-virtual-computer/client.d.ts +21 -0
- package/dist/local-virtual-computer/client.d.ts.map +1 -0
- package/dist/local-virtual-computer/client.js +503 -0
- package/dist/local-virtual-computer/client.js.map +1 -0
- package/dist/local-virtual-computer/detached-process.d.ts +15 -0
- package/dist/local-virtual-computer/detached-process.d.ts.map +1 -0
- package/dist/local-virtual-computer/detached-process.js +111 -0
- package/dist/local-virtual-computer/detached-process.js.map +1 -0
- package/dist/local-virtual-computer/detached-worker.d.ts +11 -0
- package/dist/local-virtual-computer/detached-worker.d.ts.map +1 -0
- package/dist/local-virtual-computer/detached-worker.js +97 -0
- package/dist/local-virtual-computer/detached-worker.js.map +1 -0
- package/dist/local-virtual-computer/dialect.d.ts +26 -0
- package/dist/local-virtual-computer/dialect.d.ts.map +1 -0
- package/dist/local-virtual-computer/dialect.js +61 -0
- package/dist/local-virtual-computer/dialect.js.map +1 -0
- package/dist/local-virtual-computer/engine.d.ts +63 -0
- package/dist/local-virtual-computer/engine.d.ts.map +1 -0
- package/dist/local-virtual-computer/engine.js +125 -0
- package/dist/local-virtual-computer/engine.js.map +1 -0
- package/dist/local-virtual-computer/guest-stdio.d.ts +15 -0
- package/dist/local-virtual-computer/guest-stdio.d.ts.map +1 -0
- package/dist/local-virtual-computer/guest-stdio.js +330 -0
- package/dist/local-virtual-computer/guest-stdio.js.map +1 -0
- package/dist/local-virtual-computer/index.d.ts +36 -0
- package/dist/local-virtual-computer/index.d.ts.map +1 -0
- package/dist/local-virtual-computer/index.js +341 -0
- package/dist/local-virtual-computer/index.js.map +1 -0
- package/dist/local-virtual-computer/podman-machine.d.ts +17 -0
- package/dist/local-virtual-computer/podman-machine.d.ts.map +1 -0
- package/dist/local-virtual-computer/podman-machine.js +181 -0
- package/dist/local-virtual-computer/podman-machine.js.map +1 -0
- package/dist/local-virtual-computer/setup/contract.d.ts +360 -0
- package/dist/local-virtual-computer/setup/contract.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/contract.js +3 -0
- package/dist/local-virtual-computer/setup/contract.js.map +1 -0
- package/dist/local-virtual-computer/setup/elevated.d.ts +78 -0
- package/dist/local-virtual-computer/setup/elevated.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/elevated.js +548 -0
- package/dist/local-virtual-computer/setup/elevated.js.map +1 -0
- package/dist/local-virtual-computer/setup/host.d.ts +12 -0
- package/dist/local-virtual-computer/setup/host.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/host.js +93 -0
- package/dist/local-virtual-computer/setup/host.js.map +1 -0
- package/dist/local-virtual-computer/setup/idle.d.ts +18 -0
- package/dist/local-virtual-computer/setup/idle.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/idle.js +38 -0
- package/dist/local-virtual-computer/setup/idle.js.map +1 -0
- package/dist/local-virtual-computer/setup/image.d.ts +7 -0
- package/dist/local-virtual-computer/setup/image.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/image.js +124 -0
- package/dist/local-virtual-computer/setup/image.js.map +1 -0
- package/dist/local-virtual-computer/setup/index.d.ts +17 -0
- package/dist/local-virtual-computer/setup/index.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/index.js +17 -0
- package/dist/local-virtual-computer/setup/index.js.map +1 -0
- package/dist/local-virtual-computer/setup/limits.d.ts +30 -0
- package/dist/local-virtual-computer/setup/limits.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/limits.js +58 -0
- package/dist/local-virtual-computer/setup/limits.js.map +1 -0
- package/dist/local-virtual-computer/setup/plan.d.ts +8 -0
- package/dist/local-virtual-computer/setup/plan.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/plan.js +131 -0
- package/dist/local-virtual-computer/setup/plan.js.map +1 -0
- package/dist/local-virtual-computer/setup/readiness.d.ts +11 -0
- package/dist/local-virtual-computer/setup/readiness.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/readiness.js +320 -0
- package/dist/local-virtual-computer/setup/readiness.js.map +1 -0
- package/dist/local-virtual-computer/setup/registry.d.ts +18 -0
- package/dist/local-virtual-computer/setup/registry.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/registry.js +50 -0
- package/dist/local-virtual-computer/setup/registry.js.map +1 -0
- package/dist/local-virtual-computer/setup/restart.d.ts +8 -0
- package/dist/local-virtual-computer/setup/restart.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/restart.js +60 -0
- package/dist/local-virtual-computer/setup/restart.js.map +1 -0
- package/dist/local-virtual-computer/setup/resume.d.ts +14 -0
- package/dist/local-virtual-computer/setup/resume.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/resume.js +58 -0
- package/dist/local-virtual-computer/setup/resume.js.map +1 -0
- package/dist/local-virtual-computer/setup/run.d.ts +8 -0
- package/dist/local-virtual-computer/setup/run.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/run.js +200 -0
- package/dist/local-virtual-computer/setup/run.js.map +1 -0
- package/dist/local-virtual-computer/setup/windows-folder.d.ts +11 -0
- package/dist/local-virtual-computer/setup/windows-folder.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/windows-folder.js +14 -0
- package/dist/local-virtual-computer/setup/windows-folder.js.map +1 -0
- package/dist/local-virtual-computer/setup/wsl-package.d.ts +19 -0
- package/dist/local-virtual-computer/setup/wsl-package.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/wsl-package.js +242 -0
- package/dist/local-virtual-computer/setup/wsl-package.js.map +1 -0
- package/dist/local-virtual-computer/setup/wslc-shared.d.ts +27 -0
- package/dist/local-virtual-computer/setup/wslc-shared.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/wslc-shared.js +137 -0
- package/dist/local-virtual-computer/setup/wslc-shared.js.map +1 -0
- package/dist/local-virtual-computer/ssh-forward.d.ts +19 -0
- package/dist/local-virtual-computer/ssh-forward.d.ts.map +1 -0
- package/dist/local-virtual-computer/ssh-forward.js +179 -0
- package/dist/local-virtual-computer/ssh-forward.js.map +1 -0
- package/dist/local-virtual-computer/wslc-engine.d.ts +27 -0
- package/dist/local-virtual-computer/wslc-engine.d.ts.map +1 -0
- package/dist/local-virtual-computer/wslc-engine.js +98 -0
- package/dist/local-virtual-computer/wslc-engine.js.map +1 -0
- package/local-computer/Dockerfile +99 -0
- package/local-computer/app-catalog.cjs +240 -0
- package/local-computer/app-native-host.cjs +144 -0
- package/local-computer/browser-home.cjs +249 -0
- package/local-computer/build-image.mjs +310 -0
- package/local-computer/desktop-worker.cjs +477 -0
- package/local-computer/entrypoint.sh +161 -0
- package/local-computer/home.html +28 -0
- package/local-computer/new-tab/home.css +255 -0
- package/local-computer/new-tab/home.js +65 -0
- package/local-computer/new-tab/manifest.json +12 -0
- package/local-computer/new-tab/wordmark.svg +33 -0
- package/local-computer/openbox-rc.xml +41 -0
- package/local-computer/prepare-home.cjs +121 -0
- package/local-computer/tint2rc +44 -0
- package/package.json +13 -4
- package/src/backends/http-worker-client.ts +31 -3
- package/src/index.ts +13 -0
- package/src/local-virtual-computer/client.ts +573 -0
- package/src/local-virtual-computer/detached-process.ts +133 -0
- package/src/local-virtual-computer/detached-worker.ts +124 -0
- package/src/local-virtual-computer/dialect.ts +83 -0
- package/src/local-virtual-computer/engine.ts +197 -0
- package/src/local-virtual-computer/guest-stdio.ts +332 -0
- package/src/local-virtual-computer/index.ts +447 -0
- package/src/local-virtual-computer/podman-machine.ts +264 -0
- package/src/local-virtual-computer/setup/contract.ts +395 -0
- package/src/local-virtual-computer/setup/elevated.ts +655 -0
- package/src/local-virtual-computer/setup/host.ts +100 -0
- package/src/local-virtual-computer/setup/idle.ts +39 -0
- package/src/local-virtual-computer/setup/image.ts +159 -0
- package/src/local-virtual-computer/setup/index.ts +16 -0
- package/src/local-virtual-computer/setup/limits.ts +68 -0
- package/src/local-virtual-computer/setup/plan.ts +144 -0
- package/src/local-virtual-computer/setup/readiness.ts +386 -0
- package/src/local-virtual-computer/setup/registry.ts +50 -0
- package/src/local-virtual-computer/setup/restart.ts +64 -0
- package/src/local-virtual-computer/setup/resume.ts +65 -0
- package/src/local-virtual-computer/setup/run.ts +225 -0
- package/src/local-virtual-computer/setup/windows-folder.ts +13 -0
- package/src/local-virtual-computer/setup/wsl-package.ts +242 -0
- package/src/local-virtual-computer/setup/wslc-shared.ts +147 -0
- package/src/local-virtual-computer/ssh-forward.ts +195 -0
- package/src/local-virtual-computer/wslc-engine.ts +124 -0
- package/worker/server.js +1499 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { mkdtemp, rmdir, unlink, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { createServer } from 'node:net'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join, win32 } from 'node:path'
|
|
6
|
+
import type { LocalPortForward } from './engine.js'
|
|
7
|
+
|
|
8
|
+
/** Internal retained authority for retrying an unconfirmed owned startup cleanup. */
|
|
9
|
+
export class LocalForwardCleanupError extends Error {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly forward: LocalPortForward,
|
|
12
|
+
cause: unknown,
|
|
13
|
+
) {
|
|
14
|
+
super('The owned local computer forward requires cleanup recovery', { cause })
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function removeIfPresent(
|
|
19
|
+
path: string,
|
|
20
|
+
remove: (path: string) => Promise<void>,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
await remove(path)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface LocalSshForwardOptions {
|
|
30
|
+
readonly identity: string
|
|
31
|
+
readonly port: number
|
|
32
|
+
readonly user: string
|
|
33
|
+
readonly hostPublicKey: string
|
|
34
|
+
readonly ports: readonly number[]
|
|
35
|
+
readonly signal?: AbortSignal
|
|
36
|
+
readonly onLost: () => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function reservePort(): Promise<number> {
|
|
40
|
+
const server = createServer()
|
|
41
|
+
return await new Promise<number>((resolve, reject) => {
|
|
42
|
+
server.once('error', reject)
|
|
43
|
+
server.listen(0, '127.0.0.1', () => {
|
|
44
|
+
const address = server.address()
|
|
45
|
+
server.close((error) => {
|
|
46
|
+
if (error) reject(error)
|
|
47
|
+
else if (!address || typeof address === 'string')
|
|
48
|
+
reject(new Error('No local forwarding port'))
|
|
49
|
+
else resolve(address.port)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An owned loopback tunnel to an already verified local WSL machine only. */
|
|
56
|
+
export async function startLocalSshForward(
|
|
57
|
+
options: LocalSshForwardOptions,
|
|
58
|
+
): Promise<LocalPortForward> {
|
|
59
|
+
options.signal?.throwIfAborted()
|
|
60
|
+
if (!/^ssh-ed25519 [A-Za-z0-9+/=]+$/.test(options.hostPublicKey))
|
|
61
|
+
throw new Error('The registered local machine did not provide its SSH host public key')
|
|
62
|
+
const ports = await Promise.all(options.ports.map(() => reservePort()))
|
|
63
|
+
const directory = await mkdtemp(join(tmpdir(), 'namzu-pal-forward-'))
|
|
64
|
+
const knownHosts = join(directory, 'known_hosts')
|
|
65
|
+
await writeFile(knownHosts, `[127.0.0.1]:${options.port} ${options.hostPublicKey}\n`, {
|
|
66
|
+
mode: 0o600,
|
|
67
|
+
flag: 'wx',
|
|
68
|
+
})
|
|
69
|
+
const child = spawn(
|
|
70
|
+
win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'OpenSSH', 'ssh.exe'),
|
|
71
|
+
[
|
|
72
|
+
'-N',
|
|
73
|
+
'-T',
|
|
74
|
+
'-v',
|
|
75
|
+
'-F',
|
|
76
|
+
'NUL',
|
|
77
|
+
'-o',
|
|
78
|
+
'BatchMode=yes',
|
|
79
|
+
'-o',
|
|
80
|
+
'ExitOnForwardFailure=yes',
|
|
81
|
+
'-o',
|
|
82
|
+
'StrictHostKeyChecking=yes',
|
|
83
|
+
'-o',
|
|
84
|
+
`UserKnownHostsFile=${knownHosts}`,
|
|
85
|
+
'-o',
|
|
86
|
+
'HostKeyAlgorithms=ssh-ed25519',
|
|
87
|
+
'-o',
|
|
88
|
+
'IdentitiesOnly=yes',
|
|
89
|
+
'-o',
|
|
90
|
+
'ProxyCommand=none',
|
|
91
|
+
'-o',
|
|
92
|
+
'ProxyJump=none',
|
|
93
|
+
'-i',
|
|
94
|
+
options.identity,
|
|
95
|
+
'-p',
|
|
96
|
+
String(options.port),
|
|
97
|
+
...ports.flatMap((port, index) => [
|
|
98
|
+
'-L',
|
|
99
|
+
`127.0.0.1:${port}:127.0.0.1:${options.ports[index]}`,
|
|
100
|
+
]),
|
|
101
|
+
`${options.user}@127.0.0.1`,
|
|
102
|
+
],
|
|
103
|
+
{ windowsHide: true, stdio: ['ignore', 'ignore', 'pipe'] },
|
|
104
|
+
)
|
|
105
|
+
let ended = false
|
|
106
|
+
let closing = false
|
|
107
|
+
let ready = false
|
|
108
|
+
let cleanup: Promise<void> | undefined
|
|
109
|
+
const closed = new Promise<void>((resolve) => {
|
|
110
|
+
child.once('close', () => {
|
|
111
|
+
ended = true
|
|
112
|
+
if (ready && !closing) {
|
|
113
|
+
try {
|
|
114
|
+
options.onLost()
|
|
115
|
+
} catch {
|
|
116
|
+
// A host callback must not crash the process-close event dispatch.
|
|
117
|
+
// The ended handle remains fenced and close() retains recovery.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
resolve()
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
const close = (): Promise<void> => {
|
|
124
|
+
if (cleanup) return cleanup
|
|
125
|
+
closing = true
|
|
126
|
+
cleanup = (async () => {
|
|
127
|
+
if (
|
|
128
|
+
!ended &&
|
|
129
|
+
child.pid !== undefined &&
|
|
130
|
+
child.exitCode === null &&
|
|
131
|
+
child.signalCode === null &&
|
|
132
|
+
!child.kill()
|
|
133
|
+
)
|
|
134
|
+
throw new Error('The owned local computer forwarding process could not be stopped')
|
|
135
|
+
await closed
|
|
136
|
+
await removeIfPresent(knownHosts, unlink)
|
|
137
|
+
await removeIfPresent(directory, rmdir)
|
|
138
|
+
})().catch((error: unknown) => {
|
|
139
|
+
cleanup = undefined
|
|
140
|
+
throw error
|
|
141
|
+
})
|
|
142
|
+
return cleanup
|
|
143
|
+
}
|
|
144
|
+
const forward: LocalPortForward = {
|
|
145
|
+
ports,
|
|
146
|
+
assertAlive() {
|
|
147
|
+
if (ended || closing) throw new Error('The local computer forwarding process has ended')
|
|
148
|
+
},
|
|
149
|
+
close,
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await new Promise<void>((resolve, reject) => {
|
|
153
|
+
let diagnostic = ''
|
|
154
|
+
const opened = new Set<number>()
|
|
155
|
+
const detach = () => {
|
|
156
|
+
options.signal?.removeEventListener('abort', abort)
|
|
157
|
+
child.stderr.removeListener('data', data)
|
|
158
|
+
child.removeListener('close', fail)
|
|
159
|
+
}
|
|
160
|
+
const abort = () => {
|
|
161
|
+
detach()
|
|
162
|
+
reject(options.signal?.reason)
|
|
163
|
+
}
|
|
164
|
+
const fail = () => {
|
|
165
|
+
detach()
|
|
166
|
+
reject(new Error('The verified local machine SSH forwarding process failed'))
|
|
167
|
+
}
|
|
168
|
+
const data = (chunk: Buffer) => {
|
|
169
|
+
diagnostic = (diagnostic + chunk.toString('utf8')).slice(-16_384)
|
|
170
|
+
for (const port of ports)
|
|
171
|
+
if (diagnostic.includes(`Local forwarding listening on 127.0.0.1 port ${port}.`))
|
|
172
|
+
opened.add(port)
|
|
173
|
+
if (opened.size === ports.length) {
|
|
174
|
+
ready = true
|
|
175
|
+
detach()
|
|
176
|
+
resolve()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
child.stderr.on('data', data)
|
|
180
|
+
child.once('error', fail)
|
|
181
|
+
child.once('close', fail)
|
|
182
|
+
options.signal?.addEventListener('abort', abort, { once: true })
|
|
183
|
+
if (options.signal?.aborted) abort()
|
|
184
|
+
}).finally(() => child.stderr.resume())
|
|
185
|
+
options.signal?.throwIfAborted()
|
|
186
|
+
return forward
|
|
187
|
+
} catch (error) {
|
|
188
|
+
try {
|
|
189
|
+
await close()
|
|
190
|
+
} catch {
|
|
191
|
+
throw new LocalForwardCleanupError(forward, error)
|
|
192
|
+
}
|
|
193
|
+
throw error
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises'
|
|
2
|
+
import { win32 } from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
LocalCommandError,
|
|
5
|
+
type LocalComputerCommandRunner,
|
|
6
|
+
type LocalComputerSetupCode,
|
|
7
|
+
LocalComputerSetupError,
|
|
8
|
+
type LocalEngine,
|
|
9
|
+
nodeCommandRunner,
|
|
10
|
+
} from './engine.js'
|
|
11
|
+
|
|
12
|
+
/** The oldest wslc runtime the provider is verified against. */
|
|
13
|
+
export const WSLC_MINIMUM_VERSION = [2, 9, 3] as const
|
|
14
|
+
|
|
15
|
+
export interface LocalWslcEngineOptions {
|
|
16
|
+
/** Path to wslc.exe. Defaults to the WSL install directory, then PATH. */
|
|
17
|
+
readonly wslcBinary?: string
|
|
18
|
+
readonly runner?: LocalComputerCommandRunner
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MESSAGES: Record<LocalComputerSetupCode, string> = {
|
|
22
|
+
'engine-missing': 'Linux container support is not installed on this PC.',
|
|
23
|
+
'engine-too-old': 'Linux container support on this PC is too old for Namzu.',
|
|
24
|
+
'virtualization-off':
|
|
25
|
+
'This PC has virtualization switched off. Turn it on in the PC startup settings, then try again.',
|
|
26
|
+
'blocked-by-policy': 'Your organisation does not allow Linux containers on this PC.',
|
|
27
|
+
'image-missing':
|
|
28
|
+
'The Pal computer is not set up on this PC yet. Namzu sets it up; finish the setup from the Pal computer and try again.',
|
|
29
|
+
'sdk-mismatch': "Namzu needs an update to work with this version of Windows' Linux support.",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function wslcSetupError(code: LocalComputerSetupCode, cause?: unknown) {
|
|
33
|
+
return new LocalComputerSetupError(MESSAGES[code], code, cause ? { cause } : undefined)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Turn the machine-readable codes of a failed wslc command into a typed
|
|
38
|
+
* setup error. Anything unrecognised stays an ordinary failure.
|
|
39
|
+
*/
|
|
40
|
+
export function mapWslcFailure(error: unknown): LocalComputerSetupError | undefined {
|
|
41
|
+
if (error instanceof LocalComputerSetupError) return undefined
|
|
42
|
+
const codes = new Set(error instanceof LocalCommandError ? error.errorCodes : [])
|
|
43
|
+
if (codes.has('0x80370102') || codes.has('HCS_E_HYPERV_NOT_INSTALLED'))
|
|
44
|
+
return wslcSetupError('virtualization-off', error)
|
|
45
|
+
if (codes.has('0x8004060C') || codes.has('WSLC_E_CONTAINER_DISABLED'))
|
|
46
|
+
return wslcSetupError('blocked-by-policy', error)
|
|
47
|
+
if (codes.has('0x8004060B') || codes.has('WSLC_E_SDK_UPDATE_NEEDED'))
|
|
48
|
+
return wslcSetupError('sdk-mismatch', error)
|
|
49
|
+
if (codes.has('WSLC_E_IMAGE_NOT_FOUND')) return wslcSetupError('image-missing', error)
|
|
50
|
+
if (codes.has('ENOENT')) return wslcSetupError('engine-missing', error)
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Parse the first line of `wslc version`, for example `wslc 3.0.1.0`. */
|
|
55
|
+
export function parseWslcVersion(output: string): [number, number, number] | undefined {
|
|
56
|
+
const match = /^\s*wslc\s+(\d+)\.(\d+)\.(\d+)/im.exec(output)
|
|
57
|
+
if (!match) return undefined
|
|
58
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function atLeast(found: readonly number[], floor: readonly number[]): boolean {
|
|
62
|
+
for (let i = 0; i < floor.length; i++) {
|
|
63
|
+
if ((found[i] ?? 0) !== floor[i]) return (found[i] ?? 0) > (floor[i] ?? 0)
|
|
64
|
+
}
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function exists(path: string): Promise<boolean> {
|
|
69
|
+
try {
|
|
70
|
+
await access(path)
|
|
71
|
+
return true
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface WslcHost {
|
|
78
|
+
readonly platform?: NodeJS.Platform
|
|
79
|
+
readonly env?: NodeJS.ProcessEnv
|
|
80
|
+
readonly exists?: (path: string) => Promise<boolean>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function resolveBinary(options: LocalWslcEngineOptions, host: WslcHost): Promise<string> {
|
|
84
|
+
if (options.wslcBinary) return options.wslcBinary
|
|
85
|
+
const platform = host.platform ?? process.platform
|
|
86
|
+
const env = host.env ?? process.env
|
|
87
|
+
const programFiles = env.ProgramW6432 ?? env.ProgramFiles
|
|
88
|
+
if (platform === 'win32' && programFiles) {
|
|
89
|
+
const installed = win32.join(programFiles, 'WSL', 'wslc.exe')
|
|
90
|
+
if (await (host.exists ?? exists)(installed)) return installed
|
|
91
|
+
}
|
|
92
|
+
return 'wslc'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Find a working wslc and prove its session starts. Never installs or updates
|
|
97
|
+
* anything: setup is a separate, consented operation.
|
|
98
|
+
*/
|
|
99
|
+
export async function resolveLocalWslcEngine(
|
|
100
|
+
options: LocalWslcEngineOptions,
|
|
101
|
+
signal?: AbortSignal,
|
|
102
|
+
host: WslcHost = {},
|
|
103
|
+
): Promise<LocalEngine> {
|
|
104
|
+
const binary = await resolveBinary(options, host)
|
|
105
|
+
const runner = options.runner ?? nodeCommandRunner
|
|
106
|
+
const run: LocalEngine['run'] = async (args, call) => {
|
|
107
|
+
try {
|
|
108
|
+
return await runner.run(binary, args, { ...call, env: { ...process.env, ...call?.env } })
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw mapWslcFailure(error) ?? error
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const version = parseWslcVersion(await run(['version'], { signal }))
|
|
114
|
+
if (!version) throw wslcSetupError('engine-missing')
|
|
115
|
+
if (!atLeast(version, WSLC_MINIMUM_VERSION))
|
|
116
|
+
throw new LocalComputerSetupError(
|
|
117
|
+
`Linux container support on this PC (${version.join('.')}) is too old; ${WSLC_MINIMUM_VERSION.join('.')} or newer is required.`,
|
|
118
|
+
'engine-too-old',
|
|
119
|
+
)
|
|
120
|
+
// The first command that needs the session starts its virtual machine, so
|
|
121
|
+
// this is where "virtualization is off" and "blocked by policy" surface.
|
|
122
|
+
await run(['ps', '-a', '-q'], { signal })
|
|
123
|
+
return { endpoint: 'wslc://local', dialect: 'wslc', run }
|
|
124
|
+
}
|