mirror of
https://github.com/cline/cline.git
synced 2026-09-19 02:05:44 +08:00
* Fix issue with Windows notification Fix Windows proto tooling Fix Windows unit test path normalization Revert "Fix Windows unit test path normalization" This reverts commit 73400a3ca6f0300d009f7c8238a016d769186f3a. Remove package-lock.json changes * Remove unnecessary changes * Use command approval string for notifications * Address PR feedback on Windows notifications * Fix Windows protoc path for CI * Polish notification safety and test coverage * Fix unfound tests in CI * Harden Windows notifications and protoc execution * Fix Windows path normalization in glob test * Fix as per Greptile feedback
210 lines
7.7 KiB
JavaScript
Executable File
210 lines
7.7 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import chalk from "chalk"
|
|
import { execFileSync, execSync } from "child_process"
|
|
import fsSync from "fs"
|
|
import * as fs from "fs/promises"
|
|
import { globby } from "globby"
|
|
import { createRequire } from "module"
|
|
import os from "os"
|
|
import * as path from "path"
|
|
import { rmrf } from "./file-utils.mjs"
|
|
import { main as generateHostBridgeClient } from "./generate-host-bridge-client.mjs"
|
|
import { main as generateProtoBusSetup } from "./generate-protobus-setup.mjs"
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const isWindows = process.platform === "win32"
|
|
const GRPC_TOOLS_PROTOC = path.join(require.resolve("grpc-tools"), "../bin", isWindows ? "protoc.exe" : "protoc")
|
|
// Legacy compatibility: some older/local Windows setups provision protoc into tmp-protoc.
|
|
// Prefer that path when present, but fall back to the grpc-tools bundled binary used by CI/npm installs.
|
|
const LEGACY_WINDOWS_PROTOC = path.resolve("tmp-protoc/bin/protoc.exe")
|
|
const PROTOC = isWindows && fsSync.existsSync(LEGACY_WINDOWS_PROTOC) ? LEGACY_WINDOWS_PROTOC : GRPC_TOOLS_PROTOC
|
|
|
|
if (!fsSync.existsSync(PROTOC)) {
|
|
const windowsHint = isWindows
|
|
? ` Neither ${LEGACY_WINDOWS_PROTOC} nor the grpc-tools bundled protoc at ${GRPC_TOOLS_PROTOC} exists.`
|
|
: ""
|
|
console.error(chalk.red(`protoc not found at ${PROTOC}.${windowsHint}`))
|
|
process.exit(1)
|
|
}
|
|
|
|
const PROTO_DIR = path.resolve("proto")
|
|
const TS_OUT_DIR = path.resolve("src/shared/proto")
|
|
const GRPC_JS_OUT_DIR = path.resolve("src/generated/grpc-js")
|
|
const NICE_JS_OUT_DIR = path.resolve("src/generated/nice-grpc")
|
|
const DESCRIPTOR_OUT_DIR = path.resolve("dist-standalone/proto")
|
|
|
|
const TS_PROTO_PLUGIN = isWindows
|
|
? path.resolve("node_modules/.bin/protoc-gen-ts_proto.cmd") // Use the .bin directory path for Windows
|
|
: require.resolve("ts-proto/protoc-gen-ts_proto")
|
|
|
|
const TS_PROTO_OPTIONS = [
|
|
"env=both",
|
|
"esModuleInterop=true",
|
|
"outputServices=generic-definitions", // output generic ServiceDefinitions
|
|
"outputIndex=true", // output an index file for each package which exports all protos in the package.
|
|
"useOptionals=none", // scalar and message fields are required unless they are marked as optional.
|
|
"useDate=false", // Timestamp fields will not be automatically converted to Date.
|
|
]
|
|
|
|
async function main() {
|
|
await cleanup()
|
|
await compileProtos()
|
|
await generateProtoBusSetup()
|
|
await generateHostBridgeClient()
|
|
}
|
|
async function compileProtos() {
|
|
console.log(chalk.bold.blue("Compiling Protocol Buffers..."))
|
|
|
|
// Check for Apple Silicon compatibility before proceeding
|
|
checkAppleSiliconCompatibility()
|
|
|
|
// Create output directories if they don't exist
|
|
for (const dir of [TS_OUT_DIR, GRPC_JS_OUT_DIR, NICE_JS_OUT_DIR, DESCRIPTOR_OUT_DIR]) {
|
|
await fs.mkdir(dir, { recursive: true })
|
|
}
|
|
|
|
// Process all proto files
|
|
const protoFiles = await globby("**/*.proto", { cwd: PROTO_DIR, realpath: true })
|
|
console.log(chalk.cyan(`Processing ${protoFiles.length} proto files from`), PROTO_DIR)
|
|
|
|
tsProtoc(TS_OUT_DIR, protoFiles, TS_PROTO_OPTIONS)
|
|
// grpc-js is used to generate service impls for the ProtoBus service.
|
|
tsProtoc(GRPC_JS_OUT_DIR, protoFiles, ["outputServices=grpc-js", ...TS_PROTO_OPTIONS])
|
|
// nice-js is used for the Host Bridge client impls because it uses promises.
|
|
tsProtoc(NICE_JS_OUT_DIR, protoFiles, ["outputServices=nice-grpc,useExactTypes=false", ...TS_PROTO_OPTIONS])
|
|
|
|
const descriptorFile = path.join(DESCRIPTOR_OUT_DIR, "descriptor_set.pb")
|
|
const descriptorProtocArgs = [
|
|
`--proto_path=${PROTO_DIR}`,
|
|
`--descriptor_set_out=${descriptorFile}`,
|
|
"--include_imports",
|
|
...protoFiles,
|
|
]
|
|
try {
|
|
log_verbose(chalk.cyan("Generating descriptor set..."))
|
|
log_verbose(`${PROTOC} ${descriptorProtocArgs.join(" ")}`)
|
|
execFileSync(PROTOC, descriptorProtocArgs, { stdio: "inherit" })
|
|
} catch (error) {
|
|
console.error(chalk.red("Error generating descriptor set for proto file:"), error)
|
|
process.exit(1)
|
|
}
|
|
|
|
log_verbose(chalk.green("Protocol Buffer code generation completed successfully."))
|
|
log_verbose(chalk.green(`TypeScript files generated in: ${TS_OUT_DIR}`))
|
|
}
|
|
|
|
function tsProtoc(outDir, protoFiles, protoOptions) {
|
|
const args = [
|
|
`--proto_path=${PROTO_DIR}`,
|
|
`--plugin=protoc-gen-ts_proto=${TS_PROTO_PLUGIN}`,
|
|
`--ts_proto_out=${outDir}`,
|
|
`--ts_proto_opt=${protoOptions.join(",")}`,
|
|
...protoFiles,
|
|
]
|
|
try {
|
|
log_verbose(chalk.cyan(`Generating TypeScript code in ${outDir} for:\n${protoFiles.join("\n")}...`))
|
|
log_verbose(`${PROTOC} ${args.join(" ")}`)
|
|
execFileSync(PROTOC, args, { stdio: "inherit" })
|
|
} catch (error) {
|
|
console.error(chalk.red("Error generating TypeScript for proto files:"), error)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
async function cleanup() {
|
|
// Clean up existing generated files
|
|
log_verbose(chalk.cyan("Cleaning up existing generated TypeScript files..."))
|
|
await rmrf(TS_OUT_DIR)
|
|
await rmrf("src/generated")
|
|
|
|
// Clean up generated files that were moved.
|
|
await rmrf("src/standalone/services/host-grpc-client.ts")
|
|
await rmrf("src/standalone/server-setup.ts")
|
|
await rmrf("src/hosts/vscode/host-grpc-service-config.ts")
|
|
await rmrf("src/core/controller/grpc-service-config.ts")
|
|
const oldhostbridgefiles = [
|
|
"src/hosts/vscode/workspace/methods.ts",
|
|
"src/hosts/vscode/workspace/index.ts",
|
|
"src/hosts/vscode/diff/methods.ts",
|
|
"src/hosts/vscode/diff/index.ts",
|
|
"src/hosts/vscode/env/methods.ts",
|
|
"src/hosts/vscode/env/index.ts",
|
|
"src/hosts/vscode/window/methods.ts",
|
|
"src/hosts/vscode/window/index.ts",
|
|
"src/hosts/vscode/watch/methods.ts",
|
|
"src/hosts/vscode/watch/index.ts",
|
|
"src/hosts/vscode/uri/methods.ts",
|
|
"src/hosts/vscode/uri/index.ts",
|
|
]
|
|
const oldprotobusfiles = [
|
|
"src/core/controller/account/index.ts",
|
|
"src/core/controller/account/methods.ts",
|
|
"src/core/controller/browser/index.ts",
|
|
"src/core/controller/browser/methods.ts",
|
|
"src/core/controller/checkpoints/index.ts",
|
|
"src/core/controller/checkpoints/methods.ts",
|
|
"src/core/controller/file/index.ts",
|
|
"src/core/controller/file/methods.ts",
|
|
"src/core/controller/mcp/index.ts",
|
|
"src/core/controller/mcp/methods.ts",
|
|
"src/core/controller/models/index.ts",
|
|
"src/core/controller/models/methods.ts",
|
|
"src/core/controller/slash/index.ts",
|
|
"src/core/controller/slash/methods.ts",
|
|
"src/core/controller/state/index.ts",
|
|
"src/core/controller/state/methods.ts",
|
|
"src/core/controller/task/index.ts",
|
|
"src/core/controller/task/methods.ts",
|
|
"src/core/controller/ui/index.ts",
|
|
"src/core/controller/ui/methods.ts",
|
|
"src/core/controller/web/index.ts",
|
|
"src/core/controller/web/methods.ts",
|
|
]
|
|
for (const file of [...oldhostbridgefiles, ...oldprotobusfiles]) {
|
|
await rmrf(file)
|
|
}
|
|
}
|
|
|
|
// Check for Apple Silicon compatibility
|
|
function checkAppleSiliconCompatibility() {
|
|
// Only run check on macOS
|
|
if (process.platform !== "darwin") {
|
|
return
|
|
}
|
|
|
|
// Check if running on Apple Silicon
|
|
const cpuArchitecture = os.arch()
|
|
if (cpuArchitecture === "arm64") {
|
|
try {
|
|
// Check if Rosetta is installed
|
|
const rosettaCheck = execSync('/usr/bin/pgrep oahd || echo "NOT_INSTALLED"').toString().trim()
|
|
|
|
if (rosettaCheck === "NOT_INSTALLED") {
|
|
console.log(chalk.yellow("Detected Apple Silicon (ARM64) architecture."))
|
|
console.log(
|
|
chalk.red("Rosetta 2 is NOT installed. The npm version of protoc is not compatible with Apple Silicon."),
|
|
)
|
|
console.log(chalk.cyan("Please install Rosetta 2 using the following command:"))
|
|
console.log(chalk.cyan(" softwareupdate --install-rosetta --agree-to-license"))
|
|
console.log(chalk.red("Aborting build process."))
|
|
process.exit(1)
|
|
}
|
|
} catch (_error) {
|
|
console.log(chalk.yellow("Could not determine Rosetta installation status. Proceeding anyway."))
|
|
}
|
|
}
|
|
}
|
|
|
|
function log_verbose(s) {
|
|
if (process.argv.includes("-v") || process.argv.includes("--verbose")) {
|
|
console.log(s)
|
|
}
|
|
}
|
|
|
|
// Run the main function
|
|
main().catch((error) => {
|
|
console.error(chalk.red("Error:"), error)
|
|
process.exit(1)
|
|
})
|