From 4de06fac744e2f16d196b14f62203e26f3ea9551 Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Thu, 14 May 2026 05:04:29 +0800 Subject: [PATCH 01/13] doc: clarify filtered test behavior (#1725) - fix outdated `npm run unit` examples - explain that `--filter` affects test execution, not compilation scope - note that `npm test --filter=...` still triggers the default full rebuild --- CONTRIBUTING.md | 29 +++++++++++++++++++---------- unit-test/README.md | 22 ++++++++++++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6485f4279..663fc2304 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,27 +70,37 @@ npm test --NAPI_VERSION=X where X is the version of Node-API you want to target. -To run a specific unit test, filter conditions are available +To run a subset of the test suite, filter conditions are available. +The `--filter` option limits which JavaScript test modules are executed by +`node test`. The default `pretest` step is still `node-gyp rebuild -C test`, +so `npm test --filter=...` still performs a full rebuild of the test addon +targets before the filtered tests run. **Example:** - compile and run only tests on objectwrap.cc and objectwrap.js + perform the default test rebuild, then run only the `objectwrap` test module ``` - npm run unit --filter=objectwrap + npm test --filter=objectwrap ``` -Multiple unit tests cane be selected with wildcards +Multiple test modules can be selected with wildcards. **Example:** -compile and run all test files ending with "reference" -> function_reference.cc, object_reference.cc, reference.cc +perform the default test rebuild, then run all test modules ending with +`reference`: +`function_reference`, `object_reference`, and `reference` ``` - npm run unit --filter=*reference + npm test --filter=*reference ``` -Multiple filter conditions can be joined to broaden the test selection +Multiple filter conditions can be joined to broaden the test selection. **Example:** - compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file - npm run unit --filter='*function objectwrap' + perform the default test rebuild, then run all tests under + `threadsafe_function` and `typed_threadsafe_function`, and also the + `objectwrap` test module + ``` + npm test --filter='*function objectwrap' + ``` As an alternative, `ninja` can be used to build the tests. Please follow the instructions in [Build with ninja](doc/contributing/build_with_ninja.md). @@ -203,4 +213,3 @@ The downside of this approach is the following: authors might not find the right patterns and instead implement things themselves - There might be greater friction for the Node-API WG in evolving APIs since the ecosystem would have taken dependencies on the API shape of **node-addon-api** - diff --git a/unit-test/README.md b/unit-test/README.md index e10b1c448..2dfd5abfb 100644 --- a/unit-test/README.md +++ b/unit-test/README.md @@ -1,11 +1,17 @@ # Enable running tests with specific filter conditions: +The `--filter` option limits which test modules are executed by `node test`. +The default `pretest` step is still `node-gyp rebuild -C test`, so +`npm test --filter=...` still performs a full rebuild of the test addon +targets before the filtered tests run. + ### Example: - - compile and run only tests on objectwrap.cc and objectwrap.js + - perform the default test rebuild, then run only the `objectwrap` + test module ``` - npm run test --filter=objectwrap + npm test --filter=objectwrap ``` @@ -13,16 +19,20 @@ ### Example: - - compile and run all tests files ending with reference -> function_reference.cc object_reference.cc reference.cc + - perform the default test rebuild, then run all test modules ending + with `reference` + (`function_reference`, `object_reference`, and `reference`) ``` - npm run test --filter=*reference + npm test --filter=*reference ``` # Multiple filter conditions are also allowed ### Example: - - compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file + - perform the default test rebuild, then run all tests under + `threadsafe_function` and `typed_threadsafe_function`, and also the + `objectwrap` test module ``` - npm run test --filter='*function objectwrap' + npm test --filter='*function objectwrap' ``` From 6a9456fa9e05b1f9886b9eeccbb8b73379f74c4a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 10:20:27 -0400 Subject: [PATCH 02/13] chore: release v8.8.0 (#1721) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ README.md | 2 +- package.json | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5fd9048a6..6139450aa 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "8.7.0" + ".": "8.8.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 69bb7d160..7917ee681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # node-addon-api Changelog +## [8.8.0](https://github.com/nodejs/node-addon-api/compare/v8.7.0...v8.8.0) (2026-05-13) + + +### Features + +* add std::string_view overload for Symbol::For ([#1722](https://github.com/nodejs/node-addon-api/issues/1722)) ([f65113b](https://github.com/nodejs/node-addon-api/commit/f65113b6ce54271b0a26f97fc624b5574b64a048)) +* add String::New overload for string_view ([#1706](https://github.com/nodejs/node-addon-api/issues/1706)) ([0add130](https://github.com/nodejs/node-addon-api/commit/0add1306f60b81432da94d13683aa0b06aa52925)) + ## [8.7.0](https://github.com/nodejs/node-addon-api/compare/v8.6.0...v8.7.0) (2026-03-23) diff --git a/README.md b/README.md index 72a449eb2..221592218 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and exception handling semantics with low overhead. API references are available in the [doc](doc/README.md) directory. -## Current version: 8.7.0 +## Current version: 8.8.0 (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index d9b2e39bf..f245983d4 100644 --- a/package.json +++ b/package.json @@ -472,7 +472,7 @@ "lint:fix": "eslint --fix && node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "8.7.0", + "version": "8.8.0", "support": true, "engines": { "node": "^18 || ^20 || >= 21" From 00b95efea6522980e9661a729a59b926ecf5c6b6 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 24 May 2026 20:47:54 +0200 Subject: [PATCH 03/13] feat: add support for SharedArrayBuffer in TypedArray and TypedArrayOf (#1731) --- doc/typed_array.md | 15 +++++++++++++++ doc/typed_array_of.md | 28 ++++++++++++++++++++++++++++ napi-inl.h | 30 ++++++++++++++++++++++++++++++ napi.h | 42 +++++++++++++++++++++++++++++++++++++++++- test/typedarray.cc | 25 +++++++++++++++++++++++++ test/typedarray.js | 36 ++++++++++++++++++++++++++++++++++++ 6 files changed, 175 insertions(+), 1 deletion(-) diff --git a/doc/typed_array.md b/doc/typed_array.md index c2d3773a0..95aefb2e9 100644 --- a/doc/typed_array.md +++ b/doc/typed_array.md @@ -43,6 +43,21 @@ Napi::ArrayBuffer Napi::TypedArray::ArrayBuffer() const; Returns the backing array buffer. +**NOTE**: If the `Napi::TypedArray` is not backed by an `Napi::ArrayBuffer`, +this method will terminate the process with a fatal error when using +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior +otherwise. Use `Buffer()` instead to get the backing buffer without assuming its +type. + +### Buffer + +```cpp +Napi::Value Napi::TypedArray::Buffer() const; +``` + +Returns the backing array buffer as a generic `Napi::Value`, allowing optional +type-checking with `Is*()` and type-casting with `As<>()` methods. + ### ElementSize ```cpp diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index a982b80e5..4ced5841c 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -77,6 +77,34 @@ static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, Returns a new `Napi::TypedArrayOf` instance. +### New + +Wraps the provided `Napi::SharedArrayBuffer` into a new `Napi::TypedArray` instance. + +The array `type` parameter can normally be omitted (because it is inferred from +the template parameter `T`), except when creating a "clamped" array. + +```cpp +static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type); +``` + +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance. +- `[in] elementLength`: The length to array, in elements. +- `[in] arrayBuffer`: The backing `Napi::SharedArrayBuffer` instance. +- `[in] bufferOffset`: The offset into the `Napi::SharedArrayBuffer` where the array starts, + in bytes. +- `[in] type`: The type of array to allocate (optional). + +Returns a new `Napi::TypedArrayOf` instance. + +**NOTE**: The support for this overload of `Napi::TypedArrayOf::New()` is only +available when using `NAPI_EXPERIMENTAL` and building against Node.js headers +that supports this feature. + ### Constructor Initializes an empty instance of the `Napi::TypedArrayOf` class. diff --git a/napi-inl.h b/napi-inl.h index cc2fad6f7..9c0c47859 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2594,6 +2594,14 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { return Napi::ArrayBuffer(_env, arrayBuffer); } +inline Napi::Value TypedArray::Buffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); +} + //////////////////////////////////////////////////////////////////////////////// // TypedArrayOf class //////////////////////////////////////////////////////////////////////////////// @@ -2645,6 +2653,28 @@ inline TypedArrayOf TypedArrayOf::New(napi_env env, bufferOffset)); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); +} +#endif + template inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} diff --git a/napi.h b/napi.h index eaae1e711..870a5c290 100644 --- a/napi.h +++ b/napi.h @@ -1339,7 +1339,21 @@ class TypedArray : public Object { napi_typedarray_type TypedArrayType() const; ///< Gets the type of this typed-array. - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + + // Gets the backing `ArrayBuffer`. + // + // If this `TypedArray` is not backed by an `ArrayBuffer`, this method will + // terminate the process with a fatal error when using + // `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior + // otherwise. Use `Buffer()` instead to get the backing buffer without + // assuming its type. + Napi::ArrayBuffer ArrayBuffer() const; + + // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`). + // + // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the + // backing buffer prior to casting with `As()`. + Napi::Value Buffer() const; uint8_t ElementSize() const; ///< Gets the size in bytes of one element in the array. @@ -1433,6 +1447,32 @@ class TypedArrayOf : public TypedArray { ///< template parameter T. ); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + /// Creates a new TypedArray instance over a provided SharedArrayBuffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::SharedArrayBuffer + arrayBuffer, ///< Backing shared array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); +#endif + static void CheckCast(napi_env env, napi_value value); TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. diff --git a/test/typedarray.cc b/test/typedarray.cc index 0b9e0970e..795f2819e 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -297,6 +297,11 @@ Value GetTypedArrayBuffer(const CallbackInfo& info) { return array.ArrayBuffer(); } +Value GetTypedArrayBufferValue(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return array.Buffer(); +} + Value GetTypedArrayElement(const CallbackInfo& info) { TypedArray array = info[0].As(); size_t index = info[1].As().Uint32Value(); @@ -389,12 +394,30 @@ void SetTypedArrayElement(const CallbackInfo& info) { } } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +Value CreateInt8TypedArrayFromSharedArrayBuffer(const CallbackInfo& info) { + auto buffer = info[0].As(); + size_t length = buffer.ByteLength(); + + return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, + info.Env(), + length, + buffer.As(), + 0, + napi_int8_array); +} +#endif + } // end anonymous namespace Object InitTypedArray(Env env) { Object exports = Object::New(env); exports["createTypedArray"] = Function::New(env, CreateTypedArray); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createInt8TypedArrayFromSharedArrayBuffer"] = + Function::New(env, CreateInt8TypedArrayFromSharedArrayBuffer); +#endif exports["createInvalidTypedArray"] = Function::New(env, CreateInvalidTypedArray); exports["getTypedArrayType"] = Function::New(env, GetTypedArrayType); @@ -405,6 +428,8 @@ Object InitTypedArray(Env env) { exports["getTypedArrayByteLength"] = Function::New(env, GetTypedArrayByteLength); exports["getTypedArrayBuffer"] = Function::New(env, GetTypedArrayBuffer); + exports["getTypedArrayBufferValue"] = + Function::New(env, GetTypedArrayBufferValue); exports["getTypedArrayElement"] = Function::New(env, GetTypedArrayElement); exports["setTypedArrayElement"] = Function::New(env, SetTypedArrayElement); exports["checkBufferContent"] = Function::New(env, CheckBufferContent); diff --git a/test/typedarray.js b/test/typedarray.js index f7224efb7..b6ae4a7f7 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -2,6 +2,8 @@ const assert = require('assert'); +let runSharedArrayBufferTests = true; + module.exports = require('./common').runTest(test); function test (binding) { @@ -61,6 +63,9 @@ function test (binding) { const b = binding.typedarray.getTypedArrayBuffer(t); assert.ok(b instanceof ArrayBuffer); + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof ArrayBuffer); + assert.strictEqual(b, bAsValue); } catch (e) { console.log(data); throw e; @@ -100,4 +105,35 @@ function test (binding) { assert.throws(() => { binding.typedarray.createInvalidTypedArray(); }, /Invalid (pointer passed as )?argument/); + + if (binding.hasSharedArrayBuffer && runSharedArrayBufferTests) { + const length = 4; + const sab = new SharedArrayBuffer(length); + /** @type {Int8Array} */ + let t; + + try { + t = binding.typedarray.createInt8TypedArrayFromSharedArrayBuffer(sab); + } catch (ex) { + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating TypedArrays on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } + + throw ex; + } + + assert.ok(t instanceof Int8Array); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), 'int8'); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + for (let i = 0; i < length; i++) { + const value = 2 ** (i + 1); + t[i] = value; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, i), value); + } + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof SharedArrayBuffer); + assert.strictEqual(bAsValue, sab); + } } From e56a8dda287842b154cc3d7c9a9256a73367185e Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Sun, 24 May 2026 15:40:21 -0400 Subject: [PATCH 04/13] chore: add node v26 test (#1732) --- .github/workflows/ci-win.yml | 3 +++ .github/workflows/ci.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index 2417ab986..4b0ebfb23 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -22,6 +22,7 @@ jobs: - 22.x - 24.x - 25.x + - 26.x architecture: [x64, x86] os: - windows-2022 @@ -33,6 +34,8 @@ jobs: architecture: x86 - node-version: 25.x architecture: x86 + - node-version: 26.x + architecture: x86 runs-on: ${{ matrix.os }} steps: - name: Harden Runner diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5694ef0a..3813c3d62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: - 22.x - 24.x - 25.x + - 26.x os: - macos-latest - ubuntu-latest From 13c28f6673776e4015cd8d675e92b143177bb816 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:58:35 -0400 Subject: [PATCH 05/13] chore: release v8.9.0 (#1733) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ README.md | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6139450aa..a23c78a67 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "8.8.0" + ".": "8.9.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7917ee681..e3c30a2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # node-addon-api Changelog +## [8.9.0](https://github.com/nodejs/node-addon-api/compare/v8.8.0...v8.9.0) (2026-05-24) + + +### Features + +* add support for SharedArrayBuffer in TypedArray and TypedArrayOf<T> ([#1731](https://github.com/nodejs/node-addon-api/issues/1731)) ([00b95ef](https://github.com/nodejs/node-addon-api/commit/00b95efea6522980e9661a729a59b926ecf5c6b6)) + ## [8.8.0](https://github.com/nodejs/node-addon-api/compare/v8.7.0...v8.8.0) (2026-05-13) diff --git a/README.md b/README.md index 221592218..3259d7743 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and exception handling semantics with low overhead. API references are available in the [doc](doc/README.md) directory. -## Current version: 8.8.0 +## Current version: 8.9.0 (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index f245983d4..f3d472a06 100644 --- a/package.json +++ b/package.json @@ -472,7 +472,7 @@ "lint:fix": "eslint --fix && node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "8.8.0", + "version": "8.9.0", "support": true, "engines": { "node": "^18 || ^20 || >= 21" From 722351807e21eaada1df16de1e959006c907a031 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Fri, 31 Jul 2026 10:25:14 -0400 Subject: [PATCH 06/13] fix: fix vs2026 ICE compatibility (#1739) * fix: vs2026 ICE compatibility * deps: conditionally install node-gyp for vs2026 * fixup! increase CI timeout-minutes to 60 --- .github/workflows/ci-win.yml | 9 ++++++++- .github/workflows/ci.yml | 7 ++++++- napi-inl.h | 33 +++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index 4b0ebfb23..3bd60207a 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -10,7 +10,7 @@ permissions: jobs: test: - timeout-minutes: 30 + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -60,6 +60,13 @@ jobs: - name: Install dependencies run: | npm install + # node-gyp@12 (from package.json) supports Visual Studio 2026, but only + # node-gyp@13 emits the linker options that Node.js 26 builds require + # (older node-gyp trips LNK1117 on '/opt:lldltojobs'). Upgrade in place for + # Node.js >= 26; other versions keep node-gyp@12. + - name: Use node-gyp@13 for Node.js >= 26 + if: matrix.node-version == '26.x' + run: npm install --no-save node-gyp@13 - name: npm test shell: bash run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3813c3d62..c583b8c8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ permissions: jobs: test: - timeout-minutes: 30 + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -55,6 +55,11 @@ jobs: - name: Install dependencies run: | npm install + # Node.js >= 26 requires node-gyp@13; older versions keep node-gyp@12 + # (from package.json). + - name: Use node-gyp@13 for Node.js >= 26 + if: matrix.node-version == '26.x' + run: npm install --no-save node-gyp@13 - name: npm test run: | if [ "${{ matrix.api_version }}" = "experimental" ]; then diff --git a/napi-inl.h b/napi-inl.h index 9c0c47859..ec63ffeee 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4681,48 +4681,71 @@ template template ::InstanceVoidMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // MSVC (as of v145 / Visual Studio 2026) raises an internal compiler error + // (C1001) when a pointer-to-member-function is used as a non-type template + // parameter, as the static compile-time dispatch below does. On MSVC, fall + // back to the runtime overload, which passes `method` as a value instead. + return InstanceMethod(utf8name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = details::TemplatedInstanceVoidCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceMethod(utf8name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = details::TemplatedInstanceCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceVoidMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceMethod(name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = details::TemplatedInstanceVoidCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceMethod(name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = details::TemplatedInstanceCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template @@ -4768,6 +4791,10 @@ template ::InstanceGetterCallback getter, typename InstanceWrap::InstanceSetterCallback setter> inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( const char* utf8name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceAccessor(utf8name, getter, setter, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.getter = details::TemplatedInstanceCallback; @@ -4775,6 +4802,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( desc.data = data; desc.attributes = attributes; return desc; +#endif } template @@ -4782,6 +4810,10 @@ template ::InstanceGetterCallback getter, typename InstanceWrap::InstanceSetterCallback setter> inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( Symbol name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceAccessor(name, getter, setter, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.getter = details::TemplatedInstanceCallback; @@ -4789,6 +4821,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( desc.data = data; desc.attributes = attributes; return desc; +#endif } template diff --git a/package.json b/package.json index f3d472a06..32a2a9c5a 100644 --- a/package.json +++ b/package.json @@ -424,6 +424,7 @@ "eslint": "^9.13.0", "fs-extra": "^11.1.1", "neostandard": "^0.12.0", + "node-gyp": "^12.4.0", "pre-commit": "^1.2.2", "semver": "^7.6.0" }, From a815332197bc8cd7e295b6124bce673b05f4200b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:00:23 -0400 Subject: [PATCH 07/13] chore: release v8.9.1 (#1740) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ README.md | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a23c78a67..f51a1eb82 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "8.9.0" + ".": "8.9.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c30a2e5..466eca1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # node-addon-api Changelog +## [8.9.1](https://github.com/nodejs/node-addon-api/compare/v8.9.0...v8.9.1) (2026-07-31) + + +### Bug Fixes + +* fix vs2026 ICE compatibility ([#1739](https://github.com/nodejs/node-addon-api/issues/1739)) ([7223518](https://github.com/nodejs/node-addon-api/commit/722351807e21eaada1df16de1e959006c907a031)) + ## [8.9.0](https://github.com/nodejs/node-addon-api/compare/v8.8.0...v8.9.0) (2026-05-24) diff --git a/README.md b/README.md index 3259d7743..29184db10 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and exception handling semantics with low overhead. API references are available in the [doc](doc/README.md) directory. -## Current version: 8.9.0 +## Current version: 8.9.1 (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 32a2a9c5a..4504376cf 100644 --- a/package.json +++ b/package.json @@ -473,7 +473,7 @@ "lint:fix": "eslint --fix && node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "8.9.0", + "version": "8.9.1", "support": true, "engines": { "node": "^18 || ^20 || >= 21" From 13c854a1f57e754e5082a1961e0d32e8f0f96543 Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Tue, 11 Aug 2026 04:56:11 +0800 Subject: [PATCH 08/13] fix: resolve Symbol::For overload ambiguity (#1742) Signed-off-by: umuoy1 --- doc/symbol.md | 16 ++++--- napi-inl.h | 6 +++ napi.h | 43 ++++++++++++++++++ test/symbol.cc | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ test/symbol.js | 22 +++++++++ 5 files changed, 200 insertions(+), 5 deletions(-) diff --git a/doc/symbol.md b/doc/symbol.md index 4fa1ae0da..d0a63a144 100644 --- a/doc/symbol.md +++ b/doc/symbol.md @@ -51,20 +51,26 @@ Returns a `Napi::Symbol` representing a well-known `Symbol` from the ```cpp static Napi::Symbol Napi::Symbol::For(napi_env env, const std::string& description); static Napi::Symbol Napi::Symbol::For(napi_env env, std::string_view description); +template +static Napi::Symbol Napi::Symbol::For(napi_env env, T&& description); static Napi::Symbol Napi::Symbol::For(napi_env env, const char* description); -static Napi::Symbol Napi::Symbol::For(napi_env env, String description); +static Napi::Symbol Napi::Symbol::For(napi_env env, Napi::String description); static Napi::Symbol Napi::Symbol::For(napi_env env, napi_value description); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. - `[in] description`: The C++ string representing the `Napi::Symbol` in the global registry to retrieve. `description` may be any of: - - `const std::string&` - UTF8 string description. - - `std::string_view` - represents a UTF8 string view. - - `const char*` - represents a UTF8 string description. - - `String` - Node addon API String description. + - `const std::string&` - represents a UTF-8 string. + - `std::string_view` - represents a UTF-8 string view. + - `const char*` - represents a UTF-8 string description. + - `Napi::String` - Node-API string description. - `napi_value` - Node-API `napi_value` description. +String-like arguments implicitly convertible to both `const std::string&` and +`std::string_view` that do not have a unique best match among the non-template +overloads are resolved through `std::string_view`. + Searches in the global registry for existing symbol with the given name. If the symbol already exist it will be returned, otherwise a new symbol will be created in the registry. It's equivalent to Symbol.for() called from JavaScript. [`Napi::Name`]: ./name.md diff --git a/napi-inl.h b/napi-inl.h index ec63ffeee..4a1c18b6e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1428,6 +1428,12 @@ inline MaybeOrValue Symbol::For(napi_env env, return Symbol::For(env, descriptionValue); } +template > +inline MaybeOrValue Symbol::For(napi_env env, T&& description) { + std::string_view descriptionView = std::forward(description); + return Symbol::For(env, descriptionView); +} + inline MaybeOrValue Symbol::For(napi_env env, const char* description) { napi_value descriptionValue = String::New(env, description); return Symbol::For(env, descriptionValue); diff --git a/napi.h b/napi.h index 870a5c290..c534743e9 100644 --- a/napi.h +++ b/napi.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include // VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known @@ -786,6 +788,41 @@ class String : public Name { const; ///< Converts a String value to a UTF-16 encoded C++ string. }; +namespace details { + +// This overload set must mirror the non-template Symbol::For overloads. +struct string_convertible_probe { + static void select(const std::string&); + static void select(std::string_view); + static void select(const char*); + static void select(String); + static void select(napi_value); +}; + +template +struct has_unambiguous_string_convertible_overload : std::false_type {}; + +template +struct has_unambiguous_string_convertible_overload< + T, + std::void_t()))>> + : std::true_type {}; + +// Enable the template overload only for string-like arguments that have no +// unique best match among the non-template Symbol::For overloads. +// +// Exclude nullptr because it matches the pointer overloads equally well and +// cannot safely initialize a std::string_view. +template +using enable_if_ambiguous_string_convertible_t = + std::enable_if_t> && + std::is_convertible_v && + std::is_convertible_v && + !has_unambiguous_string_convertible_overload::value, + int>; + +} // namespace details + /// A JavaScript symbol value. class Symbol : public Name { public: @@ -831,6 +868,12 @@ class Symbol : public Name { // Create a symbol in the global registry, UTF-8 encoded cpp string view static MaybeOrValue For(napi_env env, std::string_view description); + // Resolve otherwise ambiguous string-like arguments through the + // std::string_view overload + template = 0> + static MaybeOrValue For(napi_env env, T&& description); + // Create a symbol in the global registry, C style string (null terminated) static MaybeOrValue For(napi_env env, const char* description); diff --git a/test/symbol.cc b/test/symbol.cc index d978739ff..e7c8b7447 100644 --- a/test/symbol.cc +++ b/test/symbol.cc @@ -1,10 +1,62 @@ #include #include +#include #include "test_helper.h" using namespace Napi; +namespace { + +struct StringLike { + operator std::string() const { return "unexpected-string-key"; } + operator std::string_view() const { return value; } + + std::string value; +}; + +struct RvalueStringLike { + operator std::string() && { return "unexpected-rvalue-string-key"; } + operator std::string_view() && { return value; } + + std::string value; +}; + +struct StringOnlyLike { + operator std::string() const { return value; } + + std::string value; +}; + +struct BothBases : std::string, std::string_view {}; + +struct ViewAndNapiString : std::string_view, Napi::String {}; + +struct StringReferenceLike { + operator std::string&() const { return stringValue; } + operator std::string&&() const { return std::move(stringValue); } + operator std::string_view() const { return viewValue; } + + mutable std::string stringValue; + std::string_view viewValue; +}; + +struct ImplicitAndExplicitStringViewLike { + operator std::string() const { return "unexpected-string-key"; } + + // Copy-initialization must ignore the explicit conversion below. + // Direct-initialization would prefer it for a non-const lvalue. + operator std::string_view() const& { return value; } + + explicit operator std::string_view() & { + return "unexpected-explicit-string-view-key"; + } + + std::string_view value; +}; + +} // namespace + Symbol CreateNewSymbolWithNoArgs(const Napi::CallbackInfo&) { return Napi::Symbol(); } @@ -47,6 +99,58 @@ Symbol FetchSymbolFromGlobalRegistryWithStringViewKey( return MaybeUnwrap(Napi::Symbol::For(info.Env(), std::string_view(key))); } +Symbol FetchSymbolFromGlobalRegistryWithStringLikeKey( + const Napi::CallbackInfo& info) { + StringLike key{info[0].As().Utf8Value()}; + return MaybeUnwrap(Napi::Symbol::For(info.Env(), key)); +} + +Symbol FetchSymbolFromGlobalRegistryWithRvalueStringLikeKey( + const Napi::CallbackInfo& info) { + return MaybeUnwrap(Napi::Symbol::For( + info.Env(), RvalueStringLike{info[0].As().Utf8Value()})); +} + +Symbol FetchSymbolFromGlobalRegistryWithStringOnlyLikeKey( + const Napi::CallbackInfo& info) { + StringOnlyLike key{info[0].As().Utf8Value()}; + return MaybeUnwrap(Napi::Symbol::For(info.Env(), key)); +} + +Symbol FetchSymbolFromGlobalRegistryWithBothBasesKey( + const Napi::CallbackInfo& info) { + std::string value = info[0].As().Utf8Value(); + BothBases key; + static_cast(key) = "unexpected-string-key"; + static_cast(key) = value; + return MaybeUnwrap(Symbol::For(info.Env(), key)); +} + +Symbol FetchSymbolFromGlobalRegistryWithViewAndNapiStringKey( + const Napi::CallbackInfo& info) { + Env env = info.Env(); + std::string value = info[0].As().Utf8Value(); + ViewAndNapiString key; + static_cast(key) = value; + static_cast(key) = + Napi::String::New(env, "unexpected-napi-string-key"); + return MaybeUnwrap(Symbol::For(env, key)); +} + +Symbol FetchSymbolFromGlobalRegistryWithStringReferenceKey( + const Napi::CallbackInfo& info) { + std::string value = info[0].As().Utf8Value(); + StringReferenceLike key{"unexpected-string-reference-key", value}; + return MaybeUnwrap(Symbol::For(info.Env(), key)); +} + +Symbol FetchSymbolFromGlobalRegistryWithImplicitViewKey( + const Napi::CallbackInfo& info) { + std::string value = info[0].As().Utf8Value(); + ImplicitAndExplicitStringViewLike key{value}; + return MaybeUnwrap(Symbol::For(info.Env(), key)); +} + Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { String cppStringKey = info[0].As(); return MaybeUnwrap( @@ -83,6 +187,20 @@ Object InitSymbol(Env env) { Function::New(env, FetchSymbolFromGlobalRegistryWithCppKey); exports["getSymbolFromGlobalRegistryWithStringViewKey"] = Function::New(env, FetchSymbolFromGlobalRegistryWithStringViewKey); + exports["getSymbolFromGlobalRegistryWithStringLikeKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithStringLikeKey); + exports["getSymbolFromGlobalRegistryWithRvalueStringLikeKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithRvalueStringLikeKey); + exports["getSymbolFromGlobalRegistryWithStringOnlyLikeKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithStringOnlyLikeKey); + exports["getSymbolFromGlobalRegistryWithBothBasesKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithBothBasesKey); + exports["getSymbolFromGlobalRegistryWithViewAndNapiStringKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithViewAndNapiStringKey); + exports["getSymbolFromGlobalRegistryWithStringReferenceKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithStringReferenceKey); + exports["getSymbolFromGlobalRegistryWithImplicitViewKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithImplicitViewKey); exports["testUndefinedSymbolCanBeCreated"] = Function::New(env, TestUndefinedSymbolsCanBeCreated); exports["testNullSymbolCanBeCreated"] = diff --git a/test/symbol.js b/test/symbol.js index baf39c81b..6ecbb90d3 100644 --- a/test/symbol.js +++ b/test/symbol.js @@ -42,6 +42,7 @@ function test (binding) { const symbTwo = fetchFunction(symbol); assert(symbOne && symbTwo); assert(symbOne === symbTwo); + assert(symbOne === Symbol.for(symbol)); } assertCanCreateSymbol('testing'); @@ -55,6 +56,27 @@ function test (binding) { assertCanCreateOrFetchGlobalSymbols('data', binding.symbol.getSymbolFromGlobalRegistry); assertCanCreateOrFetchGlobalSymbols('CppKey', binding.symbol.getSymbolFromGlobalRegistryWithCppKey); assertCanCreateOrFetchGlobalSymbols('StringViewKey', binding.symbol.getSymbolFromGlobalRegistryWithStringViewKey); + assertCanCreateOrFetchGlobalSymbols( + 'StringLikeKey', + binding.symbol.getSymbolFromGlobalRegistryWithStringLikeKey); + assertCanCreateOrFetchGlobalSymbols( + 'RvalueStringLikeKey', + binding.symbol.getSymbolFromGlobalRegistryWithRvalueStringLikeKey); + assertCanCreateOrFetchGlobalSymbols( + 'StringOnlyLikeKey', + binding.symbol.getSymbolFromGlobalRegistryWithStringOnlyLikeKey); + assertCanCreateOrFetchGlobalSymbols( + 'BothBasesKey', + binding.symbol.getSymbolFromGlobalRegistryWithBothBasesKey); + assertCanCreateOrFetchGlobalSymbols( + 'ViewAndNapiStringKey', + binding.symbol.getSymbolFromGlobalRegistryWithViewAndNapiStringKey); + assertCanCreateOrFetchGlobalSymbols( + 'StringReferenceKey', + binding.symbol.getSymbolFromGlobalRegistryWithStringReferenceKey); + assertCanCreateOrFetchGlobalSymbols( + 'ImplicitViewKey', + binding.symbol.getSymbolFromGlobalRegistryWithImplicitViewKey); assertCanCreateOrFetchGlobalSymbols('CKey', binding.symbol.getSymbolFromGlobalRegistryWithCKey); assert(binding.symbol.createNewSymbolWithNoArgs() === undefined); From 2faf3294972dfcb3707fd5e28d451485f19e3dad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:06:07 -0400 Subject: [PATCH 09/13] chore: release v8.9.2 (#1744) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ README.md | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f51a1eb82..b6df5aa29 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "8.9.1" + ".": "8.9.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 466eca1df..afe8fd559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # node-addon-api Changelog +## [8.9.2](https://github.com/nodejs/node-addon-api/compare/v8.9.1...v8.9.2) (2026-08-10) + + +### Bug Fixes + +* resolve Symbol::For overload ambiguity ([#1742](https://github.com/nodejs/node-addon-api/issues/1742)) ([13c854a](https://github.com/nodejs/node-addon-api/commit/13c854a1f57e754e5082a1961e0d32e8f0f96543)) + ## [8.9.1](https://github.com/nodejs/node-addon-api/compare/v8.9.0...v8.9.1) (2026-07-31) diff --git a/README.md b/README.md index 29184db10..268c1e70f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and exception handling semantics with low overhead. API references are available in the [doc](doc/README.md) directory. -## Current version: 8.9.1 +## Current version: 8.9.2 (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 4504376cf..bc824264d 100644 --- a/package.json +++ b/package.json @@ -473,7 +473,7 @@ "lint:fix": "eslint --fix && node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "8.9.1", + "version": "8.9.2", "support": true, "engines": { "node": "^18 || ^20 || >= 21" From 5a8e2857445967c7a693db4fc48249c29b87bff2 Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Thu, 13 Aug 2026 05:06:38 +0800 Subject: [PATCH 10/13] tools: find Python for git-clang-format (#1743) Signed-off-by: umuoy1 --- .github/workflows/linter.yml | 7 +++ tools/README.md | 5 +++ tools/clang-format.js | 83 +++++++++++++++++++++++++++++++++--- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 4cd921796..578223b8e 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -2,6 +2,9 @@ name: Style Checks on: [push, pull_request] +env: + PYTHON_VERSION: '3.11' + permissions: contents: read @@ -24,6 +27,10 @@ jobs: with: fetch-depth: 0 - run: git branch -a + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: diff --git a/tools/README.md b/tools/README.md index 6b80e94f5..7fab9ab61 100644 --- a/tools/README.md +++ b/tools/README.md @@ -4,6 +4,11 @@ The clang-format checking tools is designed to check changed lines of code compared to given git-refs. +The tool requires Python 3 to run `git-clang-format`. It first tries the +executable specified by the `PYTHON` environment variable, when set. On +Windows it then tries the Python launcher (`py -3`), followed by `python3` and +`python`. On other platforms it tries `python3` and then `python`. + ## Migration Script The migration tool is designed to reduce repetitive work in the migration process. However, the script is not aiming to convert every thing for you. There are usually some small fixes and major reconstruction required. diff --git a/tools/clang-format.js b/tools/clang-format.js index e4bb4f52e..60cbbc514 100644 --- a/tools/clang-format.js +++ b/tools/clang-format.js @@ -1,10 +1,52 @@ #!/usr/bin/env node -const spawn = require('child_process').spawnSync; +const spawnSync = require('child_process').spawnSync; const path = require('path'); const filesToCheck = ['*.h', '*.cc']; const FORMAT_START = process.env.FORMAT_START || 'main'; +const pythonVersionCheck = [ + '-c', + 'import sys; raise SystemExit(sys.version_info[0] != 3)' +]; + +function findPython () { + const candidates = []; + + if (process.env.PYTHON) { + candidates.push({ + command: process.env.PYTHON, + args: [], + name: process.env.PYTHON + }); + } + + if (process.platform === 'win32') { + candidates.push({ command: 'py', args: ['-3'], name: 'py -3' }); + } + + candidates.push( + { command: 'python3', args: [], name: 'python3' }, + { command: 'python', args: [], name: 'python' } + ); + + for (const candidate of candidates) { + const result = spawnSync( + candidate.command, + [...candidate.args, ...pythonVersionCheck], + { stdio: 'ignore' } + ); + if (!result.error && result.status === 0) { + return candidate; + } + } + + throw new Error([ + 'Could not find a usable Python 3 executable.', + `Tried: ${candidates.map(({ name }) => name).join(', ')}.`, + 'Set the PYTHON environment variable to the path of a Python 3 executable.' + ].join('\n')); +} function main (args) { let fix = false; @@ -31,17 +73,46 @@ function main (args) { } const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format'); - const result = spawn( - 'python', - [gitClangFormatPath, ...options, '--', ...filesToCheck], + let python; + try { + python = findPython(); + } catch (error) { + console.error(error.message); + return 2; + } + + const result = spawnSync( + python.command, + [ + ...python.args, + gitClangFormatPath, + ...options, + '--', + ...filesToCheck + ], { encoding: 'utf-8' } ); - if (result.stderr) { - console.error('Error running git-clang-format:', result.stderr); + if (result.error) { + console.error('Error running git-clang-format:', result.error.message); return 2; } + if (result.status !== 0 && result.status !== 1) { + const message = ( + result.stderr || + result.stdout || + result.signal || + `exit code ${result.status}` + ).trim(); + console.error(`Error running git-clang-format: ${message}`); + return 2; + } + + if (result.stderr) { + process.stderr.write(result.stderr); + } + const clangFormatOutput = result.stdout.trim(); // Bail fast if in fix mode. if (fix) { From 4be1e86d65da0499dca1fbe9bfb86605dbde5190 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Fri, 14 Aug 2026 08:51:52 -0400 Subject: [PATCH 11/13] doc: add inline comment hints on finalizer signatures (#1738) --- doc/basic_env.md | 11 ++++----- doc/external.md | 8 +++---- doc/threadsafe_function.md | 6 ++--- doc/typed_threadsafe_function.md | 2 +- napi.h | 38 +++++++++++++++++++++++++++++--- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/doc/basic_env.md b/doc/basic_env.md index 7a5b430f1..840a5480f 100644 --- a/doc/basic_env.md +++ b/doc/basic_env.md @@ -72,17 +72,18 @@ void SetInstanceData(DataType* data, HintType* hint) const; - `[template] fini`: A function to call when the instance data is to be deleted. Accepts a function of the form `void CleanupData(Napi::Env env, DataType* data, HintType* hint)`. If not given, the default finalizer will be used, which simply -uses the `delete` operator to destroy `T*` when the add-on instance is unloaded. +uses the `delete` operator to destroy `DataType*` when the add-on instance is +unloaded. - `[in] data`: A pointer to data that will be associated with the instance of the add-on for the duration of its lifecycle. - `[in] hint`: A pointer to data that will be associated with the instance of the add-on for the duration of its lifecycle and will be passed as a hint to `fini` when the add-on instance is unloaded. -Associates a data item stored at `T* data` with the current instance of the -add-on. The item will be passed to the function `fini` which gets called when an -instance of the add-on is unloaded. This overload accepts an additional hint to -be passed to `fini`. +Associates a data item stored at `DataType* data` with the current instance of +the add-on. The item will be passed to the function `fini` which gets called +when an instance of the add-on is unloaded. This overload accepts an additional +hint to be passed to `fini`. ### GetModuleFileName diff --git a/doc/external.md b/doc/external.md index 4b4603e8e..1c48560b6 100644 --- a/doc/external.md +++ b/doc/external.md @@ -35,9 +35,8 @@ Returns the created `Napi::External` object. ```cpp template -static Napi::External Napi::External::New(napi_env env, - T* data, - Finalizer finalizeCallback); +template +static External New(napi_env env, T* data, Finalizer finalizeCallback); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. @@ -52,7 +51,8 @@ Returns the created `Napi::External` object. ```cpp template -static Napi::External Napi::External::New(napi_env env, +template +static External New(napi_env env, T* data, Finalizer finalizeCallback, Hint* finalizeHint); diff --git a/doc/threadsafe_function.md b/doc/threadsafe_function.md index fcbc2dff1..9ab404731 100644 --- a/doc/threadsafe_function.md +++ b/doc/threadsafe_function.md @@ -70,9 +70,9 @@ New(napi_env env, opportunity for cleaning up after the threads e.g. by calling `uv_thread_join()`. It is important that, aside from the main loop thread, there be no threads left using the thread-safe function after the finalize - callback completes. Must implement `void operator()(Env env, DataType* data, - ContextType* hint)`, skipping `data` or `hint` if they are not provided. Can - be retrieved via `GetContext()`. + callback completes. Must implement `void operator()(Env env, + FinalizerDataType* data, ContextType* context)`, skipping `data` or `context` + if they are not provided. Can be retrieved via `GetContext()`. - `[optional] data`: Data to be passed to `finalizeCallback`. Returns a non-empty `Napi::ThreadSafeFunction` instance. diff --git a/doc/typed_threadsafe_function.md b/doc/typed_threadsafe_function.md index 74d3cc2ed..6dcc0343e 100644 --- a/doc/typed_threadsafe_function.md +++ b/doc/typed_threadsafe_function.md @@ -82,7 +82,7 @@ New(napi_env env, calling `uv_thread_join()`. It is important that, aside from the main loop thread, there be no threads left using the thread-safe function after the finalize callback completes. Must implement `void operator()(Env env, - FinalizerDataType* data, ContextType* hint)`. + FinalizerDataType* data, ContextType* context)`. - `[optional] data`: Data to be passed to `finalizeCallback`. Returns a non-empty `Napi::TypedThreadSafeFunction` instance. diff --git a/napi.h b/napi.h index c534743e9..f35587aa0 100644 --- a/napi.h +++ b/napi.h @@ -424,12 +424,16 @@ class BasicEnv { #endif // NAPI_VERSION > 8 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + // FinalizerType must implement `void operator()(Env env)`. template inline void PostFinalizer(FinalizerType finalizeCallback) const; + // FinalizerType must implement `void operator()(Env env, T* data)`. template inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; + // FinalizerType must implement `void operator()(Env env, T* data, + // Hint* hint)`. template inline void PostFinalizer(FinalizerType finalizeCallback, T* data, @@ -1150,9 +1154,12 @@ class Object : public TypeTaggable { const Function& constructor ///< Constructor function ) const; + // Finalizer must implement `void operator()(Env env, T* data)`. template inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; + // Finalizer must implement `void operator()(Env env, T* data, + // Hint* hint)`. template inline void AddFinalizer(Finalizer finalizeCallback, T* data, @@ -1200,7 +1207,8 @@ class External : public TypeTaggable { // Finalizer must implement `void operator()(Env env, T* data)`. template static External New(napi_env env, T* data, Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + // Finalizer must implement `void operator()(Env env, T* data, + // Hint* hint)`. template static External New(napi_env env, T* data, @@ -1755,7 +1763,8 @@ class Buffer : public Uint8Array { T* data, size_t length, Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + // Finalizer must implement `void operator()(Env env, T* data, + // Hint* hint)`. template static Buffer New(napi_env env, T* data, @@ -1771,7 +1780,8 @@ class Buffer : public Uint8Array { T* data, size_t length, Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + // Finalizer must implement `void operator()(Env env, T* data, + // Hint* hint)`. template static Buffer NewOrCopy(napi_env env, T* data, @@ -2868,6 +2878,7 @@ class ThreadSafeFunction { ContextType* context); // This API may only be called from the main thread. + // Finalizer must implement `void operator()(Env env)`. template static ThreadSafeFunction New(napi_env env, const Function& callback, @@ -2877,6 +2888,8 @@ class ThreadSafeFunction { Finalizer finalizeCallback); // This API may only be called from the main thread. + // Finalizer must implement + // `void operator()(Env env, FinalizerDataType* data)`. template @@ -2889,6 +2902,8 @@ class ThreadSafeFunction { FinalizerDataType* data); // This API may only be called from the main thread. + // Finalizer must implement + // `void operator()(Env env, ContextType* context)`. template static ThreadSafeFunction New(napi_env env, const Function& callback, @@ -2899,6 +2914,8 @@ class ThreadSafeFunction { Finalizer finalizeCallback); // This API may only be called from the main thread. + // Finalizer must implement `void operator()(Env env, + // FinalizerDataType* data, ContextType* context)`. template static ThreadSafeFunction New(napi_env env, const Function& callback, @@ -2942,6 +2960,8 @@ class ThreadSafeFunction { Finalizer finalizeCallback); // This API may only be called from the main thread. + // Finalizer must implement + // `void operator()(Env env, FinalizerDataType* data)`. template @@ -2955,6 +2975,8 @@ class ThreadSafeFunction { FinalizerDataType* data); // This API may only be called from the main thread. + // Finalizer must implement + // `void operator()(Env env, ContextType* context)`. template static ThreadSafeFunction New(napi_env env, const Function& callback, @@ -2966,6 +2988,8 @@ class ThreadSafeFunction { Finalizer finalizeCallback); // This API may only be called from the main thread. + // Finalizer must implement `void operator()(Env env, + // FinalizerDataType* data, ContextType* context)`. template @@ -3125,6 +3151,8 @@ class TypedThreadSafeFunction { // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [missing] Resource [passed] Finalizer [passed] + // Finalizer must implement `void operator()(Env env, + // FinalizerDataType* data, ContextType* context)`. template @@ -3167,6 +3195,8 @@ class TypedThreadSafeFunction { // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [passed] Resource [missing] Finalizer [passed] + // Finalizer must implement `void operator()(Env env, + // FinalizerDataType* data, ContextType* context)`. template @@ -3183,6 +3213,8 @@ class TypedThreadSafeFunction { // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [passed] Resource [passed] Finalizer [passed] + // Finalizer must implement `void operator()(Env env, + // FinalizerDataType* data, ContextType* context)`. template Date: Fri, 21 Aug 2026 11:29:27 +0800 Subject: [PATCH 12/13] feat!: bump minimum supported Node.js version to 22 (#1751) Signed-off-by: umuoy1 --- .github/workflows/ci-win.yml | 8 ++------ .github/workflows/ci.yml | 2 -- .github/workflows/codeql.yml | 4 ++-- README.md | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index 3bd60207a..a0dc12140 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -18,22 +18,18 @@ jobs: - standard - experimental node-version: - - 20.x - 22.x - 24.x - - 25.x - 26.x architecture: [x64, x86] os: - windows-2022 - windows-2025 exclude: - # Skip when node 24.x or 25.x AND architecture is x86 since there is - # no published Node.js x86 build for those versions. + # Skip Node.js 24.x and 26.x on x86 since there are no published + # Node.js x86 builds for those versions. - node-version: 24.x architecture: x86 - - node-version: 25.x - architecture: x86 - node-version: 26.x architecture: x86 runs-on: ${{ matrix.os }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c583b8c8e..22e867e45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,8 @@ jobs: - standard - experimental node-version: - - 20.x - 22.x - 24.x - - 25.x - 26.x os: - macos-latest diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8758fdcef..12f2cc70d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -68,11 +68,11 @@ jobs: # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - name: Use Node.js v18.x + - name: Use Node.js v22.x if: matrix.language == 'cpp' uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 18.x + node-version: 22.x - name: Build cpp if: matrix.language == 'cpp' diff --git a/README.md b/README.md index 268c1e70f..1d220793a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ This allows addons built with it to run with Node.js versions which support the **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. -The oldest Node.js version supported by the current version of node-addon-api is Node.js 18.x. +The oldest Node.js version supported by the current version of node-addon-api is Node.js 22.x. ## Badges diff --git a/package.json b/package.json index bc824264d..b8118238e 100644 --- a/package.json +++ b/package.json @@ -476,6 +476,6 @@ "version": "8.9.2", "support": true, "engines": { - "node": "^18 || ^20 || >= 21" + "node": "^22 || ^24 || >= 26" } } From 25f7ba08642611556f3e5d10b0cfb125cdf275df Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Sat, 22 Aug 2026 02:43:34 +0800 Subject: [PATCH 13/13] doc: clarify minimum C++ standard (#1754) Signed-off-by: umuoy1 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1d220793a..389ed8a33 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ This allows addons built with it to run with Node.js versions which support the every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. The oldest Node.js version supported by the current version of node-addon-api is Node.js 22.x. +The minimum supported C++ standard follows that of the oldest supported +Node.js release line. The current version therefore requires C++17 or later. ## Badges