113 lines · 3.5 KB · TypeScript
RawDownload
import * as NodeOS from "node:os";import * as Data from "effect/Data";import * as Effect from "effect/Effect";import * as Path from "effect/Path";import {  readPathFromLoginShell,  readEnvironmentFromWindowsShell,  resolveWindowsEnvironment,  type CommandAvailabilityOptions,  type WindowsShellEnvironmentReader,  listLoginShellCandidates,  mergePathEntries,  readPathFromLaunchctl,} from "@t3tools/shared/shell"; type WindowsCommandAvailabilityChecker = (  command: string,  options?: CommandAvailabilityOptions,) => Effect.Effect<boolean, never>; class ShellPathReadError extends Data.TaggedError("ShellPathReadError")<{  readonly cause: unknown;}> {} function logPathHydrationWarning(message: string, error?: unknown): void {  process.stderr.write(    `[server] ${message} ${error instanceof Error ? error.message : (error ?? "")}\n`,  );} export const fixPath = Effect.fn("fixPath")(function* (  options: {    env?: NodeJS.ProcessEnv;    platform?: NodeJS.Platform;    readPath?: typeof readPathFromLoginShell;    readWindowsEnvironment?: WindowsShellEnvironmentReader;    isWindowsCommandAvailable?: WindowsCommandAvailabilityChecker;    readLaunchctlPath?: typeof readPathFromLaunchctl;    userShell?: string;    logWarning?: (message: string, error?: unknown) => void;  } = {},) {  const platform = options.platform ?? process.platform;  const env = options.env ?? process.env;  const logWarning = options.logWarning ?? logPathHydrationWarning;  const readPath = options.readPath ?? readPathFromLoginShell;   yield* Effect.gen(function* () {    if (platform === "win32") {      const repairedEnvironment = yield* resolveWindowsEnvironment(env, {        readEnvironment: options.readWindowsEnvironment ?? readEnvironmentFromWindowsShell,        ...(options.isWindowsCommandAvailable          ? { commandAvailable: options.isWindowsCommandAvailable }          : {}),      });      for (const [key, value] of Object.entries(repairedEnvironment)) {        if (value !== undefined) {          env[key] = value;        }      }      return;    }     if (platform !== "darwin" && platform !== "linux") return;     let shellPath: string | undefined;    for (const shell of listLoginShellCandidates(platform, env.SHELL, options.userShell)) {      shellPath = yield* Effect.try({        try: () => readPath(shell),        catch: (error) => new ShellPathReadError({ cause: error }),      }).pipe(        Effect.catchTag("ShellPathReadError", (error) =>          Effect.sync(() => {            logWarning(`Failed to read PATH from login shell ${shell}.`, error.cause);            return undefined;          }),        ),      );       if (shellPath) {        break;      }    }     const launchctlPath =      platform === "darwin" && !shellPath        ? (options.readLaunchctlPath ?? readPathFromLaunchctl)()        : undefined;    const mergedPath = mergePathEntries(shellPath ?? launchctlPath, env.PATH, platform);    if (mergedPath) {      env.PATH = mergedPath;    }  });}); export const expandHomePath = Effect.fn(function* (input: string) {  const { join } = yield* Path.Path;  if (input === "~") {    return NodeOS.homedir();  }  if (input.startsWith("~/") || input.startsWith("~\\")) {    return join(NodeOS.homedir(), input.slice(2));  }  return input;}); export const resolveBaseDir = Effect.fn(function* (raw: string | undefined) {  const { join, resolve } = yield* Path.Path;  if (!raw || raw.trim().length === 0) {    return join(NodeOS.homedir(), ".t3");  }  return resolve(yield* expandHomePath(raw.trim()));});