Replace Node.js version header generator with C++ implementation (#261)

Replaces the Node.js-based generate-version-header.js script with a C++
implementation to eliminate the Node.js runtime dependency from the build
process. The new C++ tool reads package.json and generates the same
version.h header file.

Also fixes Closure Compiler errors under strict mode in
extension_field_binary_info.js and map.js by properly assigning
goog.requireType calls to const variables instead of using standalone
goog.requireType calls.

Changes:
- Add generator/generate-version-header.cc with C++ implementation
- Remove generate-version-header.js
- Update generator/BUILD.bazel to use cc_binary instead of Node.js script
- Add rules_cc dependency to MODULE.bazel
- Update BUILD.bazel to remove deleted script from exports_files
- Fix goog.requireType usage in extension_field_binary_info.js and map.js
diff --git a/BUILD.bazel b/BUILD.bazel
index 6604d3a..7461adb 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -6,7 +6,7 @@
 load("@rules_pkg//:pkg.bzl", "pkg_tar", "pkg_zip")
 load("//:protobuf_javascript_release.bzl", "package_naming")
 
-exports_files(["generate-version-header.js", "package.json"])
+exports_files(["package.json"])
 
 config_setting(
     name = "x64_x86_windows",
@@ -36,13 +36,13 @@
 package_naming(
     name = "protobuf_javascript_pkg_naming",
     platform = select({
-        ":k8": "linux-x86_64", # currently the only supported build type in Github Actions
+        ":k8": "linux-x86_64",  # currently the only supported build type in Github Actions
         ":x64_x86_windows": "win32",
         ":x64_windows": "win64",
         ":darwin_arm64": "osx-aarch_64",
         ":darwin_x86_64": "osx-x86_64",
-        "//conditions:default": "" # continues with current behavior when no --cpu is specified allowing existing internal builds to function
-    })
+        "//conditions:default": "",  # continues with current behavior when no --cpu is specified allowing existing internal builds to function
+    }),
 )
 
 pkg_files(
@@ -54,15 +54,18 @@
 
 pkg_files(
     name = "dist_files",
-    srcs = glob([
-        "google/protobuf/*.js",
-        "google/protobuf/compiler/*.js"
-    ], allow_empty = True) + [
-        "google-protobuf.js",
-        "package.json",
-        "README.md",
+    srcs = glob(
+        [
+            "google/protobuf/*.js",
+            "google/protobuf/compiler/*.js",
+        ],
+        allow_empty = True,
+    ) + [
         "LICENSE.md",
         "LICENSE-asserts.md",
+        "README.md",
+        "google-protobuf.js",
+        "package.json",
     ],
     strip_prefix = strip_prefix.from_root(""),
 )
@@ -93,5 +96,5 @@
     srcs = [
         ":dist_tar",
         ":dist_zip",
-    ]
+    ],
 )
diff --git a/MODULE.bazel b/MODULE.bazel
index 2576f46..3acc005 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -1,5 +1,6 @@
 module(name = "protobuf_javascript", version = "4.0.1")
 
-bazel_dep(name = "protobuf", version = "33.0", repo_name = "com_google_protobuf")
-bazel_dep(name = "rules_pkg", version = "1.0.1")
 bazel_dep(name = "abseil-cpp", version = "20250512.1")
+bazel_dep(name = "protobuf", version = "33.0", repo_name = "com_google_protobuf")
+bazel_dep(name = "rules_cc", version = "0.2.15")
+bazel_dep(name = "rules_pkg", version = "1.0.1")
diff --git a/extension_field_binary_info.js b/extension_field_binary_info.js
index 4f79100..e8adeb1 100644
--- a/extension_field_binary_info.js
+++ b/extension_field_binary_info.js
@@ -36,15 +36,15 @@
 goog.module.declareLegacyNamespace();
 
 const ExtensionFieldInfo = goog.require('jspb.ExtensionFieldInfo');
-goog.requireType('jspb.BinaryReader');
-goog.requireType('jspb.BinaryWriter');
+const BinaryReader = goog.requireType('jspb.BinaryReader');
+const BinaryWriter = goog.requireType('jspb.BinaryWriter');
 
 /**
  * Stores binary-related information for a single extension field.
  * @param {!ExtensionFieldInfo<T>} fieldInfo
- * @param {function(this:jspb.BinaryReader,number,?,?)} binaryReaderFn
- * @param {function(this:jspb.BinaryWriter,number,?)
- *        |function(this:jspb.BinaryWriter,number,?,?,?,?,?)} binaryWriterFn
+ * @param {function(this:BinaryReader,number,?,?)} binaryReaderFn
+ * @param {function(this:BinaryWriter,number,?)
+ *        |function(this:BinaryWriter,number,?,?,?,?,?)} binaryWriterFn
  * @param {function(?,?)=} opt_binaryMessageSerializeFn
  * @param {function(?,?)=} opt_binaryMessageDeserializeFn
  * @param {boolean=} opt_isPacked
diff --git a/generate-version-header.js b/generate-version-header.js
deleted file mode 100644
index c21ca67..0000000
--- a/generate-version-header.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-const version = require('./package.json').version;
-const headerContent = `// Generated by generate-version-header.js
-#ifndef PROTOBUF_JAVASCRIPT_VERSION_H__
-#define PROTOBUF_JAVASCRIPT_VERSION_H__
-
-const char* const kProtobufJavascriptVersion = "${version}";
-
-#endif  // PROTOBUF_JAVASCRIPT_VERSION_H__
-`;
-
-fs.writeFileSync(process.argv[2], headerContent);
diff --git a/generator/BUILD.bazel b/generator/BUILD.bazel
index ac7756d..0c42d0f 100644
--- a/generator/BUILD.bazel
+++ b/generator/BUILD.bazel
@@ -1,9 +1,16 @@
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+
+cc_binary(
+    name = "generate-version-header",
+    srcs = ["generate-version-header.cc"],
+)
+
 genrule(
     name = "generate_version_header",
     srcs = ["//:package.json"],
     outs = ["version.h"],
-    cmd = "node $(location //:generate-version-header.js) $(OUTS)",
-    tools = ["//:generate-version-header.js"],
+    cmd = "$(location :generate-version-header) $(location //:package.json) $(OUTS)",
+    tools = [":generate-version-header"],
 )
 
 cc_binary(
@@ -18,15 +25,13 @@
     ],
     visibility = ["//visibility:public"],
     deps = [
+        "@abseil-cpp//absl/strings",
+        "@abseil-cpp//absl/strings:str_format",
         "@com_google_protobuf//:protobuf",
+        "@com_google_protobuf//:protoc_lib",
         "@com_google_protobuf//src/google/protobuf/compiler:code_generator",
         "@com_google_protobuf//src/google/protobuf/io",
         "@com_google_protobuf//src/google/protobuf/io:printer",
         "@com_google_protobuf//src/google/protobuf/io:tokenizer",
-        "@com_google_protobuf//:protoc_lib",
-        "@abseil-cpp//absl/strings",
-        "@abseil-cpp//absl/strings:str_format",
     ],
 )
-
-
diff --git a/generator/generate-version-header.cc b/generator/generate-version-header.cc
new file mode 100644
index 0000000..adca0da
--- /dev/null
+++ b/generator/generate-version-header.cc
@@ -0,0 +1,79 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <fstream>
+#include <iostream>
+#include <regex>
+#include <sstream>
+#include <string>
+
+std::string ReadPackageVersion(const std::string& package_json_path) {
+  std::ifstream file(package_json_path);
+  if (!file.is_open()) {
+    std::cerr << "Error: Could not open " << package_json_path << std::endl;
+    exit(1);
+  }
+
+  std::stringstream buffer;
+  buffer << file.rdbuf();
+  std::string content = buffer.str();
+
+  // Simple regex to extract version field from package.json
+  std::regex version_regex(R"#("version"\s*:\s*"([^"]+)")#");
+  std::smatch match;
+
+  if (std::regex_search(content, match, version_regex)) {
+    return match[1].str();
+  }
+
+  std::cerr << "Error: Could not find version in " << package_json_path
+            << std::endl;
+  exit(1);
+}
+
+void GenerateHeaderFile(const std::string& output_path,
+                        const std::string& version) {
+  std::ofstream file(output_path);
+  if (!file.is_open()) {
+    std::cerr << "Error: Could not write to " << output_path << std::endl;
+    exit(1);
+  }
+
+  file << "// Generated by generate-version-header.cc\n"
+       << "#ifndef PROTOBUF_JAVASCRIPT_VERSION_H__\n"
+       << "#define PROTOBUF_JAVASCRIPT_VERSION_H__\n"
+       << "\n"
+       << "const char* const kProtobufJavascriptVersion = \"" << version
+       << "\";\n"
+       << "\n"
+       << "#endif  // PROTOBUF_JAVASCRIPT_VERSION_H__\n";
+}
+
+int main(int argc, char* argv[]) {
+  if (argc != 3) {
+    std::cerr << "Usage: " << argv[0] << " <package_json_path> <output_header_path>" << std::endl;
+    return 1;
+  }
+
+  std::string package_json_path = argv[1];
+  std::string output_path = argv[2];
+
+  std::string version = ReadPackageVersion(package_json_path);
+  GenerateHeaderFile(output_path, version);
+
+  std::cout << "Generated " << output_path << " with version " << version
+            << std::endl;
+
+  return 0;
+}
diff --git a/map.js b/map.js
index e46087c..394d1d1 100644
--- a/map.js
+++ b/map.js
@@ -68,12 +68,8 @@
 goog.module('jspb.Map');
 goog.module.declareLegacyNamespace();
 
-
 const asserts = goog.require('jspb.asserts');
-
-goog.requireType('jspb.BinaryReader');
-goog.requireType('jspb.BinaryWriter');
-
+const BinaryReader = goog.requireType('jspb.BinaryReader');
 
 
 /**
@@ -268,13 +264,13 @@
 /** @override @final */
 ArrayIteratorIterable_.prototype.next = function () {
   if (this.idx_ < this.arr_.length) {
-    return {done: false, value: this.arr_[this.idx_++]};
+    return { done: false, value: this.arr_[this.idx_++] };
   } else {
-    return {done: true, value: undefined};
+    return { done: true, value: undefined };
   }
 };
 
-if (typeof(Symbol) != 'undefined') {
+if (typeof (Symbol) != 'undefined') {
   /** @override */
   ArrayIteratorIterable_.prototype[Symbol.iterator] = function () {
     return this;
@@ -488,17 +484,17 @@
  * we should default it to 0.
  * @template K, V
  * @param {!Map} map
- * @param {!jspb.BinaryReader} reader
- * @param {function(this:jspb.BinaryReader):K} keyReaderFn
+ * @param {!BinaryReader} reader
+ * @param {function(this:BinaryReader):K} keyReaderFn
  *     The method on BinaryReader that reads type K from the stream.
  *
- * @param {function(this:jspb.BinaryReader):V|
- *          function(this:jspb.BinaryReader,V,
- *                  function(V,!jspb.BinaryReader))} valueReaderFn
+ * @param {function(this:BinaryReader):V|
+ *          function(this:BinaryReader,V,
+ *                  function(V,!BinaryReader))} valueReaderFn
  *    The method on BinaryReader that reads type V from the stream. May be
  *    readMessage, in which case the second callback arg form is used.
  *
- * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback
+ * @param {?function(V,!BinaryReader)=} opt_valueReaderCallback
  *    The BinaryReader parsing callback for type V, if V is a message type
  *
  * @param {K=} opt_defaultKey
@@ -514,8 +510,8 @@
  *
  */
 Map.deserializeBinary = function (map, reader, keyReaderFn, valueReaderFn,
-                                      opt_valueReaderCallback, opt_defaultKey,
-                                      opt_defaultValue) {
+  opt_valueReaderCallback, opt_defaultKey,
+  opt_defaultValue) {
   var key = opt_defaultKey;
   var value = opt_defaultValue;
 
@@ -540,8 +536,8 @@
         valueReaderFn.call(reader, value, opt_valueReaderCallback);
       } else {
         value =
-            (/** @type {function(this:jspb.BinaryReader):?} */ (valueReaderFn))
-                .call(reader);
+          (/** @type {function(this:BinaryReader):?} */ (valueReaderFn))
+            .call(reader);
       }
     }
   }