From 8af64193958e41c24f1bfb3d58f2e35fc8aadf06 Mon Sep 17 00:00:00 2001 From: Stewart X Addison <6487691+sxa@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:16:59 +0000 Subject: [PATCH 1/7] test: aix: mark test-emit-on-destroyed as flaky Signed-off-by: Stewart X Addison PR-URL: https://github.com/nodejs/node/pull/61381 Refs: https://github.com/nodejs/node/issues/50245 Reviewed-By: Colin Ihrig Reviewed-By: Richard Lau Reviewed-By: Luigi Pinca --- test/async-hooks/async-hooks.status | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/async-hooks/async-hooks.status b/test/async-hooks/async-hooks.status index 673883e4fe3d4d..dbcb29baed84a0 100644 --- a/test/async-hooks/async-hooks.status +++ b/test/async-hooks/async-hooks.status @@ -21,3 +21,5 @@ test-callback-error: PASS, FLAKY [$system==freebsd] [$system==aix] +# https://github.com/nodejs/node/issues/50245 +test-emit-after-on-destroyed: PASS, FLAKY From e74b42842e768489423b7bde0767109327dc4f54 Mon Sep 17 00:00:00 2001 From: jhofstee Date: Fri, 16 Jan 2026 12:45:08 +0100 Subject: [PATCH 2/7] src: fix pointer alignment The NgLibMemoryManager::ReallocImpl method prefixes the allocated memory with its size, and returns a pointer to the region after it. This pointer can however no longer be suitably aligned. On Arm 32bits this resulted in unaligned accesses, since the NEON vst1.64 instruction was used with a not properly aligned addresses. A reproducer is available at [1]. Correct this by allocating the maximum of the the size of the size_t and the max alignment. [1] https://github.com/victronenergy/venus/issues/1559. PR-URL: https://github.com/nodejs/node/pull/61336 Reviewed-By: Matteo Collina Reviewed-By: Anna Henningsen Reviewed-By: Rafael Gonzaga --- src/node_mem-inl.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/node_mem-inl.h b/src/node_mem-inl.h index 70d28dd524be84..64f4704e62bb0c 100644 --- a/src/node_mem-inl.h +++ b/src/node_mem-inl.h @@ -8,6 +8,8 @@ namespace node { namespace mem { +static constexpr size_t kReserveSizeAndAlign = + std::max(sizeof(size_t), alignof(max_align_t)); template AllocatorStruct NgLibMemoryManager::MakeAllocator() { @@ -30,19 +32,18 @@ void* NgLibMemoryManager::ReallocImpl(void* ptr, char* original_ptr = nullptr; // We prepend each allocated buffer with a size_t containing the full - // size of the allocation. - if (size > 0) size += sizeof(size_t); + // size of the allocation, while keeping the returned pointer aligned. + if (size > 0) size += kReserveSizeAndAlign; if (ptr != nullptr) { // We are free()ing or re-allocating. - original_ptr = static_cast(ptr) - sizeof(size_t); + original_ptr = static_cast(ptr) - kReserveSizeAndAlign; previous_size = *reinterpret_cast(original_ptr); // This means we called StopTracking() on this pointer before. if (previous_size == 0) { // Fall back to the standard Realloc() function. char* ret = UncheckedRealloc(original_ptr, size); - if (ret != nullptr) - ret += sizeof(size_t); + if (ret != nullptr) ret += kReserveSizeAndAlign; return ret; } } @@ -62,7 +63,7 @@ void* NgLibMemoryManager::ReallocImpl(void* ptr, manager->env()->external_memory_accounter()->Update( manager->env()->isolate(), new_size); *reinterpret_cast(mem) = size; - mem += sizeof(size_t); + mem += kReserveSizeAndAlign; } else if (size == 0) { manager->DecreaseAllocatedSize(previous_size); manager->env()->external_memory_accounter()->Decrease( @@ -95,8 +96,8 @@ void* NgLibMemoryManager::CallocImpl(size_t nmemb, template void NgLibMemoryManager::StopTrackingMemory(void* ptr) { - size_t* original_ptr = reinterpret_cast( - static_cast(ptr) - sizeof(size_t)); + size_t* original_ptr = + reinterpret_cast(static_cast(ptr) - kReserveSizeAndAlign); Class* manager = static_cast(this); manager->DecreaseAllocatedSize(*original_ptr); manager->env()->external_memory_accounter()->Decrease( From 40958eb13a98901a3e1f737795293912784105b5 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Fri, 16 Jan 2026 14:45:18 +0300 Subject: [PATCH 3/7] fs: remove duplicate fd validation in sync functions PR-URL: https://github.com/nodejs/node/pull/61361 Reviewed-By: Aviv Keller Reviewed-By: Xuguang Mei --- lib/fs.js | 7 ------- src/node_file.cc | 30 ++++++++++++++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index ace5c464b73b14..1c65c01c939f39 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -597,7 +597,6 @@ function openAsBlob(path, options = kEmptyObject) { */ function read(fd, buffer, offsetOrOptions, length, position, callback) { fd = getValidatedFd(fd); - let offset = offsetOrOptions; let params = null; if (arguments.length <= 4) { @@ -689,8 +688,6 @@ ObjectDefineProperty(read, kCustomPromisifyArgsSymbol, * @returns {number} */ function readSync(fd, buffer, offsetOrOptions, length, position) { - fd = getValidatedFd(fd); - validateBuffer(buffer); let offset = offsetOrOptions; @@ -779,7 +776,6 @@ ObjectDefineProperty(readv, kCustomPromisifyArgsSymbol, * @returns {number} */ function readvSync(fd, buffers, position) { - fd = getValidatedFd(fd); validateBufferArray(buffers); if (typeof position !== 'number') @@ -809,7 +805,6 @@ function write(fd, buffer, offsetOrOptions, length, position, callback) { } fd = getValidatedFd(fd); - let offset = offsetOrOptions; if (isArrayBufferView(buffer)) { callback ||= position || length || offset; @@ -880,7 +875,6 @@ ObjectDefineProperty(write, kCustomPromisifyArgsSymbol, * @returns {number} */ function writeSync(fd, buffer, offsetOrOptions, length, position) { - fd = getValidatedFd(fd); const ctx = {}; let result; @@ -970,7 +964,6 @@ ObjectDefineProperty(writev, kCustomPromisifyArgsSymbol, { * @returns {number} */ function writevSync(fd, buffers, position) { - fd = getValidatedFd(fd); validateBufferArray(buffers); if (buffers.length === 0) { diff --git a/src/node_file.cc b/src/node_file.cc index 15e0ff923bc112..9015dbc4a9c6ee 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -2314,8 +2314,10 @@ static void WriteBuffer(const FunctionCallbackInfo& args) { const int argc = args.Length(); CHECK_GE(argc, 4); - CHECK(args[0]->IsInt32()); - const int fd = args[0].As()->Value(); + int fd; + if (!GetValidatedFd(env, args[0]).To(&fd)) { + return; + } CHECK(Buffer::HasInstance(args[1])); Local buffer_obj = args[1].As(); @@ -2381,8 +2383,10 @@ static void WriteBuffers(const FunctionCallbackInfo& args) { const int argc = args.Length(); CHECK_GE(argc, 3); - CHECK(args[0]->IsInt32()); - const int fd = args[0].As()->Value(); + int fd; + if (!GetValidatedFd(env, args[0]).To(&fd)) { + return; + } CHECK(args[1]->IsArray()); Local chunks = args[1].As(); @@ -2441,8 +2445,10 @@ static void WriteString(const FunctionCallbackInfo& args) { const int argc = args.Length(); CHECK_GE(argc, 4); - CHECK(args[0]->IsInt32()); - const int fd = args[0].As()->Value(); + int fd; + if (!GetValidatedFd(env, args[0]).To(&fd)) { + return; + } const int64_t pos = GetOffset(args[2]); @@ -2634,8 +2640,10 @@ static void Read(const FunctionCallbackInfo& args) { const int argc = args.Length(); CHECK_GE(argc, 5); - CHECK(args[0]->IsInt32()); - const int fd = args[0].As()->Value(); + int fd; + if (!GetValidatedFd(env, args[0]).To(&fd)) { + return; + } CHECK(Buffer::HasInstance(args[1])); Local buffer_obj = args[1].As(); @@ -2765,8 +2773,10 @@ static void ReadBuffers(const FunctionCallbackInfo& args) { const int argc = args.Length(); CHECK_GE(argc, 3); - CHECK(args[0]->IsInt32()); - const int fd = args[0].As()->Value(); + int fd; + if (!GetValidatedFd(env, args[0]).To(&fd)) { + return; + } CHECK(args[1]->IsArray()); Local buffers = args[1].As(); From ae001bb4b74007f66f60c7e051d1d285e7595355 Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Fri, 16 Jan 2026 14:30:19 +0100 Subject: [PATCH 4/7] doc: add marco and rafael in last sec release PR-URL: https://github.com/nodejs/node/pull/61383 Reviewed-By: Richard Lau Reviewed-By: Rafael Gonzaga Reviewed-By: Chengzhong Wu Reviewed-By: Luigi Pinca Reviewed-By: Yagiz Nizipli Reviewed-By: Matteo Collina Reviewed-By: Colin Ihrig --- doc/contributing/security-release-process.md | 47 ++++++++++---------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/doc/contributing/security-release-process.md b/doc/contributing/security-release-process.md index b58251de636daa..1107edc5a68976 100644 --- a/doc/contributing/security-release-process.md +++ b/doc/contributing/security-release-process.md @@ -19,29 +19,30 @@ steps listed in the process as outlined in The current security stewards are documented in the main Node.js [README.md](https://github.com/nodejs/node#security-release-stewards). -| Company | Person | Release Date | -| ------------ | --------------- | ------------ | -| NearForm | Matteo | 2021-Oct-12 | -| Datadog | Bryan | 2022-Jan-10 | -| RH and IBM | Joe | 2022-Mar-18 | -| NearForm | Matteo / Rafael | 2022-Jul-07 | -| Datadog | Vladimir | 2022-Sep-23 | -| NodeSource | Juan | 2022-Nov-04 | -| RH and IBM | Michael | 2023-Feb-16 | -| NearForm | Rafael | 2023-Jun-20 | -| NearForm | Rafael | 2023-Aug-09 | -| NearForm | Rafael | 2023-Oct-13 | -| NodeSource | Rafael | 2024-Feb-14 | -| NodeSource | Rafael | 2024-Apr-03 | -| NodeSource | Rafael | 2024-Apr-10 | -| NodeSource | Rafael | 2024-Jul-08 | -| NodeSource | Rafael | 2025-Jan-21 | -| NodeSource | Rafael | 2025-May-14 | -| NodeSource | Rafael | 2025-July-15 | -| Datadog | Bryan | | -| IBM | Joe | | -| Platformatic | Matteo | | -| NodeSource | Juan | | +| Company | Person | Release Date | +| ----------------------- | --------------- | ------------ | +| NearForm | Matteo | 2021-Oct-12 | +| Datadog | Bryan | 2022-Jan-10 | +| RH and IBM | Joe | 2022-Mar-18 | +| NearForm | Matteo / Rafael | 2022-Jul-07 | +| Datadog | Vladimir | 2022-Sep-23 | +| NodeSource | Juan | 2022-Nov-04 | +| RH and IBM | Michael | 2023-Feb-16 | +| NearForm | Rafael | 2023-Jun-20 | +| NearForm | Rafael | 2023-Aug-09 | +| NearForm | Rafael | 2023-Oct-13 | +| NodeSource | Rafael | 2024-Feb-14 | +| NodeSource | Rafael | 2024-Apr-03 | +| NodeSource | Rafael | 2024-Apr-10 | +| NodeSource | Rafael | 2024-Jul-08 | +| NodeSource | Rafael | 2025-Jan-21 | +| NodeSource | Rafael | 2025-May-14 | +| NodeSource | Rafael | 2025-Jul-15 | +| HeroDevs and NodeSource | Marco / Rafael | 2026-Jan-13 | +| Datadog | Bryan | | +| IBM | Joe | | +| Platformatic | Matteo | | +| NodeSource | Juan | | ## Planning From 3da4dce29658f1371fc943bc6141159260582a28 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 16 Jan 2026 15:30:12 +0100 Subject: [PATCH 5/7] test: split test-esm-loader-hooks Previously whenever one of the test case fails in the CI, it barely logged anything useful in the CI and it was difficult to nail down the specific failing case with a local reproduction, especially when the test fixutre is inline JavaScript. This patch: - Puts all the inline JavaScript in on-disk fixtures so that they can be re-run easily. - Split the tests into individual files so that it's easier to nail down the failure - Use spawnSyncAndAssert which logs useful information when the child process does not behave as expected. - Rename the tests as module-hooks/test-async-loader-hooks-* because they belong to the module hooks test suite and are not esm-specific. PR-URL: https://github.com/nodejs/node/pull/61374 Refs: https://github.com/nodejs/reliability/blob/main/reports/2026-01-13.md Reviewed-By: Marco Ippolito Reviewed-By: Luigi Pinca --- test/es-module/test-esm-loader-hooks.mjs | 860 ------------------ .../loader-delayed-async-load.mjs | 3 + .../es-module-loaders/loader-exit-on-load.mjs | 4 + .../loader-exit-on-resolve.mjs | 4 + .../loader-exit-top-level.mjs | 1 + .../loader-globalpreload-and-initialize.mjs | 2 + .../loader-globalpreload-only.mjs | 1 + .../loader-globalpreload-with-return.mjs | 3 + .../loader-initialize-exit.mjs | 3 + .../loader-initialize-never-settling.mjs | 3 + .../loader-initialize-rejecting.mjs | 3 + .../loader-initialize-throw-null.mjs | 3 + .../loader-load-commonjs-with-source.mjs | 13 + .../loader-load-source-maps.mjs | 9 + .../es-module-loaders/loader-mixed-opt-in.mjs | 28 + .../es-module-loaders/loader-throw-bigint.mjs | 1 + .../loader-throw-boolean.mjs | 1 + .../loader-throw-empty-object.mjs | 1 + .../es-module-loaders/loader-throw-error.mjs | 1 + .../loader-throw-function.mjs | 1 + .../es-module-loaders/loader-throw-null.mjs | 1 + .../es-module-loaders/loader-throw-number.mjs | 1 + .../es-module-loaders/loader-throw-object.mjs | 1 + .../es-module-loaders/loader-throw-string.mjs | 1 + .../es-module-loaders/loader-throw-symbol.mjs | 1 + .../loader-throw-undefined.mjs | 1 + .../es-module-loaders/loader-worker-spawn.mjs | 7 + ...ster-hooks-with-current-cwd-parent-url.mjs | 3 + .../register-loader-with-url-parenturl.mjs | 11 + .../register-loader-in-sequence.mjs | 11 + ...ister-loader-initialize-never-settling.mjs | 8 + .../module-hooks/register-loader-with-cjs.cjs | 14 + .../register-loader-with-ports.mjs | 22 + ...loader-hooks-called-with-expected-args.mjs | 28 + ...sync-loader-hooks-called-with-register.mjs | 31 + ...obalpreload-no-warning-with-initialize.mjs | 21 + ...ync-loader-hooks-globalpreload-warning.mjs | 23 + ...nc-loader-hooks-initialize-in-sequence.mjs | 21 + ...t-async-loader-hooks-initialize-invoke.mjs | 22 + ...loader-hooks-initialize-never-settling.mjs | 18 + ...c-loader-hooks-initialize-process-exit.mjs | 23 + ...sync-loader-hooks-initialize-rejecting.mjs | 24 + ...ync-loader-hooks-initialize-throw-null.mjs | 24 + .../test-async-loader-hooks-mixed-opt-in.mjs | 20 + ...oks-never-settling-import-meta-resolve.mjs | 20 + ...c-loader-hooks-never-settling-load-cjs.mjs | 20 + ...oks-never-settling-load-esm-no-warning.mjs | 22 + ...s-never-settling-load-esm-with-warning.mjs | 21 + ...c-loader-hooks-never-settling-race-cjs.mjs | 20 + ...c-loader-hooks-never-settling-race-esm.mjs | 20 + ...oader-hooks-never-settling-resolve-cjs.mjs | 20 + ...-never-settling-resolve-esm-no-warning.mjs | 22 + ...ever-settling-resolve-esm-with-warning.mjs | 21 + ...t-async-loader-hooks-no-leak-internals.mjs | 20 + ...-async-loader-hooks-process-exit-async.mjs | 24 + ...t-async-loader-hooks-process-exit-sync.mjs | 24 + ...nc-loader-hooks-process-exit-top-level.mjs | 22 + ...t-async-loader-hooks-register-with-cjs.mjs | 20 + ...sync-loader-hooks-register-with-import.mjs | 22 + ...async-loader-hooks-register-with-ports.mjs | 18 + ...ync-loader-hooks-register-with-require.mjs | 22 + ...ader-hooks-register-with-url-parenturl.mjs | 26 + ...oader-hooks-remove-beforeexit-listener.mjs | 22 + ...c-loader-hooks-require-resolve-default.mjs | 20 + ...nc-loader-hooks-require-resolve-opt-in.mjs | 22 + ...est-async-loader-hooks-source-maps-cjs.mjs | 23 + .../test-async-loader-hooks-throw-bigint.mjs | 22 + .../test-async-loader-hooks-throw-boolean.mjs | 22 + ...-async-loader-hooks-throw-empty-object.mjs | 22 + .../test-async-loader-hooks-throw-error.mjs | 22 + ...test-async-loader-hooks-throw-function.mjs | 22 + .../test-async-loader-hooks-throw-null.mjs | 22 + .../test-async-loader-hooks-throw-number.mjs | 22 + .../test-async-loader-hooks-throw-object.mjs | 22 + .../test-async-loader-hooks-throw-string.mjs | 22 + .../test-async-loader-hooks-throw-symbol.mjs | 20 + ...est-async-loader-hooks-throw-undefined.mjs | 22 + ...ync-loader-hooks-use-hooks-require-esm.mjs | 20 + ...r-hooks-with-worker-permission-allowed.mjs | 24 + ...ooks-with-worker-permission-restricted.mjs | 25 + ...loader-hooks-without-worker-permission.mjs | 25 + 81 files changed, 1227 insertions(+), 860 deletions(-) delete mode 100644 test/es-module/test-esm-loader-hooks.mjs create mode 100644 test/fixtures/es-module-loaders/loader-delayed-async-load.mjs create mode 100644 test/fixtures/es-module-loaders/loader-exit-on-load.mjs create mode 100644 test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs create mode 100644 test/fixtures/es-module-loaders/loader-exit-top-level.mjs create mode 100644 test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs create mode 100644 test/fixtures/es-module-loaders/loader-globalpreload-only.mjs create mode 100644 test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs create mode 100644 test/fixtures/es-module-loaders/loader-initialize-exit.mjs create mode 100644 test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs create mode 100644 test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs create mode 100644 test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs create mode 100644 test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs create mode 100644 test/fixtures/es-module-loaders/loader-load-source-maps.mjs create mode 100644 test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-bigint.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-boolean.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-empty-object.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-error.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-function.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-null.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-number.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-object.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-string.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-symbol.mjs create mode 100644 test/fixtures/es-module-loaders/loader-throw-undefined.mjs create mode 100644 test/fixtures/es-module-loaders/loader-worker-spawn.mjs create mode 100644 test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs create mode 100644 test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs create mode 100644 test/fixtures/module-hooks/register-loader-in-sequence.mjs create mode 100644 test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs create mode 100644 test/fixtures/module-hooks/register-loader-with-cjs.cjs create mode 100644 test/fixtures/module-hooks/register-loader-with-ports.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-called-with-register.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-process-exit-async.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-register-with-import.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-register-with-ports.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-register-with-require.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-bigint.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-boolean.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-error.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-function.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-null.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-number.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-object.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-string.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-symbol.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-throw-undefined.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs create mode 100644 test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs diff --git a/test/es-module/test-esm-loader-hooks.mjs b/test/es-module/test-esm-loader-hooks.mjs deleted file mode 100644 index 3ff5954c06ef54..00000000000000 --- a/test/es-module/test-esm-loader-hooks.mjs +++ /dev/null @@ -1,860 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -describe('Loader hooks', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('are called with all expected arguments', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-input.mjs'), - fixtures.path('es-modules/json-modules.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); - assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); - assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); - assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); - assert.strictEqual(lines[4], ''); - assert.strictEqual(lines.length, 5); - }); - - it('are called with all expected arguments using register function', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader=data:text/javascript,', - '--input-type=module', - '--eval', - "import { register } from 'node:module';" + - `register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-input.mjs'))});` + - `await import(${JSON.stringify(fixtures.fileURL('es-modules/json-modules.mjs'))});`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); - assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); - assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); - assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); - assert.strictEqual(lines[4], ''); - assert.strictEqual(lines.length, 5); - }); - - describe('should handle never-settling hooks in ESM files', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('top-level await of a never-settling resolve without warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a never-settling resolve with warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), - ]); - - assert.match(stderr, /Warning: Detected unsettled top-level await at.+never-resolve\.mjs:5/); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a never-settling load without warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a never-settling load with warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), - ]); - - assert.match(stderr, /Warning: Detected unsettled top-level await at.+never-load\.mjs:5/); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a race of never-settling hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/race.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^true\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('import.meta.resolve of a never-settling resolve should throw', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - }); - - describe('should handle never-settling hooks in CJS files', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('never-settling resolve', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.cjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - - it('never-settling load', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.cjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('race of never-settling hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/race.cjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^true\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - }); - - it('should not work without worker permission', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('empty.js'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ]); - - assert.match(stderr, /Error: Access to this API has been restricted/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should allow loader hooks to spawn workers when allowed by the CLI flags', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--permission', - '--allow-worker', - '--allow-fs-read', - '*', - '--experimental-loader', - `data:text/javascript,import{Worker}from"worker_threads";new Worker(${encodeURIComponent(JSON.stringify(fixtures.path('empty.js')))}).unref()`, - fixtures.path('es-modules/esm-top-level-await.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^1\r?\n2\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should not allow loader hooks to spawn workers if restricted by the CLI flags', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - `data:text/javascript,import{Worker}from"worker_threads";new Worker(${encodeURIComponent(JSON.stringify(fixtures.path('empty.js')))}).unref()`, - fixtures.path('es-modules/esm-top-level-await.mjs'), - ]); - - assert.match(stderr, /code: 'ERR_ACCESS_DENIED'/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should not leak internals or expose import.meta.resolve', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-edge-cases.mjs'), - fixtures.path('empty.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from a custom async hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,export function load(a,b,next){if(a==="data:exit")process.exit(42);return next(a,b)}', - '--input-type=module', - '--eval', - 'import "data:exit"', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from a custom sync hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,export function resolve(a,b,next){if(a==="exit:")process.exit(42);return next(a,b)}', - '--input-type=module', - '--eval', - 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from the loader thread top-level', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,process.exit(42)', - fixtures.path('empty.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - - describe('should handle a throwing top-level body', () => { - it('should handle regular Error object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw new Error("error message")', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /Error: error message\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle null', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw null', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\nnull\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle undefined', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw undefined', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\nundefined\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle boolean', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw true', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\ntrue\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle empty plain object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw {}', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n\{\}\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle plain object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw {fn(){},symbol:Symbol("symbol"),u:undefined}', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n\{ fn: \[Function: fn\], symbol: Symbol\(symbol\), u: undefined \}\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle number', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw 1', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n1\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle bigint', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw 1n', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n1\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle string', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw "literal string"', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\nliteral string\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle symbol', async () => { - const { code, signal, stdout } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'data:text/javascript,throw Symbol("symbol descriptor")', - fixtures.path('empty.js'), - ]); - - // Throwing a symbol doesn't produce any output - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle function', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw function fnName(){}', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n\[Function: fnName\]\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - }); - - describe('globalPreload', () => { - it('should emit warning', async () => { - const { stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'data:text/javascript,export function globalPreload(){}', - '--experimental-loader', - 'data:text/javascript,export function globalPreload(){return""}', - fixtures.path('empty.js'), - ]); - - assert.strictEqual(stderr.match(/`globalPreload` has been removed; use `initialize` instead/g).length, 1); - }); - - it('should not emit warning when initialize is supplied', async () => { - const { stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'data:text/javascript,export function globalPreload(){}export function initialize(){}', - fixtures.path('empty.js'), - ]); - - assert.doesNotMatch(stderr, /`globalPreload` has been removed; use `initialize` instead/); - }); - }); - - it('should be fine to call `process.removeAllListeners("beforeExit")` from the main thread', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,export function load(a,b,c){return new Promise(d=>setTimeout(()=>d(c(a,b)),99))}', - '--input-type=module', - '--eval', - 'setInterval(() => process.removeAllListeners("beforeExit"),1).unref();await import("data:text/javascript,")', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - describe('`initialize`/`register`', () => { - it('should invoke `initialize` correctly', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), - '--input-type=module', - '--eval', - 'import os from "node:os";', - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), ['hooks initialize 1', '']); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should allow communicating with loader via `register` ports', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {MessageChannel} from 'node:worker_threads'; - import {register} from 'node:module'; - import {once} from 'node:events'; - const {port1, port2} = new MessageChannel(); - port1.on('message', (msg) => { - console.log('message', msg); - }); - const result = register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize-port.mjs'))}, - {data: port2, transferList: [port2]}, - ); - console.log('register', result); - - const timeout = setTimeout(() => {}, 2**31 - 1); // to keep the process alive. - await Promise.all([ - once(port1, 'message').then(() => once(port1, 'message')), - import('node:os'), - ]); - clearTimeout(timeout); - port1.close(); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), [ 'register undefined', - 'message initialize', - 'message resolve node:os', - '' ]); - - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should have `register` accept URL objects as `parentURL`', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--import', - `data:text/javascript,${encodeURIComponent( - 'import{ register } from "node:module";' + - 'import { pathToFileURL } from "node:url";' + - 'register("./hooks-initialize.mjs", pathToFileURL("./"));' - )}`, - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'))}, - new URL('data:'), - ); - - import('node:os').then((result) => { - console.log(JSON.stringify(result)); - }); - `, - ], { cwd: fixtures.fileURL('es-module-loaders/') }); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); - - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should have `register` work with cjs', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=commonjs', - '--eval', - ` - 'use strict'; - const {register} = require('node:module'); - register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))}, - ); - register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'))}, - ); - - import('node:os').then((result) => { - console.log(JSON.stringify(result)); - }); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); - - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('`register` should work with `require`', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--require', - fixtures.path('es-module-loaders/register-loader.cjs'), - '--input-type=module', - '--eval', - 'import "node:os";', - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), ['resolve passthru', 'resolve passthru', '']); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('`register` should work with `import`', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--import', - fixtures.fileURL('es-module-loaders/register-loader.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), ['resolve passthru', '']); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should execute `initialize` in sequence', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - console.log('result 1', register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))} - )); - console.log('result 2', register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))} - )); - - await import('node:os'); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), [ 'hooks initialize 1', - 'result 1 undefined', - 'hooks initialize 2', - 'result 2 undefined', - '' ]); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should handle `initialize` returning never-settling promise', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - try { - register('data:text/javascript,export function initialize(){return new Promise(()=>{})}'); - } catch (e) { - console.log('caught', e.code); - } - `, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout.trim(), 'caught ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should handle `initialize` returning rejecting promise', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register('data:text/javascript,export function initialize(){return Promise.reject()}'); - `, - ]); - - assert.match(stderr, /undefined\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle `initialize` throwing null', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register('data:text/javascript,export function initialize(){throw null}'); - `, - ]); - - assert.match(stderr, /null\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from a initialize hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register('data:text/javascript,export function initialize(){process.exit(42);}'); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - }); - - it('should use CJS loader to respond to require.resolve calls by default', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'resolve passthru\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should use ESM loader to respond to require.resolve calls when opting in', async () => { - const readFile = async () => {}; - const fileURLToPath = () => {}; - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - `data:text/javascript,import{readFile}from"node:fs/promises";import{fileURLToPath}from"node:url";export ${ - async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - if (url.endsWith('/common/index.js')) { - result.source = '"use strict";module.exports=require("node:module").createRequire(' + - `${JSON.stringify(url)})(${JSON.stringify(fileURLToPath(url))});\n`; - } else if (url.startsWith('file:') && (context.format == null || context.format === 'commonjs')) { - result.source = await readFile(new URL(url)); - } - return result; - }}`, - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'resolve passthru\n'.repeat(10)); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should use hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--no-experimental-require-module', - '--import', - fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'), - fixtures.path('es-modules/require-esm-throws-with-loaders.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support source maps in commonjs translator', async () => { - const readFile = async () => {}; - const hook = ` - import { readFile } from 'node:fs/promises'; - export ${ - async function load(url, context, nextLoad) { - const resolved = await nextLoad(url, context); - if (context.format === 'commonjs') { - resolved.source = await readFile(new URL(url)); - } - return resolved; - } - }`; - - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--enable-source-maps', - '--import', - `data:text/javascript,${encodeURIComponent(` - import{ register } from "node:module"; - register(${ - JSON.stringify('data:text/javascript,' + encodeURIComponent(hook)) - }); - `)}`, - fixtures.path('source-map/throw-on-require.js'), - ]); - - assert.strictEqual(stdout, ''); - assert.match(stderr, /throw-on-require\.ts:9:9/); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle mixed of opt-in modules and non-opt-in ones', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - `data:text/javascript,const fixtures=${encodeURI(JSON.stringify(fixtures.path('empty.js')))};export ${ - encodeURIComponent(function resolve(s, c, n) { - if (s.endsWith('entry-point')) { - return { - shortCircuit: true, - url: 'file:///c:/virtual-entry-point', - format: 'commonjs', - }; - } - return n(s, c); - }) - }export ${ - encodeURIComponent(async function load(u, c, n) { - if (u === 'file:///c:/virtual-entry-point') { - return { - shortCircuit: true, - source: `"use strict";require(${JSON.stringify(fixtures)});console.log("Hello");`, - format: 'commonjs', - }; - } - return n(u, c); - })}`, - 'entry-point', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'Hello\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs b/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs new file mode 100644 index 00000000000000..33bae9bdafd9c9 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs @@ -0,0 +1,3 @@ +export function load(a, b, c) { + return new Promise(d => setTimeout(() => d(c(a, b)), 99)); +} diff --git a/test/fixtures/es-module-loaders/loader-exit-on-load.mjs b/test/fixtures/es-module-loaders/loader-exit-on-load.mjs new file mode 100644 index 00000000000000..4ee413fa46ce41 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-exit-on-load.mjs @@ -0,0 +1,4 @@ +export function load(a, b, next) { + if (a === 'data:exit') process.exit(42); + return next(a, b); +} diff --git a/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs b/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs new file mode 100644 index 00000000000000..cffa4bcc486a95 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs @@ -0,0 +1,4 @@ +export function resolve(a, b, next) { + if (a === 'exit:') process.exit(42); + return next(a, b); +} diff --git a/test/fixtures/es-module-loaders/loader-exit-top-level.mjs b/test/fixtures/es-module-loaders/loader-exit-top-level.mjs new file mode 100644 index 00000000000000..6427ca06037fcd --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-exit-top-level.mjs @@ -0,0 +1 @@ +process.exit(42); diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs new file mode 100644 index 00000000000000..7055b90f4e7568 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs @@ -0,0 +1,2 @@ +export function globalPreload() {} +export function initialize() {} diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs new file mode 100644 index 00000000000000..6b7f59d6319804 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs @@ -0,0 +1 @@ +export function globalPreload() {} diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs new file mode 100644 index 00000000000000..f54cf72ce5b9f9 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs @@ -0,0 +1,3 @@ +export function globalPreload() { + return ''; +} diff --git a/test/fixtures/es-module-loaders/loader-initialize-exit.mjs b/test/fixtures/es-module-loaders/loader-initialize-exit.mjs new file mode 100644 index 00000000000000..53cb4fa26f0164 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-exit.mjs @@ -0,0 +1,3 @@ +export function initialize() { + process.exit(42); +} diff --git a/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs b/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs new file mode 100644 index 00000000000000..80033b663a5a0d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs @@ -0,0 +1,3 @@ +export function initialize() { + return new Promise(() => {}); +} diff --git a/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs b/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs new file mode 100644 index 00000000000000..899accc15114e9 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs @@ -0,0 +1,3 @@ +export function initialize() { + return Promise.reject(); +} diff --git a/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs b/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs new file mode 100644 index 00000000000000..3d834b5c23cfb3 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs @@ -0,0 +1,3 @@ +export function initialize() { + throw null; +} diff --git a/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs b/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs new file mode 100644 index 00000000000000..3f431e630cddce --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs @@ -0,0 +1,13 @@ +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; + +export async function load(url, context, nextLoad) { + const result = await nextLoad(url, context); + if (url.endsWith('/common/index.js')) { + result.source = '"use strict";module.exports=require("node:module").createRequire(' + + JSON.stringify(url) + ')(' + JSON.stringify(fileURLToPath(url)) + ');\n'; + } else if (url.startsWith('file:') && (context.format == null || context.format === 'commonjs')) { + result.source = await readFile(new URL(url)); + } + return result; +} diff --git a/test/fixtures/es-module-loaders/loader-load-source-maps.mjs b/test/fixtures/es-module-loaders/loader-load-source-maps.mjs new file mode 100644 index 00000000000000..bf5ef6b91028a4 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-load-source-maps.mjs @@ -0,0 +1,9 @@ +import { readFile } from 'node:fs/promises'; + +export async function load(url, context, nextLoad) { + const resolved = await nextLoad(url, context); + if (context.format === 'commonjs') { + resolved.source = await readFile(new URL(url)); + } + return resolved; +} diff --git a/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs b/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs new file mode 100644 index 00000000000000..69b17a88249bef --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs @@ -0,0 +1,28 @@ +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const fixtures = require('../../common/fixtures.js'); + +const fixturesPath = fixtures.path('empty.js'); + +export function resolve(s, c, n) { + if (s.endsWith('entry-point')) { + return { + shortCircuit: true, + url: 'file:///c:/virtual-entry-point', + format: 'commonjs', + }; + } + return n(s, c); +} + +export async function load(u, c, n) { + if (u === 'file:///c:/virtual-entry-point') { + return { + shortCircuit: true, + source: `"use strict";require(${JSON.stringify(fixturesPath)});console.log("Hello");`, + format: 'commonjs', + }; + } + return n(u, c); +} diff --git a/test/fixtures/es-module-loaders/loader-throw-bigint.mjs b/test/fixtures/es-module-loaders/loader-throw-bigint.mjs new file mode 100644 index 00000000000000..700b0387fa7ffc --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-bigint.mjs @@ -0,0 +1 @@ +throw 1n; diff --git a/test/fixtures/es-module-loaders/loader-throw-boolean.mjs b/test/fixtures/es-module-loaders/loader-throw-boolean.mjs new file mode 100644 index 00000000000000..874fbd92e5fa22 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-boolean.mjs @@ -0,0 +1 @@ +throw true; diff --git a/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs b/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs new file mode 100644 index 00000000000000..778886fcb90a4d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs @@ -0,0 +1 @@ +throw {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-error.mjs b/test/fixtures/es-module-loaders/loader-throw-error.mjs new file mode 100644 index 00000000000000..816051b6c7831f --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-error.mjs @@ -0,0 +1 @@ +throw new Error('error message'); diff --git a/test/fixtures/es-module-loaders/loader-throw-function.mjs b/test/fixtures/es-module-loaders/loader-throw-function.mjs new file mode 100644 index 00000000000000..35503d670976cd --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-function.mjs @@ -0,0 +1 @@ +throw function fnName() {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-null.mjs b/test/fixtures/es-module-loaders/loader-throw-null.mjs new file mode 100644 index 00000000000000..37d3d14b8bfe1b --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-null.mjs @@ -0,0 +1 @@ +throw null; diff --git a/test/fixtures/es-module-loaders/loader-throw-number.mjs b/test/fixtures/es-module-loaders/loader-throw-number.mjs new file mode 100644 index 00000000000000..6dbc3994c7ac5d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-number.mjs @@ -0,0 +1 @@ +throw 1; diff --git a/test/fixtures/es-module-loaders/loader-throw-object.mjs b/test/fixtures/es-module-loaders/loader-throw-object.mjs new file mode 100644 index 00000000000000..fa7c9b7d43af4d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-object.mjs @@ -0,0 +1 @@ +throw { fn() {}, symbol: Symbol('symbol'), u: undefined }; diff --git a/test/fixtures/es-module-loaders/loader-throw-string.mjs b/test/fixtures/es-module-loaders/loader-throw-string.mjs new file mode 100644 index 00000000000000..613a4321f50de6 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-string.mjs @@ -0,0 +1 @@ +throw 'literal string'; diff --git a/test/fixtures/es-module-loaders/loader-throw-symbol.mjs b/test/fixtures/es-module-loaders/loader-throw-symbol.mjs new file mode 100644 index 00000000000000..05876e0b4ada86 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-symbol.mjs @@ -0,0 +1 @@ +throw Symbol('symbol descriptor'); diff --git a/test/fixtures/es-module-loaders/loader-throw-undefined.mjs b/test/fixtures/es-module-loaders/loader-throw-undefined.mjs new file mode 100644 index 00000000000000..38ecbdff9801f6 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-undefined.mjs @@ -0,0 +1 @@ +throw undefined; diff --git a/test/fixtures/es-module-loaders/loader-worker-spawn.mjs b/test/fixtures/es-module-loaders/loader-worker-spawn.mjs new file mode 100644 index 00000000000000..2860d116e6478c --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-worker-spawn.mjs @@ -0,0 +1,7 @@ +import { Worker } from 'worker_threads'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const fixtures = require('../../common/fixtures.js'); + +new Worker(fixtures.path('empty.js')).unref(); diff --git a/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs b/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs new file mode 100644 index 00000000000000..580d092ab5655a --- /dev/null +++ b/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs @@ -0,0 +1,3 @@ +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; +register('./hooks-initialize.mjs', pathToFileURL('./')); diff --git a/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs b/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs new file mode 100644 index 00000000000000..1d82d2abc5576d --- /dev/null +++ b/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs @@ -0,0 +1,11 @@ +import {register} from 'node:module'; +import fixtures from '../../common/fixtures.js'; + +register( + fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'), + new URL('data:'), +); + +import('node:os').then((result) => { + console.log(JSON.stringify(result)); +}); diff --git a/test/fixtures/module-hooks/register-loader-in-sequence.mjs b/test/fixtures/module-hooks/register-loader-in-sequence.mjs new file mode 100644 index 00000000000000..c129fcfc18092d --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-in-sequence.mjs @@ -0,0 +1,11 @@ +import {register} from 'node:module'; +import fixtures from '../../common/fixtures.js'; + +console.log('result 1', register( + fixtures.fileURL('es-module-loaders/hooks-initialize.mjs') +)); +console.log('result 2', register( + fixtures.fileURL('es-module-loaders/hooks-initialize.mjs') +)); + +await import('node:os'); diff --git a/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs b/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs new file mode 100644 index 00000000000000..13091effa970bf --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs @@ -0,0 +1,8 @@ +import {register} from 'node:module'; +import fixtures from '../../common/fixtures.js'; + +try { + register(fixtures.fileURL('es-module-loaders/loader-initialize-never-settling.mjs')); +} catch (e) { + console.log('caught', e.code); +} diff --git a/test/fixtures/module-hooks/register-loader-with-cjs.cjs b/test/fixtures/module-hooks/register-loader-with-cjs.cjs new file mode 100644 index 00000000000000..2cd6c666bd147d --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-with-cjs.cjs @@ -0,0 +1,14 @@ +'use strict'; +const {register} = require('node:module'); +const fixtures = require('../../common/fixtures.js'); + +register( + fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), +); +register( + fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'), +); + +import('node:os').then((result) => { + console.log(JSON.stringify(result)); +}); diff --git a/test/fixtures/module-hooks/register-loader-with-ports.mjs b/test/fixtures/module-hooks/register-loader-with-ports.mjs new file mode 100644 index 00000000000000..45e125f96a208f --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-with-ports.mjs @@ -0,0 +1,22 @@ +import {MessageChannel} from 'node:worker_threads'; +import {register} from 'node:module'; +import {once} from 'node:events'; +import fixtures from '../../common/fixtures.js'; + +const {port1, port2} = new MessageChannel(); +port1.on('message', (msg) => { + console.log('message', msg); +}); +const result = register( + fixtures.fileURL('es-module-loaders/hooks-initialize-port.mjs'), + {data: port2, transferList: [port2]}, +); +console.log('register', result); + +const timeout = setTimeout(() => {}, 2**31 - 1); // to keep the process alive. +await Promise.all([ + once(port1, 'message').then(() => once(port1, 'message')), + import('node:os'), +]); +clearTimeout(timeout); +port1.close(); diff --git a/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs b/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs new file mode 100644 index 00000000000000..195bf02bb6fdb3 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs @@ -0,0 +1,28 @@ +// Test that loader hooks are called with all expected arguments +import '../common/index.mjs'; +import assert from 'node:assert'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/hooks-input.mjs'), + fixtures.path('es-modules/json-modules.mjs'), + ], + { + stdout(output) { + const lines = output.split('\n'); + assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); + assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); + assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); + assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); + assert.strictEqual(lines.length, 4); + }, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-called-with-register.mjs b/test/module-hooks/test-async-loader-hooks-called-with-register.mjs new file mode 100644 index 00000000000000..d1f9181240d758 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-called-with-register.mjs @@ -0,0 +1,31 @@ +// Test that loader hooks are called with all expected arguments using register function +import '../common/index.mjs'; +import assert from 'node:assert'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader=data:text/javascript,', + '--input-type=module', + '--eval', + "import { register } from 'node:module';" + + `register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-input.mjs'))});` + + `await import(${JSON.stringify(fixtures.fileURL('es-modules/json-modules.mjs'))});`, + ], + { + stdout(output) { + const lines = output.split('\n'); + assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); + assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); + assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); + assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); + assert.strictEqual(lines.length, 4); + }, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs b/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs new file mode 100644 index 00000000000000..54501748bceac1 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs @@ -0,0 +1,21 @@ +// Test that `globalPreload` should not emit warning when `initialize` is supplied + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-globalpreload-and-initialize.mjs'), + fixtures.path('empty.js'), + ], + { + stderr(output) { + assert.doesNotMatch(output, /`globalPreload` has been removed; use `initialize` instead/); + }, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs b/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs new file mode 100644 index 00000000000000..36a2c0089a91d0 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs @@ -0,0 +1,23 @@ +// Test that `globalPreload` should emit warning +import '../common/index.mjs'; +import assert from 'node:assert'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-globalpreload-only.mjs'), + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-globalpreload-with-return.mjs'), + fixtures.path('empty.js'), + ], + { + stderr(output) { + const matches = output.match(/`globalPreload` has been removed; use `initialize` instead/g); + assert.strictEqual(matches.length, 1); + }, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs b/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs new file mode 100644 index 00000000000000..bc973f58784527 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs @@ -0,0 +1,21 @@ +// Test that `initialize` should execute in sequence +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + fixtures.path('module-hooks/register-loader-in-sequence.mjs'), + ], + { + stdout: 'hooks initialize 1\n' + + 'result 1 undefined\n' + + 'hooks initialize 2\n' + + 'result 2 undefined', + trim: true, + stderr: '', + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs b/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs new file mode 100644 index 00000000000000..72dc87c12e359a --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs @@ -0,0 +1,22 @@ +// Test that `initialize` should be invoked correctly +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), + '--input-type=module', + '--eval', + 'import os from "node:os";', + ], + { + stdout: 'hooks initialize 1', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs b/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs new file mode 100644 index 00000000000000..d1c12238ef0ca6 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs @@ -0,0 +1,18 @@ +// Test that `initialize` returning never-settling promise should be handled +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + fixtures.path('module-hooks/register-loader-initialize-never-settling.mjs'), + ], + { + stdout: 'caught ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs b/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs new file mode 100644 index 00000000000000..d9476af27e8f7d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs @@ -0,0 +1,23 @@ +// Test that it should be fine to call `process.exit` from a `initialize` hook +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-initialize-exit.mjs'), + '--input-type=module', + '--eval', + 'import "node:os"', + ], + { + status: 42, + signal: null, + stdout: '', + stderr: '', + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs b/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs new file mode 100644 index 00000000000000..e5d2a62fbc86da --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs @@ -0,0 +1,24 @@ +// Test that `initialize` returning rejecting promise should be handled +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-initialize-rejecting.mjs'), + '--input-type=module', + '--eval', + 'import "node:os"', + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /undefined$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs b/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs new file mode 100644 index 00000000000000..69844e31ae91fc --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs @@ -0,0 +1,24 @@ +// Test that `initialize` throwing null should be handled +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-initialize-throw-null.mjs'), + '--input-type=module', + '--eval', + 'import "node:os"', + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /null$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs b/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs new file mode 100644 index 00000000000000..398df25d7bbe66 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs @@ -0,0 +1,20 @@ +// Test that loader should handle mixed of opt-in modules and non-opt-in ones +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-mixed-opt-in.mjs'), + 'entry-point', + ], + { + stdout: 'Hello', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs new file mode 100644 index 00000000000000..35a4f7a566e519 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs @@ -0,0 +1,20 @@ +// Test import.meta.resolve of a never-settling resolve should throw in ESM files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs'), + ], + { + stdout: /^should be output$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs new file mode 100644 index 00000000000000..05401b9080b339 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs @@ -0,0 +1,20 @@ +// Test never-settling load in CJS files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.cjs'), + ], + { + stdout: /^should be output$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs new file mode 100644 index 00000000000000..dbadecc3287672 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs @@ -0,0 +1,22 @@ +// Test top-level await of a never-settling load without warning in ESM files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), + ], + { + status: 13, + signal: null, + stdout: /^should be output$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs new file mode 100644 index 00000000000000..d4b30ae23f86f5 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs @@ -0,0 +1,21 @@ +// Test top-level await of a never-settling load with warning in ESM files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), + ], + { + status: 13, + signal: null, + stdout: /^should be output$/, + stderr: /Warning: Detected unsettled top-level await at.+never-load\.mjs:5/, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs new file mode 100644 index 00000000000000..1de677f888383b --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs @@ -0,0 +1,20 @@ +// Test race of never-settling hooks in CJS files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/race.cjs'), + ], + { + stdout: /^true$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs new file mode 100644 index 00000000000000..e662dc43210d72 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs @@ -0,0 +1,20 @@ +// Test top-level await of a race of never-settling hooks in ESM files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/race.mjs'), + ], + { + stdout: /^true$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs new file mode 100644 index 00000000000000..1469a7b313cce2 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs @@ -0,0 +1,20 @@ +// Test never-settling resolve in CJS files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.cjs'), + ], + { + stdout: /^should be output$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs new file mode 100644 index 00000000000000..43a7a98127c851 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs @@ -0,0 +1,22 @@ +// Test top-level await of a never-settling resolve without warning in ESM files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), + ], + { + status: 13, + signal: null, + stdout: /^should be output$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs new file mode 100644 index 00000000000000..1a4c40c8bb96c0 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs @@ -0,0 +1,21 @@ +// Test top-level await of a never-settling resolve with warning in ESM files +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--experimental-loader', + fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), + fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), + ], + { + status: 13, + signal: null, + stdout: /^should be output$/, + stderr: /Warning: Detected unsettled top-level await at.+never-resolve\.mjs:5/, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs b/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs new file mode 100644 index 00000000000000..781aed8601f54a --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs @@ -0,0 +1,20 @@ +// Test that loader hooks should not leak internals or expose import.meta.resolve +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-edge-cases.mjs'), + fixtures.path('empty.js'), + ], + { + stdout: '', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs new file mode 100644 index 00000000000000..af3b939bcf8c0e --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs @@ -0,0 +1,24 @@ +// Test that it should be fine to call `process.exit` from a custom async hook +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-exit-on-load.mjs'), + '--input-type=module', + '--eval', + 'import "data:exit"', + ], + { + status: 42, + signal: null, + stdout: '', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs new file mode 100644 index 00000000000000..64c079b90b7507 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs @@ -0,0 +1,24 @@ +// Test that it should be fine to call `process.exit` from a custom sync hook +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-exit-on-resolve.mjs'), + '--input-type=module', + '--eval', + 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', + ], + { + status: 42, + signal: null, + stdout: '', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs new file mode 100644 index 00000000000000..a25c0013c5076d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs @@ -0,0 +1,22 @@ +// Test that it should be fine to call process.exit from the loader thread top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-exit-top-level.mjs'), + fixtures.path('empty.js'), + ], + { + status: 42, + signal: null, + stdout: '', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs b/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs new file mode 100644 index 00000000000000..8d83ee6be87816 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs @@ -0,0 +1,20 @@ +// Test that `register` should work with cjs +import '../common/index.mjs'; +import assert from 'node:assert'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + fixtures.path('module-hooks/register-loader-with-cjs.cjs'), + ], + { + stdout(output) { + assert.deepStrictEqual(output.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); + }, + stderr: '', + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-import.mjs b/test/module-hooks/test-async-loader-hooks-register-with-import.mjs new file mode 100644 index 00000000000000..454eccb5c62478 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-import.mjs @@ -0,0 +1,22 @@ +// Test that `register` should work with `import` +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--import', + fixtures.fileURL('es-module-loaders/register-loader.mjs'), + '--input-type=module', + '--eval', + 'import "node:os"', + ], + { + stdout: 'resolve passthru', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs b/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs new file mode 100644 index 00000000000000..022760596fd929 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs @@ -0,0 +1,18 @@ +// Test that loader should allow communicating with loader via `register` ports +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + fixtures.path('module-hooks/register-loader-with-ports.mjs'), + ], + { + stdout: 'register undefined\nmessage initialize\nmessage resolve node:os', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-require.mjs b/test/module-hooks/test-async-loader-hooks-register-with-require.mjs new file mode 100644 index 00000000000000..87ea43f8a66c9d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-require.mjs @@ -0,0 +1,22 @@ +// Test that `register` should work with `require` +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--require', + fixtures.path('es-module-loaders/register-loader.cjs'), + '--input-type=module', + '--eval', + 'import "node:os";', + ], + { + stdout: 'resolve passthru\nresolve passthru', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs b/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs new file mode 100644 index 00000000000000..a033ab3c4d2fdc --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs @@ -0,0 +1,26 @@ +// Test that `register` should accept URL objects as `parentURL` +import '../common/index.mjs'; +import assert from 'node:assert'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--import', + fixtures.fileURL('es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs'), + fixtures.path('es-module-loaders/register-loader-with-url-parenturl.mjs'), + ], + { + cwd: fixtures.path('es-module-loaders/'), + }, + { + stdout(output) { + assert.deepStrictEqual(output.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}'].sort()); + }, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs b/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs new file mode 100644 index 00000000000000..9a28bb59dd3164 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs @@ -0,0 +1,22 @@ +// Test that it should be fine to call `process.removeAllListeners("beforeExit")`("beforeExit") from the main thread +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-delayed-async-load.mjs'), + '--input-type=module', + '--eval', + 'setInterval(() => process.removeAllListeners("beforeExit"),1).unref();await import("data:text/javascript,")', + ], + { + stdout: '', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs new file mode 100644 index 00000000000000..71e539fe118b8b --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs @@ -0,0 +1,20 @@ +// Test that loader should use CJS loader to respond to `require.resolve` calls by default +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), + fixtures.path('require-resolve.js'), + ], + { + stdout: 'resolve passthru', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs new file mode 100644 index 00000000000000..0cbf2fd2d942a4 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs @@ -0,0 +1,22 @@ +// Test that loader should use ESM loader to respond to `require.resolve` calls when opting in +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-load-commonjs-with-source.mjs'), + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), + fixtures.path('require-resolve.js'), + ], + { + stdout: 'resolve passthru\n'.repeat(10).trim(), + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs b/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs new file mode 100644 index 00000000000000..83bd53405d1bd9 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs @@ -0,0 +1,23 @@ +// Test that loader should support source maps in commonjs translator +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--enable-source-maps', + '--import', + fixtures.fileURL('es-module-loaders/loader-load-source-maps.mjs'), + fixtures.path('source-map/throw-on-require.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /throw-on-require\.ts:9:9/, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs b/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs new file mode 100644 index 00000000000000..34136f81bf2193 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle bigint thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-bigint.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^1$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs b/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs new file mode 100644 index 00000000000000..476fad3c141535 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle boolean thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-boolean.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^true$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs b/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs new file mode 100644 index 00000000000000..ac09704c28ca6d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle empty plain object thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-empty-object.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^\{\}$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-error.mjs b/test/module-hooks/test-async-loader-hooks-throw-error.mjs new file mode 100644 index 00000000000000..b37ce50db1e3f4 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-error.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle regular Error object thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-error.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^Error: error message$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-function.mjs b/test/module-hooks/test-async-loader-hooks-throw-function.mjs new file mode 100644 index 00000000000000..bb371f727c84c7 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-function.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle function thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-function.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^\[Function: fnName\]$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-null.mjs b/test/module-hooks/test-async-loader-hooks-throw-null.mjs new file mode 100644 index 00000000000000..33528ff2b97560 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-null.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle null thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-null.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^null$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-number.mjs b/test/module-hooks/test-async-loader-hooks-throw-number.mjs new file mode 100644 index 00000000000000..da1f301089ca6c --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-number.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle number thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-number.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^1$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-object.mjs b/test/module-hooks/test-async-loader-hooks-throw-object.mjs new file mode 100644 index 00000000000000..8280bd0d105bb9 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-object.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle plain object thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-object.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^\{ fn: \[Function: fn\], symbol: Symbol\(symbol\), u: undefined \}$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-string.mjs b/test/module-hooks/test-async-loader-hooks-throw-string.mjs new file mode 100644 index 00000000000000..4ea6372e865c61 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-string.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle string thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-string.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^literal string$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs b/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs new file mode 100644 index 00000000000000..44339537ba9ce8 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs @@ -0,0 +1,20 @@ +// Test that loader hooks should handle symbol thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-symbol.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', // Throwing a symbol doesn't produce any output + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs b/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs new file mode 100644 index 00000000000000..aff5292a857f59 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs @@ -0,0 +1,22 @@ +// Test that loader hooks should handle undefined thrown from top-level +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-throw-undefined.mjs'), + fixtures.path('empty.js'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /^undefined$/m, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs b/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs new file mode 100644 index 00000000000000..a86b0607246fa9 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs @@ -0,0 +1,20 @@ +// Test that loader should use hooks for require of ESM +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-experimental-require-module', + '--import', + fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'), + fixtures.path('es-modules/require-esm-throws-with-loaders.js'), + ], + { + stdout: '', + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs b/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs new file mode 100644 index 00000000000000..9be7e8c39600a5 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs @@ -0,0 +1,24 @@ +// Test that loader hooks should allow spawning workers when allowed by CLI flags +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndAssert } from '../common/child_process.js'; + +spawnSyncAndAssert( + execPath, + [ + '--no-warnings', + '--permission', + '--allow-worker', + '--allow-fs-read', + '*', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-worker-spawn.mjs'), + fixtures.path('es-modules/esm-top-level-await.mjs'), + ], + { + stdout: /^1\n2$/, + stderr: '', + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs b/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs new file mode 100644 index 00000000000000..fe76f2f5842c00 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs @@ -0,0 +1,25 @@ +// Test that loader hooks should not allow spawning workers if restricted by CLI flags +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--permission', + '--allow-fs-read', + '*', + '--experimental-loader', + fixtures.fileURL('es-module-loaders/loader-worker-spawn.mjs'), + fixtures.path('es-modules/esm-top-level-await.mjs'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /code: 'ERR_ACCESS_DENIED'/, + trim: true, + }, +); diff --git a/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs b/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs new file mode 100644 index 00000000000000..21589234144db1 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs @@ -0,0 +1,25 @@ +// Test that loader hooks should not work without worker permission +import '../common/index.mjs'; +import { execPath } from 'node:process'; +import fixtures from '../common/fixtures.js'; +import { spawnSyncAndExit } from '../common/child_process.js'; + +spawnSyncAndExit( + execPath, + [ + '--no-warnings', + '--permission', + '--allow-fs-read', + '*', + '--experimental-loader', + fixtures.fileURL('empty.js'), + fixtures.path('es-modules/esm-top-level-await.mjs'), + ], + { + status: 1, + signal: null, + stdout: '', + stderr: /Error: Access to this API has been restricted/, + trim: true, + }, +); From 53b3c83ed4c9ac7eb351606d1a1fd2707308dccd Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 16 Jan 2026 08:00:54 -0800 Subject: [PATCH 6/7] node-api: use Node-API in comments PR-URL: https://github.com/nodejs/node/pull/61320 Reviewed-By: Aviv Keller Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig Reviewed-By: Chengzhong Wu --- benchmark/napi/function_args/index.js | 4 ++-- benchmark/napi/function_call/index.js | 2 +- src/js_native_api_types.h | 13 ++++++------- src/js_native_api_v8.cc | 17 ++++++++--------- src/js_native_api_v8_internals.h | 12 ++++++------ src/node_api.cc | 14 +++++++------- src/node_binding.cc | 2 +- test/benchmark/test-benchmark-napi.js | 2 +- test/cctest/test_linked_binding.cc | 3 ++- .../js-native-api/test_constructor/test_null.js | 2 +- test/js-native-api/test_date/test.js | 2 +- test/js-native-api/test_object/test_null.js | 2 +- test/js-native-api/test_promise/test.js | 2 +- test/js-native-api/test_string/test_null.js | 2 +- .../test_callback_scope/test-async-hooks.js | 2 +- test/node-api/test_fatal/test_fatal.c | 2 +- .../node-api/test_threadsafe_function/binding.c | 9 ++++++--- .../test_worker_buffer_callback.c | 6 +++--- test/node-api/test_worker_terminate/test.js | 2 +- 19 files changed, 51 insertions(+), 49 deletions(-) diff --git a/benchmark/napi/function_args/index.js b/benchmark/napi/function_args/index.js index 8ce9fa9d528f5c..2cf91650f4e77a 100644 --- a/benchmark/napi/function_args/index.js +++ b/benchmark/napi/function_args/index.js @@ -1,5 +1,5 @@ // Show the difference between calling a V8 binding C++ function -// relative to a comparable N-API C++ function, +// relative to a comparable Node-API C++ function, // in various types/numbers of arguments. // Reports n of calls per second. 'use strict'; @@ -19,7 +19,7 @@ try { try { napi = require(`./build/${common.buildType}/napi_binding`); } catch { - console.error(`${__filename}: NAPI-Binding failed to load`); + console.error(`${__filename}: Node-API binding failed to load`); process.exit(0); } diff --git a/benchmark/napi/function_call/index.js b/benchmark/napi/function_call/index.js index cde4c7ae223945..b22b5ac8b2151f 100644 --- a/benchmark/napi/function_call/index.js +++ b/benchmark/napi/function_call/index.js @@ -24,7 +24,7 @@ let napi_binding; try { napi_binding = require(`./build/${common.buildType}/napi_binding`); } catch { - console.error('misc/function_call/index.js NAPI-Binding failed to load'); + console.error('misc/function_call/index.js Node-API binding failed to load'); process.exit(0); } const napi = napi_binding.hello; diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 9642db6fba0281..add2088304a4bd 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -7,12 +7,11 @@ #ifdef NAPI_EXPERIMENTAL #define NAPI_VERSION NAPI_VERSION_EXPERIMENTAL #else -// The baseline version for N-API. -// The NAPI_VERSION controls which version will be used by default when -// compilling a native addon. If the addon developer specifically wants to use -// functions available in a new version of N-API that is not yet ported in all -// LTS versions, they can set NAPI_VERSION knowing that they have specifically -// depended on that version. +// The baseline version for Node-API. +// NAPI_VERSION controls which version is used by default when compiling +// a native addon. If the addon developer wants to use functions from a +// newer Node-API version not yet available in all LTS versions, they can +// set NAPI_VERSION to explicitly depend on that version. #define NAPI_VERSION 8 #endif #endif @@ -31,7 +30,7 @@ // This file needs to be compatible with C compilers. // This is a public include file, and these includes have essentially -// became part of it's API. +// become part of its API. #include // NOLINT(modernize-deprecated-headers) #include // NOLINT(modernize-deprecated-headers) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 42e15e959bb1c9..1a16232a72cba0 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -18,7 +18,7 @@ #define CHECK_TO_NUMBER(env, context, result, src) \ CHECK_TO_TYPE((env), Number, (context), (result), (src), napi_number_expected) -// n-api defines NAPI_AUTO_LENGTH as the indicator that a string +// Node-API defines NAPI_AUTO_LENGTH as the indicator that a string // is null terminated. For V8 the equivalent is -1. The assert // validates that our cast of NAPI_AUTO_LENGTH results in -1 as // needed by V8. @@ -225,7 +225,7 @@ inline napi_status V8NameFromPropertyDescriptor( return napi_ok; } -// convert from n-api property attributes to v8::PropertyAttribute +// convert from Node-API property attributes to v8::PropertyAttribute inline v8::PropertyAttribute V8PropertyAttributesFromDescriptor( const napi_property_descriptor* descriptor) { unsigned int attribute_flags = v8::PropertyAttribute::None; @@ -378,11 +378,10 @@ inline napi_status Unwrap(napi_env env, //=== Function napi_callback wrapper ================================= -// Use this data structure to associate callback data with each N-API function -// exposed to JavaScript. The structure is stored in a v8::External which gets -// passed into our callback wrapper. This reduces the performance impact of -// calling through N-API. -// Ref: benchmark/misc/function_call +// Use this data structure to associate callback data with each Node-API +// function exposed to JavaScript. The structure is stored in a v8::External +// which gets passed into our callback wrapper. This reduces the performance +// impact of calling through Node-API. Ref: benchmark/misc/function_call // Discussion (incl. perf. data): https://github.com/nodejs/node/pull/21072 class CallbackBundle { public: @@ -407,7 +406,7 @@ class CallbackBundle { } public: - napi_env env; // Necessary to invoke C++ NAPI callback + napi_env env; // Necessary to invoke C++ Node-API callback void* cb_data; // The user provided callback data napi_callback cb; @@ -2126,7 +2125,7 @@ napi_status NAPI_CDECL napi_get_null(napi_env env, napi_value* result) { // Gets all callback info in a single call. (Ugly, but faster.) napi_status NAPI_CDECL napi_get_cb_info( - napi_env env, // [in] NAPI environment handle + napi_env env, // [in] Node-API environment handle napi_callback_info cbinfo, // [in] Opaque callback-info handle size_t* argc, // [in-out] Specifies the size of the provided argv array // and receives the actual count of args. diff --git a/src/js_native_api_v8_internals.h b/src/js_native_api_v8_internals.h index 7bf3bf0fa7e1a2..0914cf8504adc5 100644 --- a/src/js_native_api_v8_internals.h +++ b/src/js_native_api_v8_internals.h @@ -1,14 +1,14 @@ #ifndef SRC_JS_NATIVE_API_V8_INTERNALS_H_ #define SRC_JS_NATIVE_API_V8_INTERNALS_H_ -// The V8 implementation of N-API, including `js_native_api_v8.h` uses certain -// idioms which require definition here. For example, it uses a variant of -// persistent references which need not be reset in the constructor. It is the -// responsibility of this file to define these idioms. Optionally, this file -// may also define `NAPI_VERSION` and set it to the version of N-API to be +// The V8 implementation of Node-API, including `js_native_api_v8.h` uses +// certain idioms which require definition here. For example, it uses a variant +// of persistent references which need not be reset in the constructor. It is +// the responsibility of this file to define these idioms. Optionally, this file +// may also define `NAPI_VERSION` and set it to the version of Node-API to be // exposed. -// In the case of the Node.js implementation of N-API some of the idioms are +// In the case of the Node.js implementation of Node-API some of the idioms are // imported directly from Node.js by including `node_internals.h` below. Others // are bridged to remove references to the `node` namespace. `node_version.h`, // included below, defines `NAPI_VERSION`. diff --git a/src/node_api.cc b/src/node_api.cc index cd17f7c199dae5..191b3a28f568b2 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -56,10 +56,10 @@ static void ThrowNodeApiVersionError(node::Environment* node_env, result = new node_napi_env__(context, module_filename, module_api_version); // TODO(addaleax): There was previously code that tried to delete the // napi_env when its v8::Context was garbage collected; - // However, as long as N-API addons using this napi_env are in place, + // However, as long as Node-API addons using this napi_env are in place, // the Context needs to be accessible and alive. // Ideally, we'd want an on-addon-unload hook that takes care of this - // once all N-API addons using this napi_env are unloaded. + // once all Node-API addons using this napi_env are unloaded. // For now, a per-Environment cleanup hook is the best we can do. result->node_env()->AddCleanupHook( [](void* arg) { static_cast(arg)->Unref(); }, @@ -150,7 +150,7 @@ void node_napi_env__::CallbackIntoModule(T&& call) { !enforceUncaughtExceptionPolicy) { ProcessEmitDeprecationWarning( node_env, - "Uncaught N-API callback exception detected, please run node " + "Uncaught Node-API callback exception detected, please run node " "with option --force-node-api-uncaught-exceptions-policy=true " "to handle those exceptions properly.", "DEP0168"); @@ -675,8 +675,8 @@ class AsyncContext { } // end of namespace v8impl // Intercepts the Node-V8 module registration callback. Converts parameters -// to NAPI equivalents and then calls the registration callback specified -// by the NAPI module. +// to Node-API equivalents and then calls the registration callback specified +// by the Node-API module. static void napi_module_register_cb(v8::Local exports, v8::Local module, v8::Local context, @@ -796,7 +796,7 @@ node_module napi_module_to_node_module(const napi_module* mod) { } } // namespace node -// Registers a NAPI module. +// Registers a Node-API module. void NAPI_CDECL napi_module_register(napi_module* mod) { node::node_module* nm = new node::node_module(node::napi_module_to_node_module(mod)); @@ -839,7 +839,7 @@ struct napi_async_cleanup_hook_handle__ { if (done_cb_ != nullptr) done_cb_(done_data_); // Release the `env` handle asynchronously since it would be surprising if - // a call to a N-API function would destroy `env` synchronously. + // a call to a Node-API function would destroy `env` synchronously. static_cast(env_)->node_env()->SetImmediate( [env = env_](node::Environment*) { env->Unref(); }); } diff --git a/src/node_binding.cc b/src/node_binding.cc index 5bd07e5253ae64..3b284583d6ccc9 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -526,7 +526,7 @@ void DLOpen(const FunctionCallbackInfo& args) { } } - // -1 is used for N-API modules + // -1 is used for Node-API modules if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) { // Even if the module did self-register, it may have done so with the // wrong version. We must only give up after having checked to see if it diff --git a/test/benchmark/test-benchmark-napi.js b/test/benchmark/test-benchmark-napi.js index 518e10a5111a5b..51137440774b43 100644 --- a/test/benchmark/test-benchmark-napi.js +++ b/test/benchmark/test-benchmark-napi.js @@ -3,7 +3,7 @@ const common = require('../common'); if (common.isWindows) { - common.skip('vcbuild.bat doesn\'t build the n-api benchmarks yet'); + common.skip('vcbuild.bat doesn\'t build the Node-API benchmarks yet'); } const { isMainThread } = require('worker_threads'); diff --git a/test/cctest/test_linked_binding.cc b/test/cctest/test_linked_binding.cc index bcfc5050c9a3ec..10507950becfc8 100644 --- a/test/cctest/test_linked_binding.cc +++ b/test/cctest/test_linked_binding.cc @@ -348,7 +348,8 @@ TEST_F(LinkedBindingTest, ManyBindingsTest) { AddLinkedBinding(*test_env, "local_linked1", InitializeLocalBinding, &calls); AddLinkedBinding(*test_env, "local_linked2", InitializeLocalBinding, &calls); AddLinkedBinding(*test_env, "local_linked3", InitializeLocalBinding, &calls); - AddLinkedBinding(*test_env, local_linked_napi); // Add a N-API addon as well. + AddLinkedBinding(*test_env, + local_linked_napi); // Add a Node-API addon as well. AddLinkedBinding(*test_env, "local_linked4", InitializeLocalBinding, &calls); AddLinkedBinding(*test_env, "local_linked5", InitializeLocalBinding, &calls); diff --git a/test/js-native-api/test_constructor/test_null.js b/test/js-native-api/test_constructor/test_null.js index 832d2e6ef4811a..701b952ff0a953 100644 --- a/test/js-native-api/test_constructor/test_null.js +++ b/test/js-native-api/test_constructor/test_null.js @@ -2,7 +2,7 @@ const common = require('../../common'); const assert = require('assert'); -// Test passing NULL to object-related N-APIs. +// Test passing NULL to object-related Node-APIs. const { testNull } = require(`./build/${common.buildType}/test_constructor`); const expectedResult = { envIsNull: 'Invalid argument', diff --git a/test/js-native-api/test_date/test.js b/test/js-native-api/test_date/test.js index e504208520e4d3..bfae9e55b2b8ae 100644 --- a/test/js-native-api/test_date/test.js +++ b/test/js-native-api/test_date/test.js @@ -2,7 +2,7 @@ const common = require('../../common'); -// This tests the date-related n-api calls +// This tests the date-related Node-API calls const assert = require('assert'); const test_date = require(`./build/${common.buildType}/test_date`); diff --git a/test/js-native-api/test_object/test_null.js b/test/js-native-api/test_object/test_null.js index 399952aaeb0724..cb68df16ce095f 100644 --- a/test/js-native-api/test_object/test_null.js +++ b/test/js-native-api/test_object/test_null.js @@ -2,7 +2,7 @@ const common = require('../../common'); const assert = require('assert'); -// Test passing NULL to object-related N-APIs. +// Test passing NULL to object-related Node-APIs. const { testNull } = require(`./build/${common.buildType}/test_object`); const expectedForProperty = { diff --git a/test/js-native-api/test_promise/test.js b/test/js-native-api/test_promise/test.js index 3b43e6132ed4d9..5980ba9def6839 100644 --- a/test/js-native-api/test_promise/test.js +++ b/test/js-native-api/test_promise/test.js @@ -2,7 +2,7 @@ const common = require('../../common'); -// This tests the promise-related n-api calls +// This tests the promise-related Node-API calls const assert = require('assert'); const test_promise = require(`./build/${common.buildType}/test_promise`); diff --git a/test/js-native-api/test_string/test_null.js b/test/js-native-api/test_string/test_null.js index ad19b4a82b588b..bd927fa3e48a3f 100644 --- a/test/js-native-api/test_string/test_null.js +++ b/test/js-native-api/test_string/test_null.js @@ -2,7 +2,7 @@ const common = require('../../common'); const assert = require('assert'); -// Test passing NULL to object-related N-APIs. +// Test passing NULL to object-related Node-APIs. const { testNull } = require(`./build/${common.buildType}/test_string`); const expectedResult = { diff --git a/test/node-api/test_callback_scope/test-async-hooks.js b/test/node-api/test_callback_scope/test-async-hooks.js index bb5927236f6765..7c12e25848d0f9 100644 --- a/test/node-api/test_callback_scope/test-async-hooks.js +++ b/test/node-api/test_callback_scope/test-async-hooks.js @@ -5,7 +5,7 @@ const assert = require('assert'); const async_hooks = require('async_hooks'); // The async_hook that we enable would register the process.emitWarning() -// call from loading the N-API addon as asynchronous activity because +// call from loading the Node-API addon as asynchronous activity because // it contains a process.nextTick() call. Monkey patch it to be a no-op // before we load the addon in order to avoid this. process.emitWarning = () => {}; diff --git a/test/node-api/test_fatal/test_fatal.c b/test/node-api/test_fatal/test_fatal.c index aebbda76888f4f..6d3f345c315829 100644 --- a/test/node-api/test_fatal/test_fatal.c +++ b/test/node-api/test_fatal/test_fatal.c @@ -2,7 +2,7 @@ // on a threading library for a new project it bears remembering that in the // future libuv may introduce API changes which may render it non-ABI-stable, // which, in turn, may affect the ABI stability of the project despite its use -// of N-API. +// of Node-API. #include #include #include "../../js-native-api/common.h" diff --git a/test/node-api/test_threadsafe_function/binding.c b/test/node-api/test_threadsafe_function/binding.c index 53b1cb3056370f..5a2e9355719685 100644 --- a/test/node-api/test_threadsafe_function/binding.c +++ b/test/node-api/test_threadsafe_function/binding.c @@ -2,7 +2,7 @@ // on a threading library for a new project it bears remembering that in the // future libuv may introduce API changes which may render it non-ABI-stable, // which, in turn, may affect the ABI stability of the project despite its use -// of N-API. +// of Node-API. #include #include #include "../../js-native-api/common.h" @@ -207,8 +207,11 @@ static napi_value StartThreadInternal(napi_env env, NODE_API_ASSERT(env, (ts_fn == NULL), "Existing thread-safe function"); napi_value async_name; - NODE_API_CALL(env, napi_create_string_utf8(env, - "N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &async_name)); + NODE_API_CALL(env, + napi_create_string_utf8(env, + "Node-API Thread-safe Function Test", + NAPI_AUTO_LENGTH, + &async_name)); NODE_API_CALL(env, napi_get_value_uint32(env, argv[3], &ts_info.max_queue_size)); NODE_API_CALL(env, napi_create_threadsafe_function(env, diff --git a/test/node-api/test_worker_buffer_callback/test_worker_buffer_callback.c b/test/node-api/test_worker_buffer_callback/test_worker_buffer_callback.c index bf4a101763d091..34307b44e55483 100644 --- a/test/node-api/test_worker_buffer_callback/test_worker_buffer_callback.c +++ b/test/node-api/test_worker_buffer_callback/test_worker_buffer_callback.c @@ -25,9 +25,9 @@ NAPI_MODULE_INIT() { NODE_API_CALL(env, napi_define_properties( env, exports, sizeof(properties) / sizeof(*properties), properties)); - // This is a slight variation on the non-N-API test: We create an ArrayBuffer - // rather than a Node.js Buffer, since testing the latter would only test - // the same code paths and not the ones specific to N-API. + // This is a slight variation on the non-Node-API test: We create an + // ArrayBuffer rather than a Node.js Buffer, since testing the latter would + // only test the same code paths and not the ones specific to Node-API. napi_value buffer; char* data = malloc(sizeof(char)); diff --git a/test/node-api/test_worker_terminate/test.js b/test/node-api/test_worker_terminate/test.js index eefb974af5a669..138acdbb59b9dc 100644 --- a/test/node-api/test_worker_terminate/test.js +++ b/test/node-api/test_worker_terminate/test.js @@ -5,7 +5,7 @@ const { Worker, isMainThread, workerData } = require('worker_threads'); if (isMainThread) { // Load the addon in the main thread first. - // This checks that N-API addons can be loaded from multiple contexts + // This checks that Node-API addons can be loaded from multiple contexts // when they are not loaded through NAPI_MODULE(). require(`./build/${common.buildType}/test_worker_terminate`); From e3071d52b6e8190d2158d7604161503a8b626b37 Mon Sep 17 00:00:00 2001 From: Vas Sudanagunta Date: Fri, 16 Jan 2026 12:48:44 -0500 Subject: [PATCH 7/7] test_runner: nix dead reporter code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reporter/utils.js formatTestReport: 1. hasChildren and showErrorDetails are never both true in existing calls. 2. Thus if hasChildren is true, showErrorDetails is false. 3. So `|| data.details?.error?.failureType === 'subtestsFailed'` is irrelevant. 4. And `\n${error}` never occurs. Even though all tests pass after this commit, what if future reporter code might make calls where both hasChildren and showErrorDetails are true? I will address this in the last commit of this PR. Trust me for now. PR-URL: https://github.com/nodejs/node/pull/59700 Reviewed-By: Pietro Marchini Reviewed-By: Moshe Atlow Reviewed-By: Jacob Smith Reviewed-By: Gürgün Dayıoğlu --- lib/internal/test_runner/reporter/spec.js | 9 ++------- lib/internal/test_runner/reporter/utils.js | 8 ++------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/internal/test_runner/reporter/spec.js b/lib/internal/test_runner/reporter/spec.js index 9031025e57d930..515d17fd42b455 100644 --- a/lib/internal/test_runner/reporter/spec.js +++ b/lib/internal/test_runner/reporter/spec.js @@ -53,7 +53,7 @@ class SpecReporter extends Transform { } this.#failedTests = []; // Clean up the failed tests - return ArrayPrototypeJoin(results, '\n'); ; + return ArrayPrototypeJoin(results, '\n'); } #handleTestReportEvent(type, data) { const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event @@ -71,13 +71,8 @@ class SpecReporter extends Transform { ArrayPrototypeUnshift(this.#reported, msg); prefix += `${indent(msg.nesting)}${reporterUnicodeSymbolMap['arrow:right']}${msg.name}\n`; } - let hasChildren = false; - if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) { - ArrayPrototypeShift(this.#reported); - hasChildren = true; - } const indentation = indent(data.nesting); - return `${formatTestReport(type, data, prefix, indentation, hasChildren, false)}\n`; + return `${formatTestReport(type, data, false, prefix, indentation)}\n`; } #handleEvent({ type, data }) { switch (type) { diff --git a/lib/internal/test_runner/reporter/utils.js b/lib/internal/test_runner/reporter/utils.js index 63656813c17376..26e4a2d1a5c36c 100644 --- a/lib/internal/test_runner/reporter/utils.js +++ b/lib/internal/test_runner/reporter/utils.js @@ -58,7 +58,6 @@ function indent(nesting) { } function formatError(error, indent) { - if (!error) return ''; const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error; const message = ArrayPrototypeJoin( RegExpPrototypeSymbolSplit( @@ -68,7 +67,7 @@ function formatError(error, indent) { return `\n${indent} ${message}\n`; } -function formatTestReport(type, data, prefix = '', indent = '', hasChildren = false, showErrorDetails = true) { +function formatTestReport(type, data, showErrorDetails = true, prefix = '', indent = '') { let color = reporterColorMap[type] ?? colors.white; let symbol = reporterUnicodeSymbolMap[type] ?? ' '; const { skip, todo, expectFailure } = data; @@ -83,10 +82,7 @@ function formatTestReport(type, data, prefix = '', indent = '', hasChildren = fa title += ` # EXPECTED FAILURE`; } - const error = showErrorDetails ? formatError(data.details?.error, indent) : ''; - const err = hasChildren ? - (!error || data.details?.error?.failureType === 'subtestsFailed' ? '' : `\n${error}`) : - error; + const err = showErrorDetails && data.details?.error ? formatError(data.details.error, indent) : ''; if (skip !== undefined) { color = colors.gray;