#!/usr/bin/env npx tsx import * as grpc from "@grpc/grpc-js" import { ReflectionService } from "@grpc/reflection" import * as health from "grpc-health-check" import * as os from "os" import { type DiffServiceServer, DiffServiceService } from "../src/generated/grpc-js/host/diff" import { type EnvServiceServer, EnvServiceService } from "../src/generated/grpc-js/host/env" import { type TestingServiceServer, TestingServiceService } from "../src/generated/grpc-js/host/testing" import { type WindowServiceServer, WindowServiceService } from "../src/generated/grpc-js/host/window" import { type WorkspaceServiceServer, WorkspaceServiceService } from "../src/generated/grpc-js/host/workspace" import { getPackageDefinition } from "./proto-utils.mjs" export async function startTestHostBridgeServer() { const server = new grpc.Server() // Set up health check const healthImpl = new health.HealthImplementation({ "": "SERVING" }) healthImpl.addToServer(server) // Add host bridge services using the mock implementations server.addService(WorkspaceServiceService, createMockService("WorkspaceService")) server.addService(WindowServiceService, createMockService("WindowService")) server.addService(EnvServiceService, createMockService("EnvService")) server.addService(DiffServiceService, createMockService("DiffService")) server.addService(TestingServiceService, createMockService("TestingService")) // Load package definition for reflection service const packageDefinition = await getPackageDefinition() // Filter service names to only include host services const hostBridgeServiceNames = Object.keys(packageDefinition).filter( (name) => name.startsWith("host.") || name.startsWith("grpc.health"), ) const reflection = new ReflectionService(packageDefinition, { services: hostBridgeServiceNames, }) reflection.addToServer(server) const bindAddress = process.env.HOST_BRIDGE_ADDRESS || `127.0.0.1:26041` server.bindAsync(bindAddress, grpc.ServerCredentials.createInsecure(), (err) => { if (err) { console.error(`Failed to bind test host bridge server to ${bindAddress}:`, err) process.exit(1) } server.start() console.log(`Test HostBridge gRPC server listening on ${bindAddress}`) }) } /** * Creates a mock gRPC service implementation using Proxy * @param serviceName Name of the service for logging * @returns A proxy that implements the service interface */ function createMockService(serviceName: string): T { const handler: ProxyHandler = { get(_target, prop) { // Return a function that handles the gRPC call return (call: any, callback: any) => { console.log(`Hostbridge: ${serviceName}.${String(prop)} called with:`, call.request) // Special cases that need specific return values switch (prop) { case "getWorkspacePaths": const workspaceDir = process.env.TEST_HOSTBRIDGE_WORKSPACE_DIR || "/test-workspace" callback(null, { paths: [workspaceDir], }) return case "getMachineId": callback(null, { value: "fake-machine-id-" + os.hostname(), }) return case "getTelemetrySettings": callback(null, { isEnabled: 2, // Setting.DISABLED errorLevel: "all", }) return case "clipboardReadText": callback(null, { value: "", }) return case "getWebviewHtml": callback(null, { html: "Fake Webview", }) return case "showTextDocument": callback(null, { document_path: call.request?.path || "", view_column: 1, is_active: true, }) return case "openDiff": callback(null, { diff_id: "fake-diff-" + Date.now(), }) return case "getDocumentText": callback(null, { content: "", }) return case "getOpenTabs": case "getVisibleTabs": case "showOpenDialogue": callback(null, { paths: [], }) return case "getDiagnostics": callback(null, { file_diagnostics: [], }) return // For streaming methods (like subscribeToTelemetrySettings) case "subscribeToTelemetrySettings": // Just end the stream immediately call.end() return } // Default: return empty object for all other methods callback(null, {}) } }, } return new Proxy({} as T, handler) } if (require.main === module) { startTestHostBridgeServer().catch((err) => { console.error("Failed to start test host bridge server:", err) process.exit(1) }) }