| 1 | import { isAbsolute, normalize, resolve, sep } from "node:path"; |
| 2 | |
| 3 | export interface HomeEnvironment { |
| 4 | env: NodeJS.ProcessEnv; |
| 5 | platform: NodeJS.Platform; |
| 6 | homedir(): string; |
| 7 | cwd(): string; |
| 8 | } |
| 9 | |
| 10 | const VAR_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g; |
| 11 | |
| 12 | // path.normalize keeps a trailing separator; Go's filepath.Clean does not. |
| 13 | function clean(path: string): string { |
| 14 | const normalized = normalize(path); |
| 15 | return normalized.length > 1 && normalized.endsWith(sep) && !/^[A-Za-z]:\\$/.test(normalized) ? normalized.slice(0, -1) : normalized; |
| 16 | } |
| 17 | |
| 18 | function expandVars(value: string, env: NodeJS.ProcessEnv): string { |
| 19 | if (!value.includes("${")) return value; |
| 20 | return value.replace(VAR_REF, (_match, name: string, fallback: string | undefined) => { |
| 21 | const found = env[name]; |
| 22 | if (found) return found; |
| 23 | return fallback ?? ""; |
| 24 | }); |
| 25 | } |
| 26 | |
| 27 | function userHome(input: HomeEnvironment): string { |
| 28 | const fromEnv = input.platform === "win32" ? input.env.USERPROFILE : input.env.HOME; |
| 29 | if (fromEnv && fromEnv.trim() !== "") return fromEnv; |
| 30 | try { |
| 31 | return input.homedir(); |
| 32 | } catch { |
| 33 | return ""; |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | function cleanEnvDir(input: HomeEnvironment, name: string): string { |
| 38 | let dir = (input.env[name] ?? "").trim(); |
| 39 | if (dir === "") return ""; |
| 40 | dir = expandVars(dir, input.env); |
| 41 | if (dir === "~") { |
| 42 | const home = userHome(input); |
| 43 | if (home !== "") dir = home; |
| 44 | } else if (dir.startsWith("~/") || dir.startsWith("~\\")) { |
| 45 | const home = userHome(input); |
| 46 | if (home !== "") dir = home + sep + dir.slice(2); |
| 47 | } |
| 48 | if (!isAbsolute(dir)) dir = resolve(input.cwd(), dir); |
| 49 | return clean(dir); |
| 50 | } |
| 51 | |
| 52 | // Mirrors internal/config.ReasonixHomeDir so the hello `instance.home` matches |
| 53 | // what the Go service computes for the same environment. |
| 54 | export function reasonixHome(input: HomeEnvironment): string { |
| 55 | const explicit = cleanEnvDir(input, "REASONIX_HOME"); |
| 56 | if (explicit !== "") return explicit; |
| 57 | const home = userHome(input); |
| 58 | if (input.platform === "win32") { |
| 59 | const appData = (input.env.APPDATA ?? "").trim(); |
| 60 | if (appData !== "") return clean(appData + sep + "reasonix"); |
| 61 | if (home !== "") return clean(home + sep + "AppData" + sep + "Roaming" + sep + "reasonix"); |
| 62 | return ""; |
| 63 | } |
| 64 | if (home !== "") return clean(home + sep + ".reasonix"); |
| 65 | const xdg = (input.env.XDG_CONFIG_HOME ?? "").trim(); |
| 66 | if (xdg !== "") return clean(xdg + sep + "reasonix"); |
| 67 | return ""; |
| 68 | } |
| 69 |