diff --git a/scripts/build-proto.mjs b/scripts/build-proto.mjs index a072beb6fd..6dde1bde2f 100755 --- a/scripts/build-proto.mjs +++ b/scripts/build-proto.mjs @@ -9,7 +9,7 @@ import chalk from "chalk" import os from "os" import { createRequire } from "module" -import { serviceNameMap, hostServiceNameMap } from "./build-proto-config.mjs" +import { serviceNameMap } from "./build-proto-config.mjs" const require = createRequire(import.meta.url) const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc") @@ -88,9 +88,6 @@ async function main() { await generateProtoBusMethodRegistrations() await generateProtoBusGrpcClientConfig() - await generateHostBridgeServiceConfig() - await generateHostBridgeMethodRegistrations() - console.log(chalk.bold.blue("Finished Protocol Buffer code generation.")) } @@ -420,163 +417,6 @@ service ${serviceClassName} { } } -/** - * Generate method registration files for host services - */ -async function generateHostBridgeMethodRegistrations() { - log_verbose(chalk.cyan("Generating host method registration files...")) - // Host service directories derived from imported hostServiceNameMap - const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) => - path.join("src/hosts/vscode/hostbridge", serviceKey), - ) - - // Parse proto files for streaming methods - const hostProtoFiles = await globby("*.proto", { cwd: path.join(PROTO_DIR, "host") }) - const streamingMethodsMap = await parseProtoForStreamingMethods(hostProtoFiles, path.join(PROTO_DIR, "host")) - - for (const serviceDir of hostServiceDirs) { - const serviceName = path.basename(serviceDir) - const fullServiceName = hostServiceNameMap[serviceName] - const streamingMethods = streamingMethodsMap.get(fullServiceName) || [] - const outputDir = path.join("src/generated/hosts/vscode/hostbridge", serviceName) - - log_verbose(chalk.cyan(`Generating method registrations for host ${serviceName}...`)) - - // Get all TypeScript files in the service directory - const files = await globby("*.ts", { cwd: serviceDir }) - - // Filter out index.ts and methods.ts - const implementationFiles = files.filter((file) => file !== "index.ts" && file !== "methods.ts") - - // Create the methods.ts file with header - let methodsContent = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY -// Generated ${SCRIPT_NAME} - -// Import all method implementations -import { registerMethod } from "./index"\n` - - // Import implementations directly - for (const file of implementationFiles) { - const baseName = path.basename(file, ".ts") - methodsContent += `import { ${baseName} } from "@hosts/vscode/hostbridge/${serviceName}/${baseName}"\n` - } - - // Add streaming methods information - if (streamingMethods.length > 0) { - methodsContent += `\n// Streaming methods for this service -export const streamingMethods = ${JSON.stringify( - streamingMethods.map((m) => m.name), - null, - 2, - )}\n` - } - - // Add registration function - methodsContent += `\n// Register all ${serviceName} service methods -export function registerAllMethods(): void { -\t// Register each method with the registry\n` - - // Add registration statements - for (const file of implementationFiles) { - const baseName = path.basename(file, ".ts") - const isStreaming = streamingMethods.some((m) => m.name === baseName) - - if (isStreaming) { - methodsContent += `\tregisterMethod("${baseName}", ${baseName}, { isStreaming: true })\n` - } else { - methodsContent += `\tregisterMethod("${baseName}", ${baseName})\n` - } - } - - // Close the function - methodsContent += `}` - - // Write the methods.ts file - const registryFile = path.join(outputDir, "methods.ts") - await writeFileWithMkdirs(registryFile, methodsContent) - log_verbose(chalk.green(`Generated ${registryFile}`)) - - // Generate index.ts file - const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1) - const indexContent = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY -// Generated by ${SCRIPT_NAME} - -import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "@hosts/vscode/hostbridge-grpc-service" -import { StreamingResponseHandler } from "@hosts/vscode/hostbridge-grpc-handler" -import { registerAllMethods } from "./methods" - -// Create ${serviceName} service registry -const ${serviceName}Service = createServiceRegistry("${serviceName}") - -// Export the method handler types and registration function -export type ${capitalizedServiceName}MethodHandler = ServiceMethodHandler -export type ${capitalizedServiceName}StreamingMethodHandler = StreamingMethodHandler -export const registerMethod = ${serviceName}Service.registerMethod - -// Export the request handlers -export const handle${capitalizedServiceName}ServiceRequest = ${serviceName}Service.handleRequest -export const handle${capitalizedServiceName}ServiceStreamingRequest = ${serviceName}Service.handleStreamingRequest -export const isStreamingMethod = ${serviceName}Service.isStreamingMethod - -// Register all ${serviceName} methods -registerAllMethods()` - - // Write the index.ts file - const indexFile = path.join(outputDir, "index.ts") - await writeFileWithMkdirs(indexFile, indexContent) - log_verbose(chalk.green(`Generated ${indexFile}`)) - } - - log_verbose(chalk.green("Host method registration files generated successfully.")) -} - -/** - * Generate a service configuration file for host services - */ -async function generateHostBridgeServiceConfig() { - log_verbose(chalk.cyan("Generating host service configuration file...")) - - const serviceImports = [] - const serviceConfigs = [] - - // Add all services from the hostServiceNameMap - for (const [dirName, fullServiceName] of Object.entries(hostServiceNameMap)) { - const capitalizedName = dirName.charAt(0).toUpperCase() + dirName.slice(1) - serviceImports.push( - `import { handle${capitalizedName}ServiceRequest, handle${capitalizedName}ServiceStreamingRequest } from "@generated/hosts/vscode/hostbridge/${dirName}/index"`, - ) - serviceConfigs.push(` - "${fullServiceName}": { - requestHandler: handle${capitalizedName}ServiceRequest, - streamingHandler: handle${capitalizedName}ServiceStreamingRequest - }`) - } - - const content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY -// Generated by ${SCRIPT_NAME} - -import { StreamingResponseHandler } from "@/hosts/vscode/hostbridge-grpc-handler" -${serviceImports.join("\n")} - -/** - * Configuration for a host service handler - */ -export interface HostServiceHandlerConfig { - requestHandler: (method: string, message: any) => Promise; - streamingHandler: (method: string, message: any, responseStream: StreamingResponseHandler, requestId?: string) => Promise; -} - -/** - * Map of host service names to their handler configurations - */ -export const hostServiceHandlers: Record = {${serviceConfigs.join(",")} -};` - - const filePath = "src/generated/hosts/vscode/hostbridge-grpc-service-config.ts" - await writeFileWithMkdirs(filePath, content) - log_verbose(chalk.green(`Generated host service configuration at ${filePath}`)) -} - async function cleanup() { // Clean up existing generated files log_verbose(chalk.cyan("Cleaning up existing generated TypeScript files...")) diff --git a/scripts/generate-host-bridge-client.mjs b/scripts/generate-host-bridge-client.mjs index 90851d83d9..906fabb1ce 100755 --- a/scripts/generate-host-bridge-client.mjs +++ b/scripts/generate-host-bridge-client.mjs @@ -6,10 +6,15 @@ import * as grpc from "@grpc/grpc-js" import * as protoLoader from "@grpc/proto-loader" import chalk from "chalk" -const IMPL_FILE = path.resolve("src/generated/standalone/host-bridge-clients.ts") -const INTERFACE_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts") const DESCRIPTOR_SET = path.resolve("dist-standalone/proto/descriptor_set.pb") +// Contains the interface definitions for the host bridge clients. +const TYPES_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts") +// Contains the ExternalHostBridgeClientManager for the external host bridge clients (using nice-grpc). +const EXTERNAL_CLIENT_FILE = path.resolve("src/generated/hosts/standalone/host-bridge-clients.ts") +// Contains the handler map for the external host bridge clients (using the custom service registry). +const VSCODE_CLIENT_FILE = path.resolve("src/generated/hosts/vscode/hostbridge-grpc-service-config.ts") + const typeNameToFQN = new Map() function addTypeNameToFqn(name, fqn) { @@ -48,24 +53,23 @@ async function main() { } } - // Generate interfaces file - await generateInterfacesFile(hostServices) - - // // Generate implementation file - await generateImplementationFile(hostServices) + await generateTypesFile(hostServices) + await generateExternalClientFile(hostServices) + await generateVscodeClientFile(hostServices) console.log(`Generated host bridge client files at:`) - console.log(`- ${INTERFACE_FILE}`) - console.log(`- ${IMPL_FILE}`) + console.log(`- ${TYPES_FILE}`) + console.log(`- ${EXTERNAL_CLIENT_FILE}`) + console.log(`- ${VSCODE_CLIENT_FILE}`) } /** * Generate the client interfaces file. */ -async function generateInterfacesFile(hostServices) { +async function generateTypesFile(hostServices) { const clientInterfaces = [] for (const [name, def] of Object.entries(hostServices)) { - const clientInterface = generateClientInterface(name, def) + const clientInterface = generateClientInterfaceType(name, def) clientInterfaces.push(clientInterface) } const content = `// GENERATED CODE -- DO NOT EDIT! @@ -76,14 +80,14 @@ import { StreamingCallbacks } from "@hosts/host-provider-types" ${clientInterfaces.join("\n\n")} ` // Write output file - await fs.mkdir(path.dirname(INTERFACE_FILE), { recursive: true }) - await fs.writeFile(INTERFACE_FILE, content) + await fs.mkdir(path.dirname(TYPES_FILE), { recursive: true }) + await fs.writeFile(TYPES_FILE, content) } /** * Generate a client interface for a service. */ -function generateClientInterface(serviceName, serviceDefinition) { +function generateClientInterfaceType(serviceName, serviceDefinition) { // Get the methods from the service definition const methods = Object.entries(serviceDefinition.service) .map(([methodName, methodDef]) => { @@ -110,9 +114,9 @@ ${methods} } /** - * Generate the client implementations file. + * Generate the external client implementations file. */ -async function generateImplementationFile(hostServices) { +async function generateExternalClientFile(hostServices) { // Generate imports const imports = [] // Add imports for the interfaces @@ -121,7 +125,7 @@ async function generateImplementationFile(hostServices) { } const clientImplementations = [] for (const [name, def] of Object.entries(hostServices)) { - clientImplementations.push(generateClientImplementation(name, def)) + clientImplementations.push(generateExternalClientSetup(name, def)) } const content = `// GENERATED CODE -- DO NOT EDIT! @@ -136,16 +140,15 @@ ${imports.join("\n")} ${clientImplementations.join("\n\n")} ` - // Write output file - await fs.mkdir(path.dirname(IMPL_FILE), { recursive: true }) - await fs.writeFile(IMPL_FILE, content) + await fs.mkdir(path.dirname(EXTERNAL_CLIENT_FILE), { recursive: true }) + await fs.writeFile(EXTERNAL_CLIENT_FILE, content) } /** * Generate a client implementation class for a service */ -function generateClientImplementation(serviceName, serviceDefinition) { +function generateExternalClientSetup(serviceName, serviceDefinition) { // Get the methods from the service definition const methods = Object.entries(serviceDefinition.service) .map(([methodName, methodDef]) => { @@ -185,6 +188,71 @@ ${methods} }` } +/** + * Generate the Vscode client setup file. + */ +async function generateVscodeClientFile(hostServices) { + const imports = [] + const clientImplementations = [] + const handlerMap = [] + for (const [serviceName, serviceDefinition] of Object.entries(hostServices)) { + const name = serviceName.replace(/Service$/, "").toLowerCase() + for (const [methodName, _methodDef] of Object.entries(serviceDefinition.service)) { + imports.push(`import { ${methodName} } from "@/hosts/vscode/hostbridge/${name}/${methodName}"`) + } + imports.push("") + + clientImplementations.push(generateVscodeClientImplementation(name, serviceDefinition)) + + handlerMap.push(` "host.${serviceName}": { + requestHandler: ${name}ServiceRegistry.handleRequest, + streamingHandler: ${name}ServiceRegistry.handleStreamingRequest, + },`) + } + + const content = `// GENERATED CODE -- DO NOT EDIT! +// Generated by scripts/generate-host-bridge-client.mjs +import { createServiceRegistry } from "@hosts/vscode/hostbridge-grpc-service" +import { HostServiceHandlerConfig } from "@hosts/vscode/hostbridge-grpc-handler" + +${imports.join("\n")} +${clientImplementations.join("\n\n")} + +/** + * Map of host service names to their handler configurations + */ +export const hostServiceHandlers: Record = { +${handlerMap.join("\n")} +} +` + + // Write output file + await fs.mkdir(path.dirname(VSCODE_CLIENT_FILE), { recursive: true }) + await fs.writeFile(VSCODE_CLIENT_FILE, content) +} + +function generateVscodeClientImplementation(serviceName, serviceDefinition) { + // Get the methods from the service definition + const name = serviceName.replace(/Service$/, "").toLowerCase() + + const methods = Object.entries(serviceDefinition.service) + .map(([methodName, methodDef]) => { + // Get fully qualified type names + const isStreamingResponse = methodDef.responseStream + if (!isStreamingResponse) { + return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName})` + } else { + return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName}, { isStreaming: true })` + } + }) + .join("\n") + + // Generate the class + return `// Setup ${name} service registry +const ${name}ServiceRegistry = createServiceRegistry("${name}") +${methods}` +} + // Run the main function main().catch((error) => { console.error(chalk.red("Error:"), error) diff --git a/src/hosts/vscode/hostbridge-grpc-handler.ts b/src/hosts/vscode/hostbridge-grpc-handler.ts index 4ca1a7cc07..c33d5f7026 100644 --- a/src/hosts/vscode/hostbridge-grpc-handler.ts +++ b/src/hosts/vscode/hostbridge-grpc-handler.ts @@ -1,5 +1,5 @@ import { StreamingCallbacks } from "@/hosts/host-provider-types" -import { HostServiceHandlerConfig, hostServiceHandlers } from "@generated/hosts/vscode/hostbridge-grpc-service-config" +import { hostServiceHandlers } from "@generated/hosts/vscode/hostbridge-grpc-service-config" import { GrpcRequestRegistry } from "@core/controller/grpc-request-registry" /** @@ -161,6 +161,19 @@ export class GrpcHandler { } } +/** + * Configuration for a host service handler + */ +export interface HostServiceHandlerConfig { + requestHandler: (method: string, message: any) => Promise + streamingHandler: ( + method: string, + message: any, + responseStream: StreamingResponseHandler, + requestId?: string, + ) => Promise +} + /** * Get the request registry instance * This allows other parts of the code to access the registry diff --git a/src/standalone/host-bridge-client-manager.ts b/src/standalone/host-bridge-client-manager.ts index cab2296a6a..4b21c4b8e2 100644 --- a/src/standalone/host-bridge-client-manager.ts +++ b/src/standalone/host-bridge-client-manager.ts @@ -5,7 +5,7 @@ import { EnvServiceClientImpl, WindowServiceClientImpl, DiffServiceClientImpl, -} from "@generated/standalone/host-bridge-clients" +} from "@generated/hosts/standalone/host-bridge-clients" import { WatchServiceClientInterface, WorkspaceServiceClientInterface,