diff --git a/src/application/SupportedClients.ts b/src/application/SupportedClients.ts index fef2fd32d9..241c36f18f 100644 --- a/src/application/SupportedClients.ts +++ b/src/application/SupportedClients.ts @@ -8,7 +8,10 @@ import { } from "./HermesClientHome.js"; import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js"; import { resolveWindowsClaudeDesktopLocation } from "./ClaudeDesktopConfigLocation.js"; -import type { MsixPackageView } from "./MsixPackageStorage.js"; +import { + type MsixPackageView, + windowsAppDataRoots, +} from "./MsixPackageStorage.js"; /** One supported client configuration location. */ export interface SetupClient { @@ -117,11 +120,7 @@ const vscodeUserDirectory = ({ env, }: ClientPathContext): string => { if (platform === "win32") - return join( - env.APPDATA ?? join(home, "AppData", "Roaming"), - "Code", - "User", - ); + return join(windowsAppDataRoots(home, env).roaming, "Code", "User"); if (platform === "darwin") return join(home, "Library", "Application Support", "Code", "User"); return join(xdgConfigHome({ home, platform, env }), "Code", "User"); @@ -133,11 +132,7 @@ const traeUserDirectory = ({ env, }: ClientPathContext): string => { if (platform === "win32") - return join( - env.APPDATA ?? join(home, "AppData", "Roaming"), - "Trae", - "User", - ); + return join(windowsAppDataRoots(home, env).roaming, "Trae", "User"); if (platform === "darwin") return join(home, "Library", "Application Support", "Trae", "User"); return join(xdgConfigHome({ home, platform, env }), "Trae", "User"); @@ -479,7 +474,7 @@ const geminiSettings = ({ const devinDirectory = ({ home, platform, env }: ClientPathContext): string => platform === "win32" - ? join(env.APPDATA ?? join(home, "AppData", "Roaming"), "devin") + ? join(windowsAppDataRoots(home, env).roaming, "devin") : join(env.XDG_CONFIG_HOME || join(home, ".config"), "devin"); /** Stable product metadata used to derive setup discovery and documentation. */ diff --git a/tests/boundary/filesystem/windowsAppDataClientPaths.test.ts b/tests/boundary/filesystem/windowsAppDataClientPaths.test.ts new file mode 100644 index 0000000000..02c559aa03 --- /dev/null +++ b/tests/boundary/filesystem/windowsAppDataClientPaths.test.ts @@ -0,0 +1,30 @@ +import { join } from "node:path"; + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { supportedClients } from "../../../src/application/SupportedClients.js"; +import { clearClientLocationEnvironment } from "../../fixtures/clientEnvironment.js"; + +beforeEach(clearClientLocationEnvironment); +afterEach(() => vi.unstubAllEnvs()); + +const home = "C:\\Users\\a"; +const roaming = join(home, "AppData", "Roaming"); + +const configPath = ( + name: string, + env: Parameters[2], +) => + supportedClients(home, "win32", env).find((client) => client.name === name) + ?.configPath; + +describe("Windows AppData client paths", () => { + it.each([ + ["vscode", join(roaming, "Code", "User", "mcp.json")], + ["trae", join(roaming, "Trae", "User", "mcp.json")], + ["devin", join(roaming, "devin", "mcp_config.json")], + ])("treats an empty APPDATA as unset for %s", (name, expected) => { + expect(configPath(name, {})).toBe(expected); + expect(configPath(name, { APPDATA: "" })).toBe(expected); + }); +});