Skip to content
Draft
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
7 changes: 5 additions & 2 deletions apps/desktop/src/ssh/DesktopSshEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ import {
SshLaunchError,
SshPairingError,
SshPasswordPromptError,
SshReadinessError,
type SshReadinessError,
} from "@t3tools/ssh/errors";
import { SshEnvironmentManager, type RemoteT3RunnerOptions } from "@t3tools/ssh/tunnel";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { HttpClient } from "effect/unstable/http";
import { ChildProcessSpawner } from "effect/unstable/process";

Expand Down Expand Up @@ -79,11 +80,13 @@ function discoverDesktopSshHostsEffect(input?: { readonly homeDir?: string }) {
return discoverSshHosts(input ?? {});
}

const isSshPasswordPromptError = Schema.is(SshPasswordPromptError);

export function isDesktopSshPasswordPromptCancellation(
error: unknown,
): error is SshPasswordPromptError {
return (
error instanceof SshPasswordPromptError &&
isSshPasswordPromptError(error) &&
DesktopSshPasswordPrompts.isDesktopSshPasswordPromptCancellation(error.cause)
);
}
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/ssh/DesktopSshRemoteApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assert, describe, it } from "@effect/vitest";
import { SshHttpBridgeError } from "@t3tools/ssh/errors";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";

import * as DesktopSshRemoteApi from "./DesktopSshRemoteApi.ts";
Expand Down Expand Up @@ -31,6 +32,8 @@ function makeLayer(
);
}

const isSshHttpBridgeError = Schema.is(SshHttpBridgeError);

describe("DesktopSshRemoteApi", () => {
it.effect("fetches and decodes the remote environment descriptor", () => {
const requestUrls: string[] = [];
Expand Down Expand Up @@ -73,7 +76,7 @@ describe("DesktopSshRemoteApi", () => {

assert.instanceOf(error, DesktopSshRemoteApi.DesktopSshRemoteApiError);
assert.equal(error.operation, "fetch-environment-descriptor");
assert.equal(error.cause instanceof SshHttpBridgeError, false);
assert.equal(isSshHttpBridgeError(error.cause), false);
}).pipe(Effect.provide(layer));
});
});
149 changes: 103 additions & 46 deletions packages/ssh/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,103 @@
import * as Data from "effect/Data";

export class SshHostDiscoveryError extends Data.TaggedError("SshHostDiscoveryError")<{
readonly message: string;
readonly cause: unknown;
}> {}

export class SshInvalidTargetError extends Data.TaggedError("SshInvalidTargetError")<{
readonly message: string;
}> {}

export class SshCommandError extends Data.TaggedError("SshCommandError")<{
readonly message: string;
readonly command: readonly string[];
readonly exitCode: number | null;
readonly stderr: string;
readonly cause?: unknown;
}> {}

export class SshLaunchError extends Data.TaggedError("SshLaunchError")<{
readonly message: string;
readonly stdout: string;
readonly cause?: unknown;
}> {}

export class SshPairingError extends Data.TaggedError("SshPairingError")<{
readonly message: string;
readonly stdout: string;
readonly cause?: unknown;
}> {}

export class SshHttpBridgeError extends Data.TaggedError("SshHttpBridgeError")<{
readonly message: string;
readonly status?: number;
readonly cause?: unknown;
}> {}

export class SshReadinessError extends Data.TaggedError("SshReadinessError")<{
readonly message: string;
readonly cause?: unknown;
}> {}

export class SshPasswordPromptError extends Data.TaggedError("SshPasswordPromptError")<{
readonly message: string;
readonly cause?: unknown;
}> {}
import * as Schema from "effect/Schema";

export class SshHostDiscoveryError extends Schema.TaggedErrorClass<SshHostDiscoveryError>()(
"SshHostDiscoveryError",
{
message: Schema.String,
cause: Schema.Defect,
},
) {}

export class SshInvalidTargetError extends Schema.TaggedErrorClass<SshInvalidTargetError>()(
"SshInvalidTargetError",
{
message: Schema.String,
},
) {}

export class SshCommandError extends Schema.TaggedErrorClass<SshCommandError>()("SshCommandError", {
message: Schema.String,
command: Schema.Array(Schema.String),
exitCode: Schema.NullOr(Schema.Number),
stderr: Schema.String,
cause: Schema.optional(Schema.Defect),
}) {}

export class SshLaunchError extends Schema.TaggedErrorClass<SshLaunchError>()("SshLaunchError", {
message: Schema.String,
stdout: Schema.String,
cause: Schema.optional(Schema.Defect),
}) {}

export class SshPairingError extends Schema.TaggedErrorClass<SshPairingError>()("SshPairingError", {
message: Schema.String,
stdout: Schema.String,
cause: Schema.optional(Schema.Defect),
}) {}

export class SshHttpBridgeError extends Schema.TaggedErrorClass<SshHttpBridgeError>()(
"SshHttpBridgeError",
{
message: Schema.String,
status: Schema.optional(Schema.Number),
cause: Schema.optional(Schema.Defect),
},
) {}

export class SshReadinessProbeFailedError extends Schema.TaggedErrorClass<SshReadinessProbeFailedError>()(
"SshReadinessProbeFailedError",
{
message: Schema.String,
requestUrl: Schema.String,
cause: Schema.optional(Schema.Defect),
},
) {}

export class SshReadinessProbeTimedOutError extends Schema.TaggedErrorClass<SshReadinessProbeTimedOutError>()(
"SshReadinessProbeTimedOutError",
{
message: Schema.String,
requestUrl: Schema.String,
attempt: Schema.Number,
probeTimeoutMs: Schema.Number,
},
) {}

export class SshReadinessTimedOutError extends Schema.TaggedErrorClass<SshReadinessTimedOutError>()(
"SshReadinessTimedOutError",
{
message: Schema.String,
baseUrl: Schema.String,
requestUrl: Schema.String,
timeoutMs: Schema.Number,
intervalMs: Schema.Number,
probeTimeoutMs: Schema.Number,
attempts: Schema.Number,
cause: Schema.optional(Schema.Defect),
},
) {}

export type SshReadinessError =
| SshReadinessProbeFailedError
| SshReadinessProbeTimedOutError
| SshReadinessTimedOutError;

const isSshReadinessProbeFailedError = Schema.is(SshReadinessProbeFailedError);
const isSshReadinessProbeTimedOutError = Schema.is(SshReadinessProbeTimedOutError);
const isSshReadinessTimedOutError = Schema.is(SshReadinessTimedOutError);

export function isSshReadinessError(cause: unknown): cause is SshReadinessError {
return (
isSshReadinessProbeFailedError(cause) ||
isSshReadinessProbeTimedOutError(cause) ||
isSshReadinessTimedOutError(cause)
);
}

export class SshPasswordPromptError extends Schema.TaggedErrorClass<SshPasswordPromptError>()(
"SshPasswordPromptError",
{
message: Schema.String,
cause: Schema.optional(Schema.Defect),
},
) {}
6 changes: 3 additions & 3 deletions packages/ssh/src/tunnel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ describe("ssh tunnel scripts", () => {
Effect.result(
waitForHttpReady({
baseUrl: "http://127.0.0.1:41773/",
timeoutMs: 1_000,
intervalMs: 100,
probeTimeoutMs: 250,
timeout: Duration.seconds(1),
interval: Duration.millis(100),
probeTimeout: Duration.millis(250),
}),
),
);
Expand Down
Loading
Loading