| ... | ... | @@ -32,7 +32,11 @@ export interface EnsureWslNodePtyOptions { |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | export type EnsureWslNodePtyResult = |
| 35 | | | { readonly ok: true; readonly nodePath: string; readonly resolvedPath: string } |
| 35 | | { |
| 36 | readonly ok: true; |
| 37 | readonly nodePath: string; |
| 38 | readonly resolvedPath: string; |
| 39 | } |
| 36 | 40 | | { |
| 37 | 41 | readonly ok: false; |
| 38 | 42 | readonly reason: string; |
| ... | ... | @@ -222,6 +226,7 @@ export const formatNodePtyProbeFailureReason = (exitCode: number): string | null |
| 222 | 226 | const NODE_PTY_PROBE_SCRIPT = ( |
| 223 | 227 | linuxServerDir: string, |
| 224 | 228 | ) => `printf 'nodePath:%s\\n' "$(command -v node 2>/dev/null)" |
| 229 | printf 'nodeVersion:%s\\n' "$(node -p 'process.versions.node' 2>/dev/null)" |
| 225 | 230 | printf 'resolvedPath:%s\\n' "$PATH" |
| 226 | 231 | cd ${shellQuote(linuxServerDir)} && node <<'NODE' >/dev/null 2>&1 |
| 227 | 232 | // The server bundle externalizes its deps to node_modules, and the WSL Node |
| ... | ... | @@ -318,6 +323,16 @@ export const parseNodePath = (stdout: string): string | null => { |
| 318 | 323 | return path ?? null; |
| 319 | 324 | }; |
| 320 | 325 | |
| 326 | export const parseNodeVersion = (stdout: string): string | null => { |
| 327 | const version = stdout |
| 328 | .split("\n") |
| 329 | .map((line) => line.trim()) |
| 330 | .filter((line) => line.startsWith("nodeVersion:")) |
| 331 | .map((line) => line.slice("nodeVersion:".length).trim()) |
| 332 | .find((value) => value.length > 0); |
| 333 | return version ?? null; |
| 334 | }; |
| 335 | |
| 321 | 336 | // Captures the login-shell PATH after the shared resolver has loaded version |
| 322 | 337 | // managers. Preserve the value byte-for-byte apart from a Windows-style CR so |
| 323 | 338 | // paths containing spaces or apostrophes can be forwarded as one env argv. |
| ... | ... | @@ -404,7 +419,11 @@ const ensureNodePtyImpl = ( |
| 404 | 419 | |
| 405 | 420 | const transportFailureReason = formatWslShellTransportFailureReason(probe.transportFailure); |
| 406 | 421 | if (transportFailureReason !== null) { |
| 407 | | return { ok: false, reason: transportFailureReason, fatal: false } as const; |
| 422 | return { |
| 423 | ok: false, |
| 424 | reason: transportFailureReason, |
| 425 | fatal: false, |
| 426 | } as const; |
| 408 | 427 | } |
| 409 | 428 | |
| 410 | 429 | // No node at all, even after the shared resolver repaired PATH. Surface |
| ... | ... | @@ -457,12 +476,31 @@ const ensureNodePtyImpl = ( |
| 457 | 476 | } as const; |
| 458 | 477 | } |
| 459 | 478 | |
| 460 | | if (probe.exitCode === 0) return { ok: true, nodePath, resolvedPath } as const; |
| 479 | if (probe.exitCode === 0) { |
| 480 | const rawVersion = parseNodeVersion(probe.stdout); |
| 481 | if ( |
| 482 | rawVersion !== null && |
| 483 | options.nodeEngineRange && |
| 484 | !satisfiesSemverRange(rawVersion, options.nodeEngineRange.trim()) |
| 485 | ) { |
| 486 | const range = options.nodeEngineRange.trim(); |
| 487 | return { |
| 488 | ok: false, |
| 489 | reason: `WSL Node.js ${rawVersion} does not satisfy the server's required engine range (${range}). Install a compatible version, and restart the desktop app.`, |
| 490 | fatal: true, |
| 491 | } as const; |
| 492 | } |
| 493 | return { ok: true, nodePath, resolvedPath } as const; |
| 494 | } |
| 461 | 495 | |
| 462 | 496 | if (options.allowBuild !== true) { |
| 463 | 497 | const packagedProbeFailure = formatNodePtyProbeFailureReason(probe.exitCode); |
| 464 | 498 | if (packagedProbeFailure !== null) { |
| 465 | | return { ok: false, reason: packagedProbeFailure, fatal: true } as const; |
| 499 | return { |
| 500 | ok: false, |
| 501 | reason: packagedProbeFailure, |
| 502 | fatal: true, |
| 503 | } as const; |
| 466 | 504 | } |
| 467 | 505 | } |
| 468 | 506 | |