Skip to content
Merged
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
108 changes: 69 additions & 39 deletions packages/shared/src/relayClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const layerHostRuntime = (env: Record<string, string> = {}, platform: NodeJS.Pla
Layer.mergeAll(
Layer.succeed(HostProcess.Platform, platform),
Layer.succeed(HostProcess.Architecture, "x64"),
Layer.succeed(HostProcess.Environment, env),
ConfigProvider.layer(ConfigProvider.fromEnv({ env })),
);

Expand Down Expand Up @@ -466,50 +467,77 @@ describe("RelayClient", () => {
);
});

it.effect.skipIf(windowsHost)(
"observes PATH changes after the manager has been constructed",
() => {
const env = { PATH: "" };
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-cloudflared-test-",
});
const binDir = `${baseDir}/bin`;
const executablePath = `${binDir}/cloudflared`;
const manager = yield* RelayClient.makeCloudflaredRelayClient({
baseDir,
});
it.effect("observes hydrated PATH changes after the manager has been constructed", () => {
const platform = windowsHost ? "win32" : "linux";
const executableName = windowsHost ? "cloudflared.exe" : "cloudflared";
const configEnv = { PATH: "" };
const hostEnv = { PATH: "" };
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-cloudflared-test-",
});
const binDir = `${baseDir}/bin`;
const executablePath = `${binDir}/${executableName}`;
const resolvedExecutablePath = windowsHost
? executablePath.replaceAll("/", "\\")
: executablePath;
const manager = yield* RelayClient.makeCloudflaredRelayClient({
baseDir,
});

expect(yield* manager.resolve).toEqual({
status: "missing",
version: RelayClient.CLOUDFLARED_VERSION,
});
const resolveWithStaleConfig = manager.resolve.pipe(
Effect.provideService(
ConfigProvider.ConfigProvider,
ConfigProvider.fromEnv({ env: configEnv }),
),
Effect.provideService(HostProcess.Environment, hostEnv),
);

yield* fileSystem.makeDirectory(binDir);
yield* fileSystem.writeFileString(executablePath, "cloudflared");
yield* fileSystem.chmod(executablePath, 0o755);
env.PATH = binDir;
expect(yield* resolveWithStaleConfig).toEqual({
status: "missing",
version: RelayClient.CLOUDFLARED_VERSION,
});

expect(yield* manager.resolve).toEqual({
status: "available",
executablePath,
source: "path",
version: RelayClient.CLOUDFLARED_VERSION,
});
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
layerHttpClient(new Uint8Array()),
layerSpawner([]),
layerHostRuntime(env),
),
yield* fileSystem.makeDirectory(binDir);
yield* fileSystem.writeFileString(executablePath, "cloudflared");
if (!windowsHost) yield* fileSystem.chmod(executablePath, 0o755);
hostEnv.PATH = binDir;

expect(yield* resolveWithStaleConfig).toEqual({
status: "available",
executablePath: resolvedExecutablePath,
source: "path",
version: RelayClient.CLOUDFLARED_VERSION,
});

const resolveWithOverride = manager.resolve.pipe(
Effect.provideService(
ConfigProvider.ConfigProvider,
ConfigProvider.fromEnv({
env: { PATH: "", T3CODE_CLOUDFLARED_PATH: executablePath },
}),
),
Effect.provideService(HostProcess.Environment, { PATH: "" }),
);
},
);
expect(yield* resolveWithOverride).toEqual({
status: "available",
executablePath,
source: "override",
version: RelayClient.CLOUDFLARED_VERSION,
});
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
layerHttpClient(new Uint8Array()),
layerSpawner([]),
layerHostRuntime(configEnv, platform),
),
),
);
});

describe("version selection", () => {
const run = <A, E>(
Expand Down Expand Up @@ -553,6 +581,7 @@ describe("RelayClient", () => {
ConfigProvider.ConfigProvider,
ConfigProvider.fromEnv({ env }),
),
Effect.provideService(HostProcess.Environment, env),
),
).toEqual({
status: "available",
Expand Down Expand Up @@ -618,6 +647,7 @@ describe("RelayClient", () => {
ConfigProvider.ConfigProvider,
ConfigProvider.fromEnv({ env: { PATH: `${baseDir}/bin` } }),
),
Effect.provideService(HostProcess.Environment, { PATH: `${baseDir}/bin` }),
);

expect(yield* resolveWith).toMatchObject({ source: "managed", version: "2026.1.0" });
Expand Down
7 changes: 4 additions & 3 deletions packages/shared/src/relayClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ const trimmedString = (name: string) =>

const CloudflaredConfig = Config.all({
executableOverride: trimmedString(CLOUDFLARED_PATH_ENV_NAME),
path: trimmedString("PATH"),
});

export interface CloudflaredRelayClientOptions {
Expand Down Expand Up @@ -347,8 +346,10 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function
});

const resolvePathExecutable = Effect.gen(function* () {
const config = yield* loadCloudflaredConfig;
const pathValue = Option.getOrUndefined(config.path);
// The default ConfigProvider is cached the first time any Config is read.
// Server startup reads its config before fixPath hydrates the environment,
// so reading PATH through Config here can retain the service's stale PATH.
const pathValue = (yield* HostProcess.Environment).PATH?.trim();
if (!pathValue) return null;
const delimiter = platform === "win32" ? ";" : ":";
for (const directory of pathValue.split(delimiter)) {
Expand Down
Loading