Skip to content

Commit 1091e1d

Browse files
committed
fix(desktop): preserve existing WSLENV entries
1 parent 8b1b56c commit 1091e1d

2 files changed

Lines changed: 100 additions & 3 deletions

File tree

apps/desktop/src/backend/DesktopBackendConfiguration.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { assert, describe, it } from "@effect/vitest";
33
import * as Effect from "effect/Effect";
44
import * as FileSystem from "effect/FileSystem";
55
import * as Layer from "effect/Layer";
6+
import * as Option from "effect/Option";
67
import * as Schema from "effect/Schema";
78

89
import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
@@ -43,12 +44,13 @@ function makeEnvironmentLayer(
4344
options?: {
4445
readonly isPackaged?: boolean;
4546
readonly devServerUrl?: string;
47+
readonly platform?: NodeJS.Platform;
4648
},
4749
) {
4850
return DesktopEnvironment.layer({
4951
dirname: "/repo/apps/desktop/src",
5052
homeDirectory: baseDir,
51-
platform: "darwin",
53+
platform: options?.platform ?? "darwin",
5254
processArch: "x64",
5355
appVersion: "1.2.3",
5456
appPath: "/repo",
@@ -71,6 +73,14 @@ function makeEnvironmentLayer(
7173
);
7274
}
7375

76+
const restoreEnv = (name: string, value: string | undefined) => {
77+
if (value === undefined) {
78+
delete process.env[name];
79+
} else {
80+
process.env[name] = value;
81+
}
82+
};
83+
7484
const withHarness = <A, E, R>(
7585
effect: Effect.Effect<
7686
A,
@@ -198,4 +208,58 @@ describe("DesktopBackendConfiguration", () => {
198208
);
199209
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
200210
);
211+
212+
it.effect("preserves existing WSLENV entries when forwarding WSL backend secrets", () =>
213+
Effect.gen(function* () {
214+
const fileSystem = yield* FileSystem.FileSystem;
215+
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
216+
prefix: "t3-desktop-backend-config-test-",
217+
});
218+
219+
const previousWslEnv = process.env.WSLENV;
220+
const previousOpenAiKey = process.env.OPENAI_API_KEY;
221+
const previousAnthropicKey = process.env.ANTHROPIC_API_KEY;
222+
try {
223+
process.env.WSLENV = "GOPATH/p:OPENAI_API_KEY/u:EMPTY::AZURE_DEVOPS_EXT_PAT/u";
224+
process.env.OPENAI_API_KEY = "openai-key";
225+
process.env.ANTHROPIC_API_KEY = "anthropic-key";
226+
227+
yield* Effect.gen(function* () {
228+
const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
229+
const config = yield* configuration.resolve;
230+
231+
assert.equal(config.executablePath, "wsl.exe");
232+
assert.equal(config.env.OPENAI_API_KEY, "openai-key");
233+
assert.equal(config.env.ANTHROPIC_API_KEY, "anthropic-key");
234+
assert.equal(
235+
config.env.WSLENV,
236+
"GOPATH/p:OPENAI_API_KEY/u:EMPTY:AZURE_DEVOPS_EXT_PAT/u:ANTHROPIC_API_KEY",
237+
);
238+
}).pipe(
239+
Effect.provide(
240+
DesktopBackendConfiguration.layer.pipe(
241+
Layer.provideMerge(serverExposureLayer),
242+
Layer.provideMerge(
243+
DesktopAppSettings.layerTest({
244+
...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
245+
wslMode: "wsl",
246+
}),
247+
),
248+
Layer.provideMerge(
249+
DesktopWslEnvironment.layerTest({
250+
isAvailable: true,
251+
windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
252+
}),
253+
),
254+
Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
255+
),
256+
),
257+
);
258+
} finally {
259+
restoreEnv("WSLENV", previousWslEnv);
260+
restoreEnv("OPENAI_API_KEY", previousOpenAiKey);
261+
restoreEnv("ANTHROPIC_API_KEY", previousAnthropicKey);
262+
}
263+
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
264+
);
201265
});

apps/desktop/src/backend/DesktopBackendConfiguration.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,39 @@ const WSL_FORWARDED_ENV_NAMES = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"] as const
5757
const backendChildEnvPatch = (): Record<string, string | undefined> =>
5858
Object.fromEntries(DESKTOP_BACKEND_ENV_NAMES.map((name) => [name, undefined]));
5959

60+
const getWslEnvEntryName = (entry: string): string => {
61+
const slashIndex = entry.indexOf("/");
62+
return slashIndex === -1 ? entry : entry.slice(0, slashIndex);
63+
};
64+
65+
const mergeWslEnv = (
66+
existingWslEnv: string | undefined,
67+
forwardedEnvNames: ReadonlyArray<string>,
68+
): string | undefined => {
69+
const entries: string[] = [];
70+
const seenNames = new Set<string>();
71+
72+
for (const rawEntry of existingWslEnv?.split(":") ?? []) {
73+
const entry = rawEntry.trim();
74+
if (entry.length === 0) continue;
75+
76+
const name = getWslEnvEntryName(entry);
77+
if (name.length === 0 || seenNames.has(name)) continue;
78+
79+
seenNames.add(name);
80+
entries.push(entry);
81+
}
82+
83+
for (const name of forwardedEnvNames) {
84+
if (seenNames.has(name)) continue;
85+
86+
seenNames.add(name);
87+
entries.push(name);
88+
}
89+
90+
return entries.length > 0 ? entries.join(":") : undefined;
91+
};
92+
6093
const { logWarning: logBackendConfigurationWarning } = DesktopObservability.makeComponentLogger(
6194
"desktop-backend-configuration",
6295
);
@@ -249,7 +282,6 @@ const resolveBackendStartConfig = Effect.fn("desktop.backendConfiguration.resolv
249282
forwardedEnvNames.push(name);
250283
}
251284
}
252-
const wslEnvNames = [...new Set(forwardedEnvNames)];
253285

254286
// Build an explicit copy of process.env minus T3CODE_HOME (dev-runner
255287
// exports the Windows-side base dir for the local backend; if it leaks
@@ -261,6 +293,7 @@ const resolveBackendStartConfig = Effect.fn("desktop.backendConfiguration.resolv
261293
if (key === "T3CODE_HOME") continue;
262294
parentEnvWithoutT3Home[key] = value;
263295
}
296+
const wslEnv = mergeWslEnv(parentEnvWithoutT3Home.WSLENV, forwardedEnvNames);
264297

265298
const baseConfig = {
266299
executablePath: "wsl.exe",
@@ -270,7 +303,7 @@ const resolveBackendStartConfig = Effect.fn("desktop.backendConfiguration.resolv
270303
...parentEnvWithoutT3Home,
271304
...backendChildEnvPatch(),
272305
...forwardedEnv,
273-
...(wslEnvNames.length > 0 ? { WSLENV: wslEnvNames.join(":") } : {}),
306+
...(wslEnv !== undefined ? { WSLENV: wslEnv } : {}),
274307
},
275308
// env is already a complete process.env minus T3CODE_HOME; pass it
276309
// verbatim instead of letting the spawner re-merge process.env on top.

0 commit comments

Comments
 (0)