diff --git a/package.json b/package.json index d6b6c51af3..6a0509e2f8 100644 --- a/package.json +++ b/package.json @@ -345,7 +345,7 @@ "watch:esbuild": "node esbuild.mjs --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", "package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.mjs --production", - "protos": "node scripts/build-proto.mjs && node scripts/generate-protobus-setup.mjs && node scripts/generate-host-bridge-client.mjs", + "protos": "node scripts/build-proto.mjs", "postprotos": "prettier src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --log-level warn", "clean": "rimraf dist dist-standalone webview-ui/build src/generated out/", "compile-tests": "node ./scripts/build-tests.js", diff --git a/scripts/build-proto.mjs b/scripts/build-proto.mjs index 1e1c188e81..93160d0cb8 100755 --- a/scripts/build-proto.mjs +++ b/scripts/build-proto.mjs @@ -7,8 +7,10 @@ import { globby } from "globby" import { createRequire } from "module" import os from "os" import * as path from "path" -import { fileURLToPath } from "url" import { rmrf } from "./file-utils.mjs" +import { main as generateProtoBusSetup } from "./generate-protobus-setup.mjs" +import { main as generateHostBridgeClient } from "./generate-host-bridge-client.mjs" +import { loadProtoDescriptorSet } from "./proto-utils.mjs" const require = createRequire(import.meta.url) const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc") @@ -34,9 +36,14 @@ const TS_PROTO_OPTIONS = [ ] async function main() { - console.log(chalk.bold.blue("Compiling Protocol Buffers...")) - await cleanup() + await compileProtos() + await checkProtos() + await generateProtoBusSetup() + await generateHostBridgeClient() +} +async function compileProtos() { + console.log(chalk.bold.blue("Compiling Protocol Buffers...")) // Check for Apple Silicon compatibility before proceeding checkAppleSiliconCompatibility() @@ -180,6 +187,51 @@ function checkAppleSiliconCompatibility() { } } +const int64TypeNames = ["TYPE_INT64", "TYPE_UINT64", "TYPE_SINT64", "TYPE_FIXED64", "TYPE_SFIXED64"] + +async function checkProtos() { + const proto = await loadProtoDescriptorSet() + const int64Fields = [] + + for (const [packageName, packageDef] of Object.entries(proto)) { + for (const [messageName, def] of Object.entries(packageDef)) { + // Skip service definitions + if (def && typeof def === "object" && "service" in def) { + continue + } + // Check message fields + if (def && def.type && def.type.field) { + for (const field of def.type.field) { + if (int64TypeNames.includes(field.type)) { + const name = `${packageName}.${messageName}.${field.name}` + int64Fields.push({ + name: name, + type: field.type, + }) + } + } + } + } + } + + if (int64Fields.length > 0) { + console.log(chalk.yellow(`\nWarning: Found ${int64Fields.length} fields using 64-bit integer types`)) + for (const field of int64Fields) { + const typeNames = { + TYPE_INT64: "int64", + TYPE_UINT64: "uint64", + TYPE_SINT64: "sint64", + TYPE_FIXED64: "fixed64", + TYPE_SFIXED64: "sfixed64", + } + log_verbose(chalk.yellow(` - ${field.name} (${typeNames[field.type]})`)) + } + log_verbose(chalk.yellow("\nWARNING: 64-bit integer fields detected in proto definitions")) + log_verbose(chalk.yellow("JavaScript cannot safely represent integers larger than 2^53-1 (Number.MAX_SAFE_INTEGER).")) + log_verbose(chalk.yellow("Consider using string representation for large numbers or implementing BigInt support.\n")) + } +} + function log_verbose(s) { if (process.argv.includes("-v") || process.argv.includes("--verbose")) { console.log(s) diff --git a/scripts/generate-host-bridge-client.mjs b/scripts/generate-host-bridge-client.mjs index d2c2e4cb73..00004cc556 100755 --- a/scripts/generate-host-bridge-client.mjs +++ b/scripts/generate-host-bridge-client.mjs @@ -15,7 +15,7 @@ const VSCODE_CLIENT_FILE = path.resolve("src/generated/hosts/vscode/hostbridge-g /** * Main function to generate the host bridge client */ -async function main() { +export async function main() { const { hostServices } = await loadServicesFromProtoDescriptor() await generateTypesFile(hostServices) @@ -234,8 +234,10 @@ const ${name}ServiceRegistry = createServiceRegistry("${name}") ${methods}` } -// Run the main function -main().catch((error) => { - console.error(chalk.red("Error:"), error) - process.exit(1) -}) +// Only run main if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(chalk.red("Error:"), error) + process.exit(1) + }) +} diff --git a/scripts/generate-protobus-setup.mjs b/scripts/generate-protobus-setup.mjs index f22e7c0bbb..6dce1c2576 100755 --- a/scripts/generate-protobus-setup.mjs +++ b/scripts/generate-protobus-setup.mjs @@ -12,7 +12,7 @@ const STANDALONE_SERVER_SETUP_FILE = path.resolve("src/generated/hosts/standalon const SCRIPT_NAME = path.relative(process.cwd(), fileURLToPath(import.meta.url)) -async function main() { +export async function main() { const { protobusServices } = await loadServicesFromProtoDescriptor() await generateWebviewProtobusClients(protobusServices) await generateVscodeServiceTypes(protobusServices) @@ -205,4 +205,10 @@ function getDirName(serviceName) { return domain.charAt(0).toLowerCase() + domain.slice(1) } -main() +// Only run main if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(chalk.red("Error:"), error) + process.exit(1) + }) +} diff --git a/scripts/proto-utils.mjs b/scripts/proto-utils.mjs index 52ff0f6453..906d1413ed 100755 --- a/scripts/proto-utils.mjs +++ b/scripts/proto-utils.mjs @@ -10,7 +10,7 @@ const DESCRIPTOR_SET = path.resolve("dist-standalone/proto/descriptor_set.pb") const typeNameToFQN = new Map() function addTypeNameToFqn(name, fqn) { - if (typeNameToFQN.has(name)) { + if (typeNameToFQN.has(name) && typeNameToFQN.get(name) !== fqn) { throw new Error(`Proto type ${name} redefined (${fqn}).`) } typeNameToFQN.set(name, fqn) @@ -23,11 +23,15 @@ export function getFqn(name) { return typeNameToFQN.get(name) } -export async function loadServicesFromProtoDescriptor() { - // Load service definitions from descriptor set +export async function loadProtoDescriptorSet() { const descriptorBuffer = await fs.readFile(DESCRIPTOR_SET) const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer(descriptorBuffer) - const proto = grpc.loadPackageDefinition(packageDefinition) + return grpc.loadPackageDefinition(packageDefinition) +} + +export async function loadServicesFromProtoDescriptor() { + // Load service definitions from descriptor set + const proto = await loadProtoDescriptorSet() // Extract host services and proto messages from the proto definition const hostServices = {}