import { assert, describe, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Result from "effect/Result"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import { baseSshArgs, getLastNonEmptyOutputLine, parseSshResolveOutput, resolveRemoteT3CliPackageSpec, runSshCommand, } from "./command.ts"; import { SshCommandExitError, SshCommandSpawnError } from "./errors.ts"; const encoder = new TextEncoder(); const makeFailedProcess = (input: { readonly stdout: string; readonly stderr?: string }) => { const stdoutStream = Stream.make(encoder.encode(input.stdout)); const stderrStream = input.stderr ? Stream.make(encoder.encode(input.stderr)) : Stream.empty; return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(123), stdout: stdoutStream, stderr: stderrStream, all: Stream.empty, exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(1)), isRunning: Effect.succeed(false), kill: () => Effect.void, stdin: Sink.drain, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, unref: Effect.succeed(Effect.void), }); }; const makeNeverFinishingProcess = () => { let finish: ((exitCode: ChildProcessSpawner.ExitCode) => void) | null = null; return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(123), stdout: Stream.empty, stderr: Stream.empty, all: Stream.empty, exitCode: Effect.callback((resume) => { finish = (exitCode) => resume(Effect.succeed(exitCode)); return Effect.sync(() => { finish = null; }); }), isRunning: Effect.succeed(true), kill: () => Effect.sync(() => { finish?.(ChildProcessSpawner.ExitCode(143)); }), stdin: Sink.drain, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, unref: Effect.succeed(Effect.void), }); }; describe("ssh command", () => { it.effect("parses resolved ssh config output into a target", () => Effect.sync(() => { assert.deepEqual( parseSshResolveOutput( "devbox", ["hostname devbox.example.com", "user julius", "port 2222", ""].join("\n"), ), { alias: "devbox", hostname: "devbox.example.com", username: "julius", port: 2222, }, ); }), ); it.effect("builds interactive ssh args without forcing batch mode", () => Effect.sync(() => { assert.deepEqual( baseSshArgs( { alias: "devbox", hostname: "devbox.example.com", username: "julius", port: 2222, }, { batchMode: "no" }, ), ["-o", "BatchMode=no", "-o", "ConnectTimeout=10", "-p", "2222"], ); }), ); it.effect("resolves the remote t3 package spec from the desktop release channel", () => Effect.sync(() => { assert.equal( resolveRemoteT3CliPackageSpec({ appVersion: "0.0.17", updateChannel: "latest", }), "t3@0.0.17", ); assert.equal( resolveRemoteT3CliPackageSpec({ appVersion: "0.0.17-nightly.20260415.44", updateChannel: "nightly", }), "t3@0.0.17-nightly.20260415.44", ); assert.equal( resolveRemoteT3CliPackageSpec({ appVersion: "0.0.0-dev", updateChannel: "nightly", isDevelopment: true, }), "t3@nightly", ); assert.equal( resolveRemoteT3CliPackageSpec({ appVersion: "0.0.0-dev", updateChannel: "latest", isDevelopment: true, }), "t3@nightly", ); }), ); it.effect("reads the last non-empty ssh output line", () => Effect.sync(() => { assert.equal( getLastNonEmptyOutputLine( ["Welcome to the host", "", '{"credential":"pairing-token"}', ""].join("\n"), ), '{"credential":"pairing-token"}', ); }), ); it.effect("records bounded output diagnostics for non-zero command failures", () => { const spawner = ChildProcessSpawner.make(() => Effect.succeed(makeFailedProcess({ stdout: "Pairing token creation failed\n" })), ); const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer); return Effect.gen(function* () { const result = yield* Effect.result( runSshCommand( { alias: "devbox", hostname: "devbox.example.com", username: "julius", port: 2222, }, { remoteCommandArgs: ["sh", "-s"] }, ), ); assert.isTrue(Result.isFailure(result)); if (Result.isFailure(result)) { assert.instanceOf(result.failure, SshCommandExitError); assert.equal(result.failure.message, "SSH command failed for julius@devbox (exit 1)."); assert.equal(result.failure.command, "ssh"); assert.equal(result.failure.argumentCount, 9); assert.equal( result.failure.stdoutBytes, encoder.encode("Pairing token creation failed\n").length, ); assert.equal(result.failure.stderrBytes, 0); assert.isFalse("stdout" in result.failure); assert.isFalse("stderr" in result.failure); } const spawnCause = new Error("ssh executable was not found"); const spawnError = new SshCommandSpawnError({ command: "ssh", argumentCount: 0, target: "devbox", cause: spawnCause, }); assert.strictEqual(spawnError.cause, spawnCause); assert.equal(spawnError.message, "Failed to spawn SSH command for devbox."); assert.notInclude(spawnError.message, spawnCause.message); }).pipe(Effect.provide(processLayer)); }); it.effect("does not expose credentials from non-zero command output", () => { const spawner = ChildProcessSpawner.make(() => Effect.succeed(makeFailedProcess({ stdout: '{"credential":"pairing-secret"}\n' })), ); const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer); return Effect.gen(function* () { const result = yield* Effect.result( runSshCommand( { alias: "devbox", hostname: "devbox.example.com", username: "julius", port: 2222, }, { remoteCommandArgs: ["sh", "-s"] }, ), ); assert.isTrue(Result.isFailure(result)); if (Result.isFailure(result)) { assert.instanceOf(result.failure, SshCommandExitError); assert.notInclude(result.failure.message, "pairing-secret"); assert.equal( result.failure.stdoutBytes, encoder.encode('{"credential":"pairing-secret"}\n').length, ); assert.isFalse("stdout" in result.failure); } }).pipe(Effect.provide(processLayer)); }); it.effect("fails commands that never finish", () => { const spawner = ChildProcessSpawner.make(() => Effect.succeed(makeNeverFinishingProcess())); const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer()); return Effect.gen(function* () { const fiber = yield* Effect.forkChild( Effect.result( runSshCommand( { alias: " ", hostname: "devbox.example.com", username: "julius", port: 2222, }, { timeoutMs: 1 }, ), ), ); yield* Effect.yieldNow; yield* TestClock.adjust(Duration.millis(1)); const result = yield* Fiber.join(fiber); assert.isTrue(Result.isFailure(result)); if (Result.isFailure(result)) { assert.equal( result.failure.message, "SSH command timed out after 1ms for devbox.example.com.", ); } }).pipe(Effect.provide(processLayer)); }); });