11#include < algorithm>
2+ #include < chrono>
23#include < climits> // INT_MAX
34#include < cmath>
5+ #include < filesystem>
46#include < sstream>
57#include < string_view> // string_view, u16string_view
8+ #include < thread>
69
710#define NAPI_EXPERIMENTAL
811
@@ -3301,6 +3304,28 @@ napi_status NAPI_CDECL napi_run_script_as_module(napi_env env,
33013304 v8::Isolate* isolate = env->isolate ;
33023305
33033306 v8::TryCatch module_try_catch (isolate);
3307+ auto set_last_exception = [&](v8::Local<v8::Value> exception) {
3308+ if (!env->last_exception .IsEmpty ()) {
3309+ env->last_exception .Reset ();
3310+ }
3311+ env->last_exception .Reset (env->isolate , exception);
3312+ };
3313+
3314+ // Normalize module path to keep cache keys stable across equivalent path
3315+ // spellings (e.g. ".", "..", and symlink traversals).
3316+ std::error_code pathError;
3317+ auto normalizedModulePath = std::filesystem::absolute (source_url, pathError);
3318+ if (pathError) {
3319+ pathError.clear ();
3320+ normalizedModulePath = std::filesystem::path (source_url);
3321+ }
3322+ normalizedModulePath = normalizedModulePath.lexically_normal ();
3323+ auto canonicalModulePath =
3324+ std::filesystem::weakly_canonical (normalizedModulePath, pathError);
3325+ if (!pathError) {
3326+ normalizedModulePath = canonicalModulePath;
3327+ }
3328+ const std::string modulePath = normalizedModulePath.string ();
33043329
33053330 // Initialize ES module system on first use
33063331 static bool es_module_initialized = false ;
@@ -3312,7 +3337,7 @@ napi_status NAPI_CDECL napi_run_script_as_module(napi_env env,
33123337 // Create script origin for ES module
33133338 v8::ScriptOrigin origin (
33143339 isolate,
3315- v8::String::NewFromUtf8 (isolate, source_url , v8::NewStringType::kNormal ).ToLocalChecked (),
3340+ v8::String::NewFromUtf8 (isolate, modulePath. c_str () , v8::NewStringType::kNormal ).ToLocalChecked (),
33163341 0 , 0 , false , -1 , v8::Local<v8::Value>(), false , false ,
33173342 true // is_module = true for ES modules
33183343 );
@@ -3330,10 +3355,6 @@ napi_status NAPI_CDECL napi_run_script_as_module(napi_env env,
33303355
33313356 v8::Local<v8::Module> module = maybe_module.ToLocalChecked ();
33323357
3333- // Register the module in our module registry for resolution
3334- // Use the source_url as the module path
3335- std::string modulePath = source_url;
3336-
33373358 // Safe Global handle management: Clear any existing entry first
33383359 auto it = v8impl::g_moduleRegistry.find (modulePath);
33393360 if (it != v8impl::g_moduleRegistry.end ()) {
@@ -3363,20 +3384,55 @@ napi_status NAPI_CDECL napi_run_script_as_module(napi_env env,
33633384 if (!module ->InstantiateModule (context, &v8impl::ResolveModuleCallback).FromMaybe (false )) {
33643385 if (instantiate_try_catch.HasCaught ()) {
33653386 // Store the exception in env->last_exception instead of throwing
3366- v8::Local<v8::Value> exception = instantiate_try_catch.Exception ();
3367- v8::String::Utf8Value error (isolate, exception);
3368-
3369- if (!env->last_exception .IsEmpty ()) {
3370- env->last_exception .Reset ();
3371- }
3372- env->last_exception .Reset (env->isolate , instantiate_try_catch.Exception ());
3387+ set_last_exception (instantiate_try_catch.Exception ());
33733388 }
33743389 return napi_set_last_error (env, napi_generic_failure);
33753390 }
33763391
33773392 // Evaluate the module
3393+ v8::TryCatch evaluate_try_catch (isolate);
33783394 v8::MaybeLocal<v8::Value> maybe_result = module ->Evaluate (context);
3379- CHECK_MAYBE_EMPTY_WITH_PREAMBLE (env, maybe_result, napi_generic_failure);
3395+ if (maybe_result.IsEmpty ()) {
3396+ if (evaluate_try_catch.HasCaught ()) {
3397+ set_last_exception (evaluate_try_catch.Exception ());
3398+ } else if (module ->GetStatus () == v8::Module::kErrored ) {
3399+ set_last_exception (module ->GetException ());
3400+ }
3401+ return napi_set_last_error (env, napi_generic_failure);
3402+ }
3403+
3404+ v8::Local<v8::Value> evaluate_result = maybe_result.ToLocalChecked ();
3405+
3406+ // Evaluate may return a promise; surface rejection as module-load failure.
3407+ if (evaluate_result->IsPromise ()) {
3408+ v8::Local<v8::Promise> promise = evaluate_result.As <v8::Promise>();
3409+ constexpr int kMaxAttempts = 100 ;
3410+ int attempts = 0 ;
3411+ while (attempts < kMaxAttempts &&
3412+ promise->State () == v8::Promise::kPending ) {
3413+ isolate->PerformMicrotaskCheckpoint ();
3414+ std::this_thread::sleep_for (std::chrono::milliseconds (1 ));
3415+ attempts++;
3416+ }
3417+
3418+ if (promise->State () == v8::Promise::kRejected ) {
3419+ set_last_exception (promise->Result ());
3420+ return napi_set_last_error (env, napi_generic_failure);
3421+ }
3422+
3423+ if (promise->State () == v8::Promise::kPending ) {
3424+ set_last_exception (v8::Exception::Error (
3425+ v8::String::NewFromUtf8 (isolate,
3426+ " Module evaluation did not settle" )
3427+ .ToLocalChecked ()));
3428+ return napi_set_last_error (env, napi_generic_failure);
3429+ }
3430+ }
3431+
3432+ if (module ->GetStatus () == v8::Module::kErrored ) {
3433+ set_last_exception (module ->GetException ());
3434+ return napi_set_last_error (env, napi_generic_failure);
3435+ }
33803436
33813437 // Get the module namespace as the result
33823438 v8::Local<v8::Value> namespace_obj = module ->GetModuleNamespace ();
@@ -3914,4 +3970,4 @@ napi_status napi_is_host_object(napi_env env, napi_value object, bool* result) {
39143970 *result = true ;
39153971
39163972 return napi_ok;
3917- }
3973+ }
0 commit comments