fix(installation): type upgrade failures (#28883)

This commit is contained in:
Shoubhit Dash
2026-05-22 23:18:17 +05:30
committed by GitHub
parent dda69d77e8
commit 536ee857c6
2 changed files with 64 additions and 11 deletions

View File

@@ -14,19 +14,23 @@ function mockHttpClient(handler: (request: HttpClientRequest.HttpClientRequest)
return Layer.succeed(HttpClient.HttpClient, client)
}
function mockSpawner(handler: (cmd: string, args: readonly string[]) => string = () => "") {
function mockSpawner(
handler: (cmd: string, args: readonly string[]) => string | { code: number; stdout?: string; stderr?: string } = () =>
"",
) {
const spawner = ChildProcessSpawner.make((command) => {
const std = ChildProcess.isStandardCommand(command) ? command : undefined
const output = handler(std?.command ?? "", std?.args ?? [])
const result = handler(std?.command ?? "", std?.args ?? [])
const output = typeof result === "string" ? { code: 0, stdout: result, stderr: "" } : result
return Effect.succeed(
ChildProcessSpawner.makeHandle({
pid: ChildProcessSpawner.ProcessId(0),
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(output.code)),
isRunning: Effect.succeed(false),
kill: () => Effect.void,
stdin: { [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") } as any,
stdout: output ? Stream.make(encoder.encode(output)) : Stream.empty,
stderr: Stream.empty,
stdout: output.stdout ? Stream.make(encoder.encode(output.stdout)) : Stream.empty,
stderr: output.stderr ? Stream.make(encoder.encode(output.stderr)) : Stream.empty,
all: Stream.empty,
getInputFd: () => ({ [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") }) as any,
getOutputFd: () => Stream.empty,
@@ -46,7 +50,7 @@ function jsonResponse(body: unknown) {
function testLayer(
httpHandler: (request: HttpClientRequest.HttpClientRequest) => Response,
spawnHandler?: (cmd: string, args: readonly string[]) => string,
spawnHandler?: (cmd: string, args: readonly string[]) => string | { code: number; stdout?: string; stderr?: string },
) {
const appProcess = AppProcess.layer.pipe(Layer.provide(mockSpawner(spawnHandler)))
return Installation.layer.pipe(Layer.provide(mockHttpClient(httpHandler)), Layer.provide(appProcess))
@@ -166,4 +170,44 @@ describe("installation", () => {
}),
)
})
describe("upgrade", () => {
testEffect(
testLayer(
() => jsonResponse({}),
(cmd) => {
if (cmd === "npm") return { code: 1, stderr: "token=secret command output" }
return ""
},
),
).effect("returns sanitized typed errors for failed package upgrades", () =>
Effect.gen(function* () {
const error = yield* Effect.flip(Installation.use.upgrade("npm", "9.9.9"))
expect(error).toBeInstanceOf(Installation.UpgradeFailedError)
expect(error.stderr).toBe("Upgrade failed for npm (exit code 1).")
expect(error.message).toBe(error.stderr)
expect(error.stderr).not.toContain("secret")
expect(error.stderr).not.toContain("command output")
}),
)
testEffect(
testLayer(
() => new Response("install script with token=secret", { status: 200 }),
(cmd) => {
if (cmd === "bash") return { code: 1, stderr: "script output with token=secret" }
return ""
},
),
).effect("returns sanitized typed errors when the curl install script fails", () =>
Effect.gen(function* () {
const error = yield* Effect.flip(Installation.use.upgrade("curl", "9.9.9"))
expect(error).toBeInstanceOf(Installation.UpgradeFailedError)
expect(error.stderr).toBe("Upgrade failed for curl (exit code 1).")
expect(error.message).toBe(error.stderr)
expect(error.stderr).not.toContain("secret")
expect(error.stderr).not.toContain("script output")
}),
)
})
})