From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <indutny@signal.org>
Date: Mon, 31 Mar 2025 11:21:29 -0700
Subject: fix: expose ReadFileSync override for modules

To avoid copying out `package.json` files out of the ASAR file we need
an API override to replace the native `ReadFileSync` in the `modules`
binding.

diff --git a/src/env_properties.h b/src/env_properties.h
index 82225b0a53dd828750991a4e15a060b736b6ea2b..4b0d31356a2496a7fc67876a22da2453efc54f53 100644
--- a/src/env_properties.h
+++ b/src/env_properties.h
@@ -508,6 +508,7 @@
   V(maybe_cache_generated_source_map, v8::Function)                            \
   V(messaging_deserialize_create_object, v8::Function)                         \
   V(message_port, v8::Object)                                                  \
+  V(modules_read_file_sync, v8::Function)                                      \
   V(builtin_module_require, v8::Function)                                      \
   V(performance_entry_callback, v8::Function)                                  \
   V(prepare_stack_trace_callback, v8::Function)                                \
diff --git a/src/node_modules.cc b/src/node_modules.cc
index c06779dea471b6f6a8dd29d4657162ef0faec043..6204986dc97686a248d6ae483f3a413ee5c51e47 100644
--- a/src/node_modules.cc
+++ b/src/node_modules.cc
@@ -21,6 +21,7 @@ namespace modules {
 
 using v8::Array;
 using v8::Context;
+using v8::Function;
 using v8::FunctionCallbackInfo;
 using v8::HandleScope;
 using v8::Isolate;
@@ -89,6 +90,7 @@ Local<Array> BindingData::PackageConfig::Serialize(Realm* realm) const {
 
 const BindingData::PackageConfig* BindingData::GetPackageJSON(
     Realm* realm, std::string_view path, ErrorContext* error_context) {
+  auto isolate = realm->isolate();
   auto binding_data = realm->GetBindingData<BindingData>();
 
   auto cache_entry = binding_data->package_configs_.find(path.data());
@@ -98,8 +100,36 @@ const BindingData::PackageConfig* BindingData::GetPackageJSON(
 
   PackageConfig package_config{};
   package_config.file_path = path;
+
+  Local<Function> modules_read_file_sync = realm->modules_read_file_sync();
+
+  int read_err;
   // No need to exclude BOM since simdjson will skip it.
-  if (ReadFileSync(&package_config.raw_json, path.data()) < 0) {
+  if (modules_read_file_sync.IsEmpty()) {
+    read_err = ReadFileSync(&package_config.raw_json, path.data());
+  } else {
+    Local<Value> args[] = {
+        v8::String::NewFromUtf8(isolate, path.data()).ToLocalChecked(),
+    };
+    Local<Value> result = modules_read_file_sync->Call(
+        realm->context(),
+        Undefined(isolate),
+        arraysize(args),
+        args).ToLocalChecked();
+
+    if (result->IsUndefined()) {
+      // Fallback
+      read_err = ReadFileSync(&package_config.raw_json, path.data());
+    } else if (result->IsFalse()) {
+      // Not found
+      read_err = -1;
+    } else {
+      BufferValue data(isolate, result);
+      package_config.raw_json = data.ToString();
+      read_err = 0;
+    }
+  }
+  if (read_err < 0) {
     return nullptr;
   }
   // In some systems, std::string is annotated to generate an
@@ -249,6 +279,12 @@ const BindingData::PackageConfig* BindingData::GetPackageJSON(
   return &cached.first->second;
 }
 
+void BindingData::OverrideReadFileSync(const FunctionCallbackInfo<Value>& args) {
+  Realm* realm = Realm::GetCurrent(args);
+  CHECK(args[0]->IsFunction());
+  realm->set_modules_read_file_sync(args[0].As<Function>());
+}
+
 void BindingData::ReadPackageJSON(const FunctionCallbackInfo<Value>& args) {
   CHECK_GE(args.Length(), 1);  // path, [is_esm, base, specifier]
   CHECK(args[0]->IsString());  // path
@@ -643,6 +679,8 @@ void InitImportMetaPathHelpers(const FunctionCallbackInfo<Value>& args) {
 void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
                                              Local<ObjectTemplate> target) {
   Isolate* isolate = isolate_data->isolate();
+  SetMethod(isolate, target, "overrideReadFileSync", OverrideReadFileSync);
+
   SetMethod(isolate, target, "readPackageJSON", ReadPackageJSON);
   SetMethod(isolate,
             target,
@@ -685,6 +723,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
 
 void BindingData::RegisterExternalReferences(
     ExternalReferenceRegistry* registry) {
+  registry->Register(OverrideReadFileSync);
+
   registry->Register(ReadPackageJSON);
   registry->Register(GetNearestParentPackageJSONType);
   registry->Register(GetNearestParentPackageJSON);
diff --git a/src/node_modules.h b/src/node_modules.h
index eb2900d8f8385238f89a6dcc972a28e5fcb1d288..e28f38d98f4f8749048af135f0dcbe55aa69c4fe 100644
--- a/src/node_modules.h
+++ b/src/node_modules.h
@@ -54,6 +54,8 @@ class BindingData : public SnapshotableObject {
   SET_SELF_SIZE(BindingData)
   SET_MEMORY_INFO_NAME(BindingData)
 
+  static void OverrideReadFileSync(
+      const v8::FunctionCallbackInfo<v8::Value>& args);
   static void ReadPackageJSON(const v8::FunctionCallbackInfo<v8::Value>& args);
   static void GetNearestParentPackageJSON(
       const v8::FunctionCallbackInfo<v8::Value>& args);
