Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/desktop/src/wsl/DesktopWslEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
parseResolvedPath,
parseToolchainReport,
probeWslDistros,
WSL_SCRIPT_SHELL_ARGS,
} from "./DesktopWslEnvironment.ts";

const encoder = new TextEncoder();
Expand Down Expand Up @@ -112,6 +113,12 @@ describe("formatWslShellTransportFailureReason", () => {
});
});

describe("WSL scripted shell transport", () => {
it("loads login state then executes stdin in a non-login child", () => {
expect(WSL_SCRIPT_SHELL_ARGS).toEqual(["--", "bash", "-l", "-c", "exec bash -s"]);
});
});

describe("buildWslNodeEnvPreamble", () => {
it("passes the required Node engine range into the shared resolver", () => {
const preamble = buildWslNodeEnvPreamble("^22.16 || ^23.11 || >=24.10");
Expand Down
12 changes: 9 additions & 3 deletions apps/desktop/src/wsl/DesktopWslEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export class DesktopWslEnvironment extends Context.Service<
const buildDistroArgs = (distro: string | null): ReadonlyArray<string> =>
distro ? ["-d", distro] : [];

// Load the user's login profile, then execute the scripted stdin in a
// non-login child so the profile's logout hook cannot rewrite its exit code.
export const WSL_SCRIPT_SHELL_ARGS = ["--", "bash", "-l", "-c", "exec bash -s"] as const;

const concatChunks = (arrays: ReadonlyArray<Uint8Array>): Uint8Array => {
let totalLength = 0;
for (const arr of arrays) totalLength += arr.byteLength;
Expand Down Expand Up @@ -174,11 +178,13 @@ const runWslScript = (
timeout: Duration.Duration,
): Effect.Effect<ShellResult, never, ChildProcessSpawner.ChildProcessSpawner> => {
const spawner = ChildProcessSpawner.ChildProcessSpawner;
// -l picks up profile-managed shell state. -s makes bash read the script
// from stdin so wsl.exe never has to re-escape the script as an argument.
// Load profile-managed shell state in a login shell, then replace it with a
// non-login child that reads stdin. This preserves exported profile state
// without running ~/.bash_logout, whose status can rewrite a successful
// scripted launch when bash exits.
const command = ChildProcess.make(
"wsl.exe",
[...buildDistroArgs(distro), "--", "bash", "-l", "-s"],
[...buildDistroArgs(distro), ...WSL_SCRIPT_SHELL_ARGS],
{
stdin: Stream.encodeText(Stream.make(bashScript)),
stdout: "pipe",
Expand Down
Loading