build-proto.mjs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #!/usr/bin/env node
  2. import chalk from "chalk"
  3. import { execSync } from "child_process"
  4. import * as fs from "fs/promises"
  5. import { globby } from "globby"
  6. import { createRequire } from "module"
  7. import os from "os"
  8. import * as path from "path"
  9. import { rmrf } from "./file-utils.mjs"
  10. import { main as generateHostBridgeClient } from "./generate-host-bridge-client.mjs"
  11. import { main as generateProtoBusSetup } from "./generate-protobus-setup.mjs"
  12. const require = createRequire(import.meta.url)
  13. const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc")
  14. const PROTO_DIR = path.resolve("proto")
  15. const TS_OUT_DIR = path.resolve("src/shared/proto")
  16. const GRPC_JS_OUT_DIR = path.resolve("src/generated/grpc-js")
  17. const NICE_JS_OUT_DIR = path.resolve("src/generated/nice-grpc")
  18. const DESCRIPTOR_OUT_DIR = path.resolve("dist-standalone/proto")
  19. const isWindows = process.platform === "win32"
  20. const TS_PROTO_PLUGIN = isWindows
  21. ? path.resolve("node_modules/.bin/protoc-gen-ts_proto.cmd") // Use the .bin directory path for Windows
  22. : require.resolve("ts-proto/protoc-gen-ts_proto")
  23. const TS_PROTO_OPTIONS = [
  24. "env=node",
  25. "esModuleInterop=true",
  26. "outputServices=generic-definitions", // output generic ServiceDefinitions
  27. "outputIndex=true", // output an index file for each package which exports all protos in the package.
  28. "useOptionals=none", // scalar and message fields are required unless they are marked as optional.
  29. "useDate=false", // Timestamp fields will not be automatically converted to Date.
  30. ]
  31. async function main() {
  32. await cleanup()
  33. await compileProtos()
  34. await generateProtoBusSetup()
  35. await generateHostBridgeClient()
  36. }
  37. async function compileProtos() {
  38. console.log(chalk.bold.blue("Compiling Protocol Buffers..."))
  39. // Check for Apple Silicon compatibility before proceeding
  40. checkAppleSiliconCompatibility()
  41. // Create output directories if they don't exist
  42. for (const dir of [TS_OUT_DIR, GRPC_JS_OUT_DIR, NICE_JS_OUT_DIR, DESCRIPTOR_OUT_DIR]) {
  43. await fs.mkdir(dir, { recursive: true })
  44. }
  45. // Process all proto files
  46. const protoFiles = await globby("**/*.proto", { cwd: PROTO_DIR, realpath: true })
  47. console.log(chalk.cyan(`Processing ${protoFiles.length} proto files from`), PROTO_DIR)
  48. tsProtoc(TS_OUT_DIR, protoFiles, TS_PROTO_OPTIONS)
  49. // grpc-js is used to generate service impls for the ProtoBus service.
  50. tsProtoc(GRPC_JS_OUT_DIR, protoFiles, ["outputServices=grpc-js", ...TS_PROTO_OPTIONS])
  51. // nice-js is used for the Host Bridge client impls because it uses promises.
  52. tsProtoc(NICE_JS_OUT_DIR, protoFiles, ["outputServices=nice-grpc,useExactTypes=false", ...TS_PROTO_OPTIONS])
  53. const descriptorFile = path.join(DESCRIPTOR_OUT_DIR, "descriptor_set.pb")
  54. const descriptorProtocCommand = [
  55. PROTOC,
  56. `--proto_path="${PROTO_DIR}"`,
  57. `--descriptor_set_out="${descriptorFile}"`,
  58. "--include_imports",
  59. ...protoFiles,
  60. ].join(" ")
  61. try {
  62. log_verbose(chalk.cyan("Generating descriptor set..."))
  63. execSync(descriptorProtocCommand, { stdio: "inherit" })
  64. } catch (error) {
  65. console.error(chalk.red("Error generating descriptor set for proto file:"), error)
  66. process.exit(1)
  67. }
  68. log_verbose(chalk.green("Protocol Buffer code generation completed successfully."))
  69. log_verbose(chalk.green(`TypeScript files generated in: ${TS_OUT_DIR}`))
  70. }
  71. async function tsProtoc(outDir, protoFiles, protoOptions) {
  72. // Build the protoc command with proper path handling for cross-platform
  73. const command = [
  74. PROTOC,
  75. `--proto_path="${PROTO_DIR}"`,
  76. `--plugin=protoc-gen-ts_proto="${TS_PROTO_PLUGIN}"`,
  77. `--ts_proto_out="${outDir}"`,
  78. `--ts_proto_opt=${protoOptions.join(",")} `,
  79. ...protoFiles.map((s) => `"${s}"`),
  80. ].join(" ")
  81. try {
  82. log_verbose(chalk.cyan(`Generating TypeScript code in ${outDir} for:\n${protoFiles.join("\n")}...`))
  83. log_verbose(command)
  84. execSync(command, { stdio: "inherit" })
  85. } catch (error) {
  86. console.error(chalk.red("Error generating TypeScript for proto files:"), error)
  87. process.exit(1)
  88. }
  89. }
  90. async function cleanup() {
  91. // Clean up existing generated files
  92. log_verbose(chalk.cyan("Cleaning up existing generated TypeScript files..."))
  93. await rmrf(TS_OUT_DIR)
  94. await rmrf("src/generated")
  95. // Clean up generated files that were moved.
  96. await rmrf("src/standalone/services/host-grpc-client.ts")
  97. await rmrf("src/standalone/server-setup.ts")
  98. await rmrf("src/hosts/vscode/host-grpc-service-config.ts")
  99. await rmrf("src/core/controller/grpc-service-config.ts")
  100. const oldhostbridgefiles = [
  101. "src/hosts/vscode/workspace/methods.ts",
  102. "src/hosts/vscode/workspace/index.ts",
  103. "src/hosts/vscode/diff/methods.ts",
  104. "src/hosts/vscode/diff/index.ts",
  105. "src/hosts/vscode/env/methods.ts",
  106. "src/hosts/vscode/env/index.ts",
  107. "src/hosts/vscode/window/methods.ts",
  108. "src/hosts/vscode/window/index.ts",
  109. "src/hosts/vscode/watch/methods.ts",
  110. "src/hosts/vscode/watch/index.ts",
  111. "src/hosts/vscode/uri/methods.ts",
  112. "src/hosts/vscode/uri/index.ts",
  113. ]
  114. const oldprotobusfiles = [
  115. "src/core/controller/account/index.ts",
  116. "src/core/controller/account/methods.ts",
  117. "src/core/controller/browser/index.ts",
  118. "src/core/controller/browser/methods.ts",
  119. "src/core/controller/checkpoints/index.ts",
  120. "src/core/controller/checkpoints/methods.ts",
  121. "src/core/controller/file/index.ts",
  122. "src/core/controller/file/methods.ts",
  123. "src/core/controller/mcp/index.ts",
  124. "src/core/controller/mcp/methods.ts",
  125. "src/core/controller/models/index.ts",
  126. "src/core/controller/models/methods.ts",
  127. "src/core/controller/slash/index.ts",
  128. "src/core/controller/slash/methods.ts",
  129. "src/core/controller/state/index.ts",
  130. "src/core/controller/state/methods.ts",
  131. "src/core/controller/task/index.ts",
  132. "src/core/controller/task/methods.ts",
  133. "src/core/controller/ui/index.ts",
  134. "src/core/controller/ui/methods.ts",
  135. "src/core/controller/web/index.ts",
  136. "src/core/controller/web/methods.ts",
  137. ]
  138. for (const file of [...oldhostbridgefiles, ...oldprotobusfiles]) {
  139. await rmrf(file)
  140. }
  141. }
  142. // Check for Apple Silicon compatibility
  143. function checkAppleSiliconCompatibility() {
  144. // Only run check on macOS
  145. if (process.platform !== "darwin") {
  146. return
  147. }
  148. // Check if running on Apple Silicon
  149. const cpuArchitecture = os.arch()
  150. if (cpuArchitecture === "arm64") {
  151. try {
  152. // Check if Rosetta is installed
  153. const rosettaCheck = execSync('/usr/bin/pgrep oahd || echo "NOT_INSTALLED"').toString().trim()
  154. if (rosettaCheck === "NOT_INSTALLED") {
  155. console.log(chalk.yellow("Detected Apple Silicon (ARM64) architecture."))
  156. console.log(
  157. chalk.red("Rosetta 2 is NOT installed. The npm version of protoc is not compatible with Apple Silicon."),
  158. )
  159. console.log(chalk.cyan("Please install Rosetta 2 using the following command:"))
  160. console.log(chalk.cyan(" softwareupdate --install-rosetta --agree-to-license"))
  161. console.log(chalk.red("Aborting build process."))
  162. process.exit(1)
  163. }
  164. } catch (_error) {
  165. console.log(chalk.yellow("Could not determine Rosetta installation status. Proceeding anyway."))
  166. }
  167. }
  168. }
  169. function log_verbose(s) {
  170. if (process.argv.includes("-v") || process.argv.includes("--verbose")) {
  171. console.log(s)
  172. }
  173. }
  174. // Run the main function
  175. main().catch((error) => {
  176. console.error(chalk.red("Error:"), error)
  177. process.exit(1)
  178. })