generate-protobus-setup.mjs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #!/usr/bin/env node
  2. import path from "path"
  3. import { fileURLToPath } from "url"
  4. import { writeFileWithMkdirs } from "./file-utils.mjs"
  5. import { getFqn, loadServicesFromProtoDescriptor } from "./proto-utils.mjs"
  6. const WEBVIEW_CLIENTS_FILE = path.resolve("webview-ui/src/services/grpc-client.ts")
  7. const VSCODE_SERVICES_FILE = path.resolve("src/generated/hosts/vscode/protobus-services.ts")
  8. const VSCODE_SERVICE_TYPES_FILE = path.resolve("src/generated/hosts/vscode/protobus-service-types.ts")
  9. const STANDALONE_SERVER_SETUP_FILE = path.resolve("src/generated/hosts/standalone/protobus-server-setup.ts")
  10. const SCRIPT_NAME = path.relative(process.cwd(), fileURLToPath(import.meta.url))
  11. export async function main() {
  12. const { protobusServices } = await loadServicesFromProtoDescriptor()
  13. await generateWebviewProtobusClients(protobusServices)
  14. await generateVscodeServiceTypes(protobusServices)
  15. await generateVscodeProtobusServers(protobusServices)
  16. await generateStandaloneProtobusServiceSetup(protobusServices)
  17. console.log(`Generated ProtoBus files at:`)
  18. console.log(`- ${WEBVIEW_CLIENTS_FILE}`)
  19. console.log(`- ${VSCODE_SERVICE_TYPES_FILE}`)
  20. console.log(`- ${VSCODE_SERVICES_FILE}`)
  21. console.log(`- ${STANDALONE_SERVER_SETUP_FILE}`)
  22. }
  23. async function generateWebviewProtobusClients(protobusServices) {
  24. const clients = []
  25. for (const [serviceName, def] of Object.entries(protobusServices)) {
  26. const rpcs = []
  27. for (const [rpcName, rpc] of Object.entries(def.service)) {
  28. const requestType = getFqn(rpc.requestType.type.name)
  29. const responseType = getFqn(rpc.responseType.type.name)
  30. if (rpc.requestStream) {
  31. throw new Error("Request streaming is not supported")
  32. }
  33. if (!rpc.responseStream) {
  34. rpcs.push(` static async ${rpcName}(request: ${requestType}): Promise<${responseType}> {
  35. return this.makeUnaryRequest("${rpcName}", request, ${requestType}.toJSON, ${responseType}.fromJSON)
  36. }`)
  37. } else {
  38. rpcs.push(` static ${rpcName}(request: ${requestType}, callbacks: Callbacks<${responseType}>): ()=>void {
  39. return this.makeStreamingRequest("${rpcName}", request, ${requestType}.toJSON, ${responseType}.fromJSON, callbacks)
  40. }`)
  41. }
  42. }
  43. clients.push(`export class ${serviceName}Client extends ProtoBusClient {
  44. static override serviceName: string = "cline.${serviceName}"
  45. ${rpcs.join("\n")}
  46. }`)
  47. }
  48. // Create output file
  49. const output = `// GENERATED CODE -- DO NOT EDIT!
  50. // Generated by ${SCRIPT_NAME}
  51. import * as proto from "@shared/proto/index"
  52. import { ProtoBusClient, Callbacks } from "./grpc-client-base"
  53. ${clients.join("\n")}
  54. `
  55. // Write output file
  56. await writeFileWithMkdirs(WEBVIEW_CLIENTS_FILE, output)
  57. }
  58. /**
  59. * Generate imports and function to add all the handlers to the server for all services defined in the proto files.
  60. */
  61. async function generateVscodeServiceTypes(protobusServices) {
  62. const servers = []
  63. for (const [serviceName, def] of Object.entries(protobusServices)) {
  64. const domain = getDomainName(serviceName)
  65. servers.push(`// ${domain} Service Handler Types`)
  66. servers.push(`export type ${serviceName}Handlers = {`)
  67. for (const [rpcName, rpc] of Object.entries(def.service)) {
  68. const requestType = getFqn(rpc.requestType.type.name)
  69. const responseType = getFqn(rpc.responseType.type.name)
  70. if (rpc.requestStream) {
  71. throw new Error("Request streaming is not supported")
  72. }
  73. if (!rpc.responseStream) {
  74. servers.push(` ${rpcName}:(controller: Controller, request: ${requestType}) => Promise<${responseType}>`)
  75. } else {
  76. servers.push(
  77. ` ${rpcName}:(controller: Controller, request: ${requestType}, responseStream: StreamingResponseHandler<${responseType}>, requestId?: string) => Promise<void>`,
  78. )
  79. }
  80. }
  81. servers.push(`}\n`)
  82. }
  83. // Create output file
  84. const output = `// GENERATED CODE -- DO NOT EDIT!
  85. // Generated by ${SCRIPT_NAME}
  86. import * as proto from "@shared/proto/index"
  87. import { Controller } from "@core/controller"
  88. import { StreamingResponseHandler } from "@/core/controller/grpc-handler"
  89. ${servers.join("\n")}
  90. `
  91. // Write output file
  92. await writeFileWithMkdirs(VSCODE_SERVICE_TYPES_FILE, output)
  93. }
  94. /**
  95. * Generate imports and function to add all the handlers to the server for all services defined in the proto files.
  96. */
  97. async function generateVscodeProtobusServers(protobusServices) {
  98. const imports = []
  99. const servers = []
  100. const serviceMap = []
  101. for (const [serviceName, def] of Object.entries(protobusServices)) {
  102. const domain = getDomainName(serviceName)
  103. const dir = getDirName(serviceName)
  104. imports.push(`// ${domain} Service`)
  105. servers.push(`const ${serviceName}Handlers: serviceTypes.${serviceName}Handlers = {`)
  106. for (const [rpcName, _rpc] of Object.entries(def.service)) {
  107. imports.push(`import { ${rpcName} } from "@core/controller/${dir}/${rpcName}"`)
  108. servers.push(` ${rpcName}: ${rpcName},`)
  109. }
  110. servers.push(`} \n`)
  111. serviceMap.push(` "cline.${serviceName}": ${serviceName}Handlers,`)
  112. imports.push("")
  113. }
  114. // Create output file
  115. const output = `// GENERATED CODE -- DO NOT EDIT!
  116. // Generated by ${SCRIPT_NAME}
  117. import * as serviceTypes from "src/generated/hosts/vscode/protobus-service-types"
  118. ${imports.join("\n")}
  119. ${servers.join("\n")}
  120. export const serviceHandlers: Record<string, any> = {
  121. ${serviceMap.join("\n")}
  122. }
  123. `
  124. // Write output file
  125. await writeFileWithMkdirs(VSCODE_SERVICES_FILE, output)
  126. }
  127. /**
  128. * Generate imports and function to add all the handlers to the server for all services defined in the proto files.
  129. */
  130. async function generateStandaloneProtobusServiceSetup(protobusServices) {
  131. const imports = []
  132. const handlerSetup = []
  133. for (const [name, def] of Object.entries(protobusServices)) {
  134. const domain = getDomainName(name)
  135. const dir = getDirName(name)
  136. imports.push(`// ${domain} Service`)
  137. handlerSetup.push(` // ${domain} Service`)
  138. handlerSetup.push(` server.addService(cline.${name}Service, {`)
  139. for (const [rpcName, rpc] of Object.entries(def.service)) {
  140. imports.push(`import { ${rpcName} } from "@core/controller/${dir}/${rpcName}"`)
  141. const requestType = "cline." + rpc.requestType.type.name
  142. const responseType = "cline." + rpc.responseType.type.name
  143. if (rpc.requestStream) {
  144. throw new Error("Request streaming is not supported")
  145. }
  146. if (rpc.responseStream) {
  147. handlerSetup.push(
  148. ` ${rpcName}: wrapStreamingResponse<${requestType},${responseType}>(${rpcName}, controller),`,
  149. )
  150. } else {
  151. handlerSetup.push(` ${rpcName}: wrapper<${requestType},${responseType}>(${rpcName}, controller),`)
  152. }
  153. }
  154. handlerSetup.push(` });`)
  155. imports.push("")
  156. handlerSetup.push("")
  157. }
  158. // Create output file
  159. const output = `// GENERATED CODE -- DO NOT EDIT!
  160. // Generated by ${SCRIPT_NAME}
  161. import * as grpc from "@grpc/grpc-js"
  162. import { cline } from "@generated/grpc-js"
  163. import { Controller } from "@core/controller"
  164. import { GrpcHandlerWrapper, GrpcStreamingResponseHandlerWrapper } from "@hosts/external/grpc-types"
  165. ${imports.join("\n")}
  166. export function addProtobusServices(
  167. server: grpc.Server,
  168. controller: Controller,
  169. wrapper: GrpcHandlerWrapper,
  170. wrapStreamingResponse: GrpcStreamingResponseHandlerWrapper,
  171. ): void {
  172. ${handlerSetup.join("\n")}
  173. }
  174. `
  175. // Write output file
  176. await writeFileWithMkdirs(STANDALONE_SERVER_SETUP_FILE, output)
  177. }
  178. function getDomainName(serviceName) {
  179. return serviceName.replace(/Service$/, "")
  180. }
  181. function getDirName(serviceName) {
  182. const domain = getDomainName(serviceName)
  183. return domain.charAt(0).toLowerCase() + domain.slice(1)
  184. }
  185. // Only run main if this script is executed directly
  186. if (import.meta.url === `file://${process.argv[1]}`) {
  187. main().catch((error) => {
  188. console.error(chalk.red("Error:"), error)
  189. process.exit(1)
  190. })
  191. }