1import {
2 type DeviceHostSummary,
3 type DevicePlatformAvailability,
4 type EnvironmentId,
5 SshDeviceHostConfig,
6} from "@t3tools/contracts";
7import * as Schema from "effect/Schema";
8
9export interface DeviceHostCheckTarget {
10 environmentId: EnvironmentId;
11 label: string;
12 connected: boolean;
13}
14export type DeviceHostCheck =
15 | { status: "pending" }
16 | { status: "local" }
17 | {
18 status: "connected";
19 platforms: ReadonlyArray<DevicePlatformAvailability>;
20 tools?: DeviceHostSummary["tools"];
21 }
22 | { status: "failed"; error: string };
23
24const decodeDeviceHostDraft = Schema.decodeUnknownOption(SshDeviceHostConfig);
25
26export function parseDeviceHostDraft(host: SshDeviceHostConfig) {
27 const { identityFile, ...rest } = host;
28 return decodeDeviceHostDraft({
29 ...rest,
30 ...(identityFile?.trim() ? { identityFile: identityFile.trim() } : {}),
31 });
32}
33
34export function deviceHostConnectionKey(host: SshDeviceHostConfig) {
35 return JSON.stringify([host.target.trim(), host.port, host.identityFile?.trim() || undefined]);
36}
37
38/** Each environment settles independently so one failure cannot hide the other results. */
39export async function checkDeviceHostConnections(
40 targets: ReadonlyArray<DeviceHostCheckTarget>,
41 host: SshDeviceHostConfig,
42 probe: (environmentId: EnvironmentId, host: SshDeviceHostConfig) => Promise<DeviceHostSummary>,
43 report: (environmentId: EnvironmentId, result: DeviceHostCheck) => void,
44) {
45 await Promise.all(
46 targets.map(async (target) => {
47 report(target.environmentId, { status: "pending" });
48 try {
49 if (!target.connected) throw new Error("Environment disconnected");
50 const result = await probe(target.environmentId, host);
51 report(
52 target.environmentId,
53 result.kind === "local"
54 ? { status: "local" }
55 : {
56 status: "connected",
57 platforms: result.platforms,
58 ...(result.tools ? { tools: result.tools } : {}),
59 },
60 );
61 } catch (error) {
62 report(target.environmentId, {
63 status: "failed",
64 error: error instanceof Error ? error.message : String(error),
65 });
66 }
67 }),
68 );
69}