Skip to content

Commit a08e55c

Browse files
committed
fix(server): Provider updates always fail on Windows
1 parent 4f0f24f commit a08e55c

2 files changed

Lines changed: 69 additions & 3 deletions

File tree

apps/server/src/provider/providerMaintenanceRunner.test.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import * as Schema from "effect/Schema";
1616
import * as Sink from "effect/Sink";
1717
import * as Stream from "effect/Stream";
1818
import { HttpClient, HttpClientResponse } from "effect/unstable/http";
19-
import { ChildProcessSpawner } from "effect/unstable/process";
19+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2020

2121
import { ProviderRegistry, type ProviderRegistryShape } from "./Services/ProviderRegistry.ts";
2222
import * as ProviderMaintenanceRunner from "./providerMaintenanceRunner.ts";
@@ -125,6 +125,7 @@ function mockSpawnerLayer(
125125
handler: (
126126
command: string,
127127
args: ReadonlyArray<string>,
128+
options: ChildProcess.CommandOptions,
128129
) => {
129130
readonly stdout?: string;
130131
readonly stderr?: string;
@@ -138,9 +139,32 @@ function mockSpawnerLayer(
138139
const childProcess = command as unknown as {
139140
readonly command: string;
140141
readonly args: ReadonlyArray<string>;
142+
readonly options: ChildProcess.CommandOptions;
141143
};
142-
return Effect.succeed(mockHandle(handler(childProcess.command, childProcess.args)));
144+
return Effect.succeed(
145+
mockHandle(handler(childProcess.command, childProcess.args, childProcess.options)),
146+
);
147+
}),
148+
);
149+
}
150+
151+
function withProcessPlatform<A, E, R>(
152+
platform: NodeJS.Platform,
153+
effect: Effect.Effect<A, E, R>,
154+
): Effect.Effect<A, E, R> {
155+
return Effect.acquireUseRelease(
156+
Effect.sync(() => {
157+
const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
158+
Object.defineProperty(process, "platform", { value: platform });
159+
return descriptor;
143160
}),
161+
() => effect,
162+
(descriptor) =>
163+
Effect.sync(() => {
164+
if (descriptor) {
165+
Object.defineProperty(process, "platform", descriptor);
166+
}
167+
}),
144168
);
145169
}
146170

@@ -319,6 +343,42 @@ describe("providerMaintenanceRunner", () => {
319343
},
320344
);
321345

346+
it.effect("runs provider update commands through a shell on Windows", () => {
347+
const calls: Array<{
348+
command: string;
349+
args: ReadonlyArray<string>;
350+
shell: ChildProcess.CommandOptions["shell"];
351+
}> = [];
352+
return withProcessPlatform(
353+
"win32",
354+
Effect.gen(function* () {
355+
const { registry } = yield* makeRegistry(baseProvider);
356+
const runner = yield* makeTestRunner(registry);
357+
358+
const result = yield* runner.updateProvider(CODEX_DRIVER);
359+
360+
assert.deepStrictEqual(calls, [
361+
{
362+
command: "npm",
363+
args: ["install", "-g", "@openai/codex@latest"],
364+
shell: true,
365+
},
366+
]);
367+
assert.strictEqual(result.providers[0]?.updateState?.status, "succeeded");
368+
}),
369+
).pipe(
370+
Effect.provide(
371+
Layer.mergeAll(
372+
latestVersionHttpClient("0.0.0"),
373+
mockSpawnerLayer((command, args, options) => {
374+
calls.push({ command, args, shell: options.shell });
375+
return { stdout: "updated" };
376+
}),
377+
),
378+
),
379+
);
380+
});
381+
322382
it.effect("updates a single provider instance without touching sibling instances", () => {
323383
const calls: Array<{ command: string; args: ReadonlyArray<string> }> = [];
324384
return Effect.gen(function* () {

apps/server/src/provider/providerMaintenanceRunner.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,13 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR
7676
const collectCommandResult = Effect.fn("ProviderMaintenanceRunner.collectCommandResult")(
7777
function* () {
7878
const child = yield* input.spawner
79-
.spawn(ChildProcess.make(input.command, [...input.args]))
79+
.spawn(
80+
ChildProcess.make(
81+
input.command,
82+
[...input.args],
83+
process.platform === "win32" ? { shell: true } : {},
84+
),
85+
)
8086
.pipe(
8187
Effect.mapError(
8288
(cause) =>

0 commit comments

Comments
 (0)