From a669b53fb60a6b91ed0d20663e8b97517d61502d Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Wed, 23 Sep 2026 16:42:02 +0100 Subject: [PATCH 1/3] Fix Windows CI against Visual Studio 2026 --- .github/workflows/ci.yml | 5 +- .github/workflows/s3-bucket.yml | 12 +- lib/util/abi_crosswalk.json | 330 +++++++++++++++++++++++++++++++- lib/util/compile.js | 2 + package.json | 1 + scripts/upgrade-node-gyp.js | 42 ++++ test/build.test.js | 22 ++- test/run.util.js | 13 +- 8 files changed, 414 insertions(+), 13 deletions(-) create mode 100644 scripts/upgrade-node-gyp.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3726c45e..24f10f58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: node-version: ['lts/-1', 'lts/*', 'latest'] - os: [macos-latest, ubuntu-latest, ubuntu-24.04-arm, windows-latest] + os: [macos-latest, ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-2022] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 @@ -21,6 +21,9 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm ci + - name: Upgrade node-gyp if it cannot detect the installed Visual Studio + if: runner.os == 'Windows' + run: npm run upgrade-node-gyp - run: npm audit || true - run: npm run lint # - run: npm run update-crosswalk # To support newer versions of Node.js diff --git a/.github/workflows/s3-bucket.yml b/.github/workflows/s3-bucket.yml index d77cba5f..8948d938 100644 --- a/.github/workflows/s3-bucket.yml +++ b/.github/workflows/s3-bucket.yml @@ -12,27 +12,31 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest, windows-2022] node-version: ['lts/-1', 'lts/*', 'latest'] env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} S3_BUCKET: ${{ secrets.S3_BUCKET }} - name: Test S3 Bucket - Node ${{ matrix.node }} on ${{ matrix.os }} + name: Test S3 Bucket - Node ${{ matrix.node-version }} on ${{ matrix.os }} steps: - name: Checkout ${{ github.ref }} uses: actions/checkout@v7 - - name: Setup node ${{ matrix.node }} + - name: Setup node ${{ matrix.node-version }} uses: actions/setup-node@v7 with: - node-version: ${{ matrix.node }} + node-version: ${{ matrix.node-version }} - name: NPM Install run: npm install + - name: Upgrade node-gyp if it cannot detect the installed Visual Studio + if: runner.os == 'Windows' + run: npm run upgrade-node-gyp + - name: Show Environment Info run: | printenv diff --git a/lib/util/abi_crosswalk.json b/lib/util/abi_crosswalk.json index 2b376e16..9ce86bd6 100644 --- a/lib/util/abi_crosswalk.json +++ b/lib/util/abi_crosswalk.json @@ -3119,6 +3119,38 @@ "node_abi": 115, "v8": "11.3" }, + "20.19.2": { + "node_abi": 115, + "v8": "11.3" + }, + "20.19.3": { + "node_abi": 115, + "v8": "11.3" + }, + "20.19.4": { + "node_abi": 115, + "v8": "11.3" + }, + "20.19.5": { + "node_abi": 115, + "v8": "11.3" + }, + "20.19.6": { + "node_abi": 115, + "v8": "11.3" + }, + "20.20.0": { + "node_abi": 115, + "v8": "11.3" + }, + "20.20.1": { + "node_abi": 115, + "v8": "11.3" + }, + "20.20.2": { + "node_abi": 115, + "v8": "11.3" + }, "21.0.0": { "node_abi": 120, "v8": "11.8" @@ -3247,6 +3279,70 @@ "node_abi": 127, "v8": "12.4" }, + "22.15.1": { + "node_abi": 127, + "v8": "12.4" + }, + "22.16.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.17.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.17.1": { + "node_abi": 127, + "v8": "12.4" + }, + "22.18.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.19.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.20.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.21.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.21.1": { + "node_abi": 127, + "v8": "12.4" + }, + "22.22.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.22.1": { + "node_abi": 127, + "v8": "12.4" + }, + "22.22.2": { + "node_abi": 127, + "v8": "12.4" + }, + "22.22.3": { + "node_abi": 127, + "v8": "12.4" + }, + "22.23.0": { + "node_abi": 127, + "v8": "12.4" + }, + "22.23.1": { + "node_abi": 127, + "v8": "12.4" + }, + "22.23.2": { + "node_abi": 127, + "v8": "12.4" + }, "23.0.0": { "node_abi": 131, "v8": "12.9" @@ -3299,8 +3395,240 @@ "node_abi": 131, "v8": "12.9" }, + "23.11.1": { + "node_abi": 131, + "v8": "12.9" + }, "24.0.0": { "node_abi": 137, "v8": "13.6" + }, + "24.0.1": { + "node_abi": 137, + "v8": "13.6" + }, + "24.0.2": { + "node_abi": 137, + "v8": "13.6" + }, + "24.1.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.2.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.3.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.4.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.4.1": { + "node_abi": 137, + "v8": "13.6" + }, + "24.5.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.6.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.7.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.8.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.9.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.10.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.11.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.11.1": { + "node_abi": 137, + "v8": "13.6" + }, + "24.12.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.13.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.13.1": { + "node_abi": 137, + "v8": "13.6" + }, + "24.14.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.14.1": { + "node_abi": 137, + "v8": "13.6" + }, + "24.15.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.16.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.17.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.18.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.18.1": { + "node_abi": 137, + "v8": "13.6" + }, + "24.19.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.20.0": { + "node_abi": 137, + "v8": "13.6" + }, + "24.21.0": { + "node_abi": 137, + "v8": "13.6" + }, + "25.0.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.1.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.2.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.2.1": { + "node_abi": 141, + "v8": "14.1" + }, + "25.3.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.4.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.5.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.6.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.6.1": { + "node_abi": 141, + "v8": "14.1" + }, + "25.7.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.8.0": { + "node_abi": 141, + "v8": "14.1" + }, + "25.8.1": { + "node_abi": 141, + "v8": "14.1" + }, + "25.8.2": { + "node_abi": 141, + "v8": "14.1" + }, + "25.9.0": { + "node_abi": 141, + "v8": "14.1" + }, + "26.0.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.1.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.2.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.3.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.3.1": { + "node_abi": 147, + "v8": "14.6" + }, + "26.4.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.5.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.5.1": { + "node_abi": 147, + "v8": "14.6" + }, + "26.6.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.7.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.8.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.8.1": { + "node_abi": 147, + "v8": "14.6" + }, + "26.8.2": { + "node_abi": 147, + "v8": "14.6" + }, + "26.9.0": { + "node_abi": 147, + "v8": "14.6" + }, + "26.10.0": { + "node_abi": 147, + "v8": "14.6" } -} +} \ No newline at end of file diff --git a/lib/util/compile.js b/lib/util/compile.js index c60455aa..fa3c0bbf 100644 --- a/lib/util/compile.js +++ b/lib/util/compile.js @@ -60,6 +60,8 @@ function which_node_gyp() { } } +module.exports.which_node_gyp = which_node_gyp; + module.exports.run_gyp = function(args, opts, callback) { let shell_cmd = ''; const cmd_args = []; diff --git a/package.json b/package.json index 73f3cdf6..d1981828 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "lint": "eslint bin/node-pre-gyp lib/*js lib/util/*js test/*js scripts/*js", "fix": "npm run lint -- --fix", "update-crosswalk": "node scripts/abi_crosswalk.js", + "upgrade-node-gyp": "node scripts/upgrade-node-gyp.js", "test": "tape test/*test.js", "test:s3": "tape test/s3.test.js", "bucket": "node scripts/set-bucket.js" diff --git a/scripts/upgrade-node-gyp.js b/scripts/upgrade-node-gyp.js new file mode 100644 index 00000000..2754f324 --- /dev/null +++ b/scripts/upgrade-node-gyp.js @@ -0,0 +1,42 @@ +'use strict'; + +// script upgrades node-gyp when the bundled copy is too old to detect the installed Visual Studio. node-gyp +// recognises Visual Studio 2026 (major version 18) only from v12 onwards, so older copies fail to configure on runner +// images shipping it. + +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const compile = require('../lib/util/compile.js'); + +const MINIMUM_MAJOR = 12; +const INSTALL_VERSION = '13.0.2'; + +const node_gyp_bin = compile.which_node_gyp(); +if (!node_gyp_bin) { + console.log('could not locate node-gyp; leaving it alone'); + process.exit(0); +} + +const { version } = require(path.join(node_gyp_bin, '../../package.json')); +console.log(`found node-gyp@${version} at ${node_gyp_bin}`); + +if (parseInt(version, 10) >= MINIMUM_MAJOR) { + console.log(`node-gyp@${version} detects Visual Studio 2026; nothing to do`); + process.exit(0); +} + +console.log(`node-gyp@${version} predates v${MINIMUM_MAJOR}; installing node-gyp@${INSTALL_VERSION}`); +cp.execFileSync('npm', ['install', '--no-save', `node-gyp@${INSTALL_VERSION}`], { stdio: 'inherit', shell: true }); + +const upgraded = path.join(__dirname, '../node_modules/node-gyp/bin/node-gyp.js'); +if (!fs.existsSync(upgraded)) { + throw new Error(`expected the upgraded node-gyp at ${upgraded}, but it is not there`); +} +console.log(`using node-gyp at ${upgraded}`); + +// export for later GitHub workflow steps; npm resets npm_config_node_gyp to its own bundled copy, +// so the tests read this name and pass it down to the commands they run +if (process.env.GITHUB_ENV) { + fs.appendFileSync(process.env.GITHUB_ENV, `NPG_TEST_NODE_GYP=${upgraded}\n`); +} diff --git a/test/build.test.js b/test/build.test.js index 3daf9b18..851e9275 100644 --- a/test/build.test.js +++ b/test/build.test.js @@ -13,6 +13,8 @@ const tar = require('tar'); const localVer = [versioning.get_runtime_abi('node'), process.platform, process.arch].join('-'); const SOEXT = { 'darwin': 'dylib', 'linux': 'so', 'win32': 'dll' }[process.platform]; +// oldest node-gyp that detects the Visual Studio on current Windows runners (2026, major version 18) +const MIN_WINDOWS_NODE_GYP_MAJOR = 12; // The list of different sample apps that we use to test const apps = [ @@ -68,6 +70,20 @@ test.Test.prototype.stringContains = function(actual, contents, message) { }); }; +/** + * Whether npm bundles a node-gyp too old to detect the Visual Studio installed on Windows. npm runs + * install hooks against its own copy, so a newer node-gyp elsewhere does not help. + * + * @returns {boolean} True on Windows when npm's node-gyp predates MIN_WINDOWS_NODE_GYP_MAJOR. + */ +function windows_npm_node_gyp_too_old() { + if (process.platform !== 'win32') return false; + // on Windows npm sits beside node.exe rather than under lib/ + const pkg = path.join(path.dirname(process.execPath), 'node_modules/npm/node_modules/node-gyp/package.json'); + if (!existsSync(pkg)) return false; + return parseInt(require(pkg).version, 10) < MIN_WINDOWS_NODE_GYP_MAJOR; +} + // Because the below tests only ensure that flags can be correctly passed to node-gyp is it not // likely they will behave differently for different apps. So we save time by avoiding running these for each app. const appOne = apps[0]; @@ -82,8 +98,10 @@ test(appOne.name + ' passes --nodedir down to node-gyp via node-pre-gyp ' + appO }); }); -// NOTE: currently fails with npm v3.x on windows (hence downgrade in appveyor.yml) -test(appOne.name + ' passes --nodedir down to node-gyp via npm' + appOne.args, (t) => { +test({ + name: appOne.name + ' passes --nodedir down to node-gyp via npm' + appOne.args, + skip: windows_npm_node_gyp_too_old() && 'npm bundles a node-gyp that cannot detect the installed Visual Studio' +}, (t) => { run('npm', 'install', '--build-from-source --nodedir=invalid-value', appOne, {}, (err, stdout, stderr) => { t.ok(err, 'Expected command to fail'); t.stringContains(stderr, 'common.gypi not found'); diff --git a/test/run.util.js b/test/run.util.js index 55e50b13..2ca63cfe 100644 --- a/test/run.util.js +++ b/test/run.util.js @@ -53,16 +53,19 @@ function run(prog, command, args, app, opts, cb) { } } + // npm resets npm_config_node_gyp to the copy it bundles, so CI passes any upgraded node-gyp + // under a name npm leaves alone and we hand it to the child as the config it reads + if (process.env.NPG_TEST_NODE_GYP) { + opts.env = Object.assign({}, opts.env || process.env, { + npm_config_node_gyp: process.env.NPG_TEST_NODE_GYP + }); + } + // unless explicitly provided, lets execute the command inside the app specific directory if (!opts.cwd) { opts.cwd = path.join(__dirname, app.name); } - // Test building with msvs 2022 - if (process.platform === 'win32') { - final_cmd += ' --msvs_version=2022 '; - } - // finish appending all arguments final_cmd += ' ' + app.args; final_cmd += ' ' + args; From 2d15a4c3dbe230dd19f1695a019339e3bfd1c8c1 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Thu, 24 Sep 2026 16:43:06 +0100 Subject: [PATCH 2/3] codeql issue fix --- test/run.util.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/run.util.js b/test/run.util.js index 2ca63cfe..985562c1 100644 --- a/test/run.util.js +++ b/test/run.util.js @@ -79,13 +79,15 @@ function run(prog, command, args, app, opts, cb) { } if (opts.npg_debug) { - if (opts.npg_debug === 'env') { - console.log('executing:', final_cmd, opts); - } else { - const someOpts = Object.assign({}, opts); - delete someOpts.env; - console.log('executing:', final_cmd, someOpts); + const someOpts = { cwd: opts.cwd }; + if (opts.npg_debug === 'env' && opts.env) { + someOpts.env = { + PATH: opts.env.PATH, + NODE_PATH: opts.env.NODE_PATH, + npm_config_node_gyp: opts.env.npm_config_node_gyp + }; } + console.log('executing:', final_cmd, someOpts); delete opts.npg_debug; } From 4d5ff017c532af7a7b27a0c56eeea14dd9260947 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 24 Sep 2026 18:59:29 +0200 Subject: [PATCH 3/3] Apply suggestion from @cclauss --- lib/util/abi_crosswalk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/abi_crosswalk.json b/lib/util/abi_crosswalk.json index 9ce86bd6..e2cde220 100644 --- a/lib/util/abi_crosswalk.json +++ b/lib/util/abi_crosswalk.json @@ -3631,4 +3631,4 @@ "node_abi": 147, "v8": "14.6" } -} \ No newline at end of file +}