diff --git a/apps/desktop/src/wsl/DesktopWslEnvironment.test.ts b/apps/desktop/src/wsl/DesktopWslEnvironment.test.ts index 9dbe43b9650d8e3f9f6de0e2c215827a7398ce18..e1188e1a3387915658db6388c52c4507e9e38431 100644 --- a/apps/desktop/src/wsl/DesktopWslEnvironment.test.ts +++ b/apps/desktop/src/wsl/DesktopWslEnvironment.test.ts @@ -12,21 +12,17 @@ import * as TestClock from "effect/testing/TestClock"; import { ChildProcessSpawner } from "effect/unstable/process"; import { - buildWslNodeEnvPreamble, buildWslRuntimeInstallScript, buildWslRuntimeInvalidateScript, buildWslRuntimePruneScript, DesktopWslDistroListError, formatMissingToolsReason, - formatNodePtyProbeFailureReason, - formatWslShellTransportFailureReason, parseNodePath, parseNodeVersion, parseResolvedPath, parseToolchainReport, parseWslRuntimeRoot, probeWslDistros, - sanitizeWslRuntimeId, } from "./DesktopWslEnvironment.ts"; const encoder = new TextEncoder(); @@ -144,46 +140,19 @@ describe("probeWslDistros", () => { }); }); -describe("formatNodePtyProbeFailureReason", () => { - it("identifies a packaged build that omitted the Linux node-pty prebuild", () => { - const reason = formatNodePtyProbeFailureReason(4); - - expect(reason).toContain("packaged Linux node-pty binary was not included"); - expect(reason).toContain("--wsl-prebuild"); - }); - - it("leaves other node-pty load failures to the compatibility diagnostic", () => { - expect(formatNodePtyProbeFailureReason(1)).toBeNull(); - }); -}); - -describe("formatWslShellTransportFailureReason", () => { - it("distinguishes timeouts and spawn failures from normal shell exit codes", () => { - expect(formatWslShellTransportFailureReason("timeout")).toContain("timed out"); - expect(formatWslShellTransportFailureReason("spawn")).toContain("could not start wsl.exe"); - expect(formatWslShellTransportFailureReason("process")).toContain("lost communication"); - expect(formatWslShellTransportFailureReason(null)).toBeNull(); - }); -}); - -describe("buildWslNodeEnvPreamble", () => { - it("passes the required Node engine range into the shared resolver", () => { - const preamble = buildWslNodeEnvPreamble("^22.16 || ^23.11 || >=24.10"); - - expect(preamble).toContain("T3_NODE_ENGINE_RANGE='^22.16 || ^23.11 || >=24.10'"); - expect(preamble.indexOf("T3_NODE_ENGINE_RANGE=")).toBeLessThan( - preamble.lastIndexOf("ensure_remote_node_path || true"), - ); - }); - - it("keeps the shared resolver permissive when no Node engine range is provided", () => { - expect(buildWslNodeEnvPreamble()).toContain("T3_NODE_ENGINE_RANGE=''"); - }); -}); - describe("WSL runtime cache", () => { - it("sanitizes cache ids before interpolating them into Linux paths", () => { - expect(sanitizeWslRuntimeId("1.2.3/x64; touch /tmp/nope")).toBe("1.2.3_x64__touch__tmp_nope"); + it.each([ + [ + "install", + (id: string) => buildWslRuntimeInstallScript("/runtime.tar.gz", id, "b".repeat(64)), + ], + ["prune", buildWslRuntimePruneScript], + ["invalidate", buildWslRuntimeInvalidateScript], + ] as const)("sanitizes cache ids in the %s script", (_, buildScript) => { + const runtimeId = "1.2.3/x64; touch /tmp/nope"; + const script = buildScript(runtimeId); + expect(script).toContain("/1.2.3_x64__touch__tmp_nope"); + expect(script).not.toContain(runtimeId); }); it("installs through a temporary directory and only reuses valid completed caches", () => { diff --git a/apps/desktop/src/wsl/DesktopWslEnvironment.ts b/apps/desktop/src/wsl/DesktopWslEnvironment.ts index b0c9f5ffe44b4e75aaece60ec642a46c44ec7b83..d49d95676e649e08dec7604228f3dc11ff7f2a3e 100644 --- a/apps/desktop/src/wsl/DesktopWslEnvironment.ts +++ b/apps/desktop/src/wsl/DesktopWslEnvironment.ts @@ -147,7 +147,7 @@ const TIMEOUT_RESULT: ShellResult = { transportFailure: "timeout", }; -export const formatWslShellTransportFailureReason = ( +const formatWslShellTransportFailureReason = ( failure: ShellResult["transportFailure"], ): string | null => { switch (failure) { @@ -165,7 +165,7 @@ export const formatWslShellTransportFailureReason = ( // Reuse the SSH remote resolver so WSL and SSH discover version-managed Node // the same way. Passing the engine range lets the resolver fall through to // version managers like nvm when a system node exists but is too old. -export const buildWslNodeEnvPreamble = ( +const buildWslNodeEnvPreamble = ( nodeEngineRange?: string | null, ): string => `${buildRemoteNodeEnvScript({ nodeEngineRange: nodeEngineRange ?? null })} ensure_remote_node_path || true @@ -263,8 +263,7 @@ const WSL_RUNTIME_READY_MARKER = ".t3code-wsl-runtime-ready"; const WSL_RUNTIME_SELECTED_MARKER = ".t3code-wsl-runtime-selected"; const WSL_RUNTIME_SELECTION_GRACE_MINUTES = 5; -export const sanitizeWslRuntimeId = (value: string): string => - value.replace(/[^A-Za-z0-9._-]/g, "_"); +const sanitizeWslRuntimeId = (value: string): string => value.replace(/[^A-Za-z0-9._-]/g, "_"); // `archiveSha256` is the digest the build recorded alongside the archive. The // install verifies the bytes before extracting, so an archive can never be @@ -491,7 +490,7 @@ export const parseWslRuntimeRoot = (stdout: string): string | null => { const NODE_PTY_PREBUILD_MISSING_EXIT_CODE = 4; -export const formatNodePtyProbeFailureReason = (exitCode: number): string | null => +const formatNodePtyProbeFailureReason = (exitCode: number): string | null => exitCode === NODE_PTY_PREBUILD_MISSING_EXIT_CODE ? "WSL support is missing from this T3 Code build: the packaged Linux node-pty binary was not included. Rebuild the Windows artifact with `--wsl-prebuild ` or install a build that includes WSL support." : null;