test-standalone-core-api-server.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/usr/bin/env npx tsx
  2. /**
  3. * Simple Cline gRPC Server
  4. *
  5. * This script provides a minimal way to run the Cline core gRPC service
  6. * without requiring the full installation, while automatically mocking all external services. Simply run:
  7. *
  8. * # One-time setup (generates protobuf files)
  9. * npm run compile-standalone
  10. * npm run test:sca-server
  11. *
  12. * The following components are started automatically:
  13. * 1. HostBridge test server
  14. * 2. ClineApiServerMock (mock implementation of the Cline API)
  15. * 3. AuthServiceMock (activated if E2E_TEST="true")
  16. *
  17. * Environment Variables for Customization:
  18. * PROJECT_ROOT - Override project root directory (default: parent of scripts dir)
  19. * CLINE_DIST_DIR - Override distribution directory (default: PROJECT_ROOT/dist-standalone)
  20. * CLINE_CORE_FILE - Override core file name (default: cline-core.js)
  21. * PROTOBUS_PORT - gRPC server port (default: 26040)
  22. * HOSTBRIDGE_PORT - HostBridge server port (default: 26041)
  23. * WORKSPACE_DIR - Working directory (default: current directory)
  24. * E2E_TEST - Enable E2E test mode (default: true)
  25. * CLINE_ENVIRONMENT - Environment setting (default: local)
  26. *
  27. * Ideal for local development, testing, or lightweight E2E scenarios.
  28. */
  29. import * as fs from "node:fs"
  30. import { mkdtempSync, rmSync } from "node:fs"
  31. import * as os from "node:os"
  32. import { ChildProcess, execSync, spawn } from "child_process"
  33. import * as path from "path"
  34. import { ClineApiServerMock } from "../src/test/e2e/fixtures/server/index"
  35. const PROTOBUS_PORT = process.env.PROTOBUS_PORT || "26040"
  36. const HOSTBRIDGE_PORT = process.env.HOSTBRIDGE_PORT || "26041"
  37. const WORKSPACE_DIR = process.env.WORKSPACE_DIR || process.cwd()
  38. const E2E_TEST = process.env.E2E_TEST || "true"
  39. const CLINE_ENVIRONMENT = process.env.CLINE_ENVIRONMENT || "local"
  40. // Locate the standalone build directory and core file with flexible path resolution
  41. const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, "..")
  42. const distDir = process.env.CLINE_DIST_DIR || path.join(projectRoot, "dist-standalone")
  43. const clineCoreFile = process.env.CLINE_CORE_FILE || "cline-core.js"
  44. const coreFile = path.join(distDir, clineCoreFile)
  45. const childProcesses: ChildProcess[] = []
  46. async function main(): Promise<void> {
  47. console.log("Starting Simple Cline gRPC Server...")
  48. console.log(`Workspace: ${WORKSPACE_DIR}`)
  49. console.log(`ProtoBus Port: ${PROTOBUS_PORT}`)
  50. console.log(`HostBridge Port: ${HOSTBRIDGE_PORT}`)
  51. console.log(`Looking for standalone build at: ${coreFile}`)
  52. if (!fs.existsSync(coreFile)) {
  53. console.error(`Standalone build not found at: ${coreFile}`)
  54. console.error("Available environment variables for customization:")
  55. console.error(" PROJECT_ROOT - Override project root directory")
  56. console.error(" CLINE_DIST_DIR - Override distribution directory")
  57. console.error(" CLINE_CORE_FILE - Override core file name")
  58. console.error("")
  59. console.error("To build the standalone version, run: npm run compile-standalone")
  60. process.exit(1)
  61. }
  62. try {
  63. await ClineApiServerMock.startGlobalServer()
  64. console.log("Cline API Server started in-process")
  65. } catch (error) {
  66. console.error("Failed to start Cline API Server:", error)
  67. process.exit(1)
  68. }
  69. const extensionsDir = path.join(distDir, "vsce-extension")
  70. const userDataDir = mkdtempSync(path.join(os.tmpdir(), "vsce"))
  71. const clineTestWorkspace = mkdtempSync(path.join(os.tmpdir(), "cline-test-workspace-"))
  72. console.log("Starting HostBridge test server...")
  73. const hostbridge: ChildProcess = spawn("npx", ["tsx", path.join(__dirname, "test-hostbridge-server.ts")], {
  74. stdio: "pipe",
  75. env: {
  76. ...process.env,
  77. TEST_HOSTBRIDGE_WORKSPACE_DIR: clineTestWorkspace,
  78. HOST_BRIDGE_ADDRESS: `127.0.0.1:${HOSTBRIDGE_PORT}`,
  79. },
  80. })
  81. childProcesses.push(hostbridge)
  82. console.log(`Temp user data dir: ${userDataDir}`)
  83. console.log(`Temp extensions dir: ${extensionsDir}`)
  84. // Extract standalone.zip if needed
  85. const standaloneZipPath = path.join(distDir, "standalone.zip")
  86. if (!fs.existsSync(standaloneZipPath)) {
  87. console.error(`standalone.zip not found at: ${standaloneZipPath}`)
  88. process.exit(1)
  89. }
  90. console.log("Extracting standalone.zip to extensions directory...")
  91. try {
  92. if (!fs.existsSync(extensionsDir)) {
  93. execSync(`unzip -q "${standaloneZipPath}" -d "${extensionsDir}"`, { stdio: "inherit" })
  94. }
  95. console.log(`Successfully extracted standalone.zip to: ${extensionsDir}`)
  96. } catch (error) {
  97. console.error("Failed to extract standalone.zip:", error)
  98. process.exit(1)
  99. }
  100. console.log("Starting Cline Core Service...")
  101. const coreService: ChildProcess = spawn("node", [clineCoreFile], {
  102. cwd: distDir,
  103. env: {
  104. ...process.env,
  105. NODE_PATH: "./node_modules",
  106. DEV_WORKSPACE_FOLDER: WORKSPACE_DIR,
  107. PROTOBUS_ADDRESS: `127.0.0.1:${PROTOBUS_PORT}`,
  108. HOST_BRIDGE_ADDRESS: `localhost:${HOSTBRIDGE_PORT}`,
  109. E2E_TEST,
  110. CLINE_ENVIRONMENT,
  111. CLINE_DIR: userDataDir,
  112. INSTALL_DIR: extensionsDir,
  113. },
  114. stdio: "inherit",
  115. })
  116. childProcesses.push(coreService)
  117. const shutdown = async () => {
  118. console.log("\nShutting down services...")
  119. while (childProcesses.length > 0) {
  120. const child = childProcesses.pop()
  121. if (child && !child.killed) child.kill("SIGINT")
  122. }
  123. await ClineApiServerMock.stopGlobalServer()
  124. try {
  125. rmSync(userDataDir, { recursive: true, force: true })
  126. rmSync(clineTestWorkspace, { recursive: true, force: true })
  127. console.log("Cleaned up temporary directories")
  128. } catch (err) {
  129. console.warn("Failed to cleanup temp directories:", err)
  130. }
  131. process.exit(0)
  132. }
  133. process.on("SIGINT", shutdown)
  134. process.on("SIGTERM", shutdown)
  135. coreService.on("exit", (code) => {
  136. console.log(`Core service exited with code ${code}`)
  137. shutdown()
  138. })
  139. hostbridge.on("exit", (code) => {
  140. console.log(`HostBridge exited with code ${code}`)
  141. shutdown()
  142. })
  143. console.log(`Cline gRPC Server is running on 127.0.0.1:${PROTOBUS_PORT}`)
  144. console.log("Press Ctrl+C to stop")
  145. }
  146. if (require.main === module) {
  147. main().catch((err) => {
  148. console.error("Failed to start simple Cline server:", err)
  149. process.exit(1)
  150. })
  151. }