Skip to content
Open
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
9 changes: 6 additions & 3 deletions apps/desktop/src/syncShellEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readEnvironmentFromLoginShell, ShellEnvironmentReader } from "@t3tools/shared/shell";
import { readEnvironmentFromLoginShell, resolveLoginShell, ShellEnvironmentReader } from "@t3tools/shared/shell";

export function syncShellEnvironment(
env: NodeJS.ProcessEnv = process.env,
Expand All @@ -7,10 +7,13 @@ export function syncShellEnvironment(
readEnvironment?: ShellEnvironmentReader;
} = {},
): void {
if ((options.platform ?? process.platform) !== "darwin") return;
const platform = options.platform ?? process.platform;
if (platform !== "darwin" && platform !== "linux") return;

try {
const shell = env.SHELL ?? "/bin/zsh";
const shell = resolveLoginShell(platform, env.SHELL);
if (!shell) return;

const shellEnvironment = (options.readEnvironment ?? readEnvironmentFromLoginShell)(shell, [
"PATH",
"SSH_AUTH_SOCK",
Expand Down
20 changes: 19 additions & 1 deletion apps/server/src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Server, type ServerShape } from "./wsServer";

const start = vi.fn(() => undefined);
const stop = vi.fn(() => undefined);
const fixPath = vi.fn(() => undefined);
let resolvedConfig: ServerConfigShape | null = null;
const serverStart = Effect.acquireRelease(
Effect.gen(function* () {
Expand All @@ -34,7 +35,7 @@ const findAvailablePort = vi.fn((preferred: number) => Effect.succeed(preferred)
const testLayer = Layer.mergeAll(
Layer.succeed(CliConfig, {
cwd: "/tmp/t3-test-workspace",
fixPath: Effect.void,
fixPath: Effect.sync(fixPath),
resolveStaticDir: Effect.undefined,
} satisfies CliConfigShape),
Layer.succeed(NetService, {
Expand Down Expand Up @@ -80,6 +81,7 @@ beforeEach(() => {
resolvedConfig = null;
start.mockImplementation(() => undefined);
stop.mockImplementation(() => undefined);
fixPath.mockImplementation(() => undefined);
findAvailablePort.mockImplementation((preferred: number) => Effect.succeed(preferred));
});

Expand Down Expand Up @@ -233,6 +235,22 @@ it.layer(testLayer)("server CLI command", (it) => {
}),
);

it.effect("hydrates PATH before server startup", () =>
Effect.gen(function* () {
yield* runCli([]);

assert.equal(fixPath.mock.calls.length, 1);
assert.equal(start.mock.calls.length, 1);
const fixPathOrder = fixPath.mock.invocationCallOrder[0];
const startOrder = start.mock.invocationCallOrder[0];
assert.isTrue(typeof fixPathOrder === "number" && typeof startOrder === "number");
if (typeof fixPathOrder !== "number" || typeof startOrder !== "number") {
throw new Error("Expected fixPath and start to be called");
}
assert.isTrue(fixPathOrder < startOrder);
}),
);

it.effect("records a startup heartbeat with thread/project counts", () =>
Effect.gen(function* () {
const recordTelemetry = vi.fn(
Expand Down
10 changes: 8 additions & 2 deletions apps/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,18 @@ const ServerConfigLive = (input: CliInput) =>
}),
);

const PathHydrationLive = Layer.effectDiscard(
Effect.gen(function* () {
const cliConfig = yield* CliConfig;
yield* cliConfig.fixPath;
}),
);

const LayerLive = (input: CliInput) =>
Layer.empty.pipe(
Layer.provideMerge(makeServerRuntimeServicesLayer()),
Layer.provideMerge(makeServerProviderLayer()),
Layer.provideMerge(PathHydrationLive),
Layer.provideMerge(ProviderHealthLive),
Layer.provideMerge(SqlitePersistence.layerConfig),
Layer.provideMerge(ServerLoggerLive),
Expand Down Expand Up @@ -237,10 +245,8 @@ export const recordStartupHeartbeat = Effect.gen(function* () {

const makeServerProgram = (input: CliInput) =>
Effect.gen(function* () {
const cliConfig = yield* CliConfig;
const { start, stopSignal } = yield* Server;
const openDeps = yield* Open;
yield* cliConfig.fixPath;

const config = yield* ServerConfig;

Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/os-jank.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as OS from "node:os";
import { Effect, Path } from "effect";
import { readPathFromLoginShell } from "@t3tools/shared/shell";
import { readPathFromLoginShell, resolveLoginShell } from "@t3tools/shared/shell";

export function fixPath(): void {
if (process.platform !== "darwin") return;

try {
const shell = process.env.SHELL ?? "/bin/zsh";
const shell = resolveLoginShell(process.platform, process.env.SHELL);
if (!shell) return;
const result = readPathFromLoginShell(shell);
if (result) {
process.env.PATH = result;
Expand Down
20 changes: 20 additions & 0 deletions packages/shared/src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ type ExecFileSyncLike = (
options: { encoding: "utf8"; timeout: number },
) => string;

export function resolveLoginShell(
platform: NodeJS.Platform,
shell: string | undefined,
): string | undefined {
const trimmedShell = shell?.trim();
if (trimmedShell) {
return trimmedShell;
}

if (platform === "darwin") {
return "/bin/zsh";
}

if (platform === "linux") {
return "/bin/bash";
}

return undefined;
}

export function extractPathFromShellOutput(output: string): string | null {
const startIndex = output.indexOf(PATH_CAPTURE_START);
if (startIndex === -1) return null;
Expand Down