From 2f9bb530f186b6010293045c7cc2d836730cb7e1 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 14 May 2026 15:25:31 -0600 Subject: [PATCH 1/2] fix(host-targets): classify CYGWIN and MINGW as windows, not linux Cygwin (CYGWIN_NT-*) and Git Bash (MINGW64_NT-*) run on Windows and need Windows binaries. The previous linux classification caused webi.sh to serve Linux ELF binaries (e.g. gh_linux_amd64.tar.gz) which cannot run on Windows hosts. Also add 'gnu' and 'libc' libc fallbacks to the Windows WATERFALL so that Cygwin/MINGW hosts (which report 'gnu' or 'libc' as their libc) can reach msvc-tagged and static Windows packages. Adds host-targets-test.js with explicit os/arch/vendor assertions for all known Cygwin and MINGW user-agent patterns, including the Windows 11 build from issue #1083. Bumps to v1.0.4. --- host-targets-test.js | 128 +++++++++++++++++++++++++++++++++++++++++++ host-targets.js | 8 ++- package.json | 4 +- uas.json | 1 + 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 host-targets-test.js diff --git a/host-targets-test.js b/host-targets-test.js new file mode 100644 index 0000000..24c8786 --- /dev/null +++ b/host-targets-test.js @@ -0,0 +1,128 @@ +'use strict'; + +let HostTargets = require('./host-targets.js'); + +function parseUa(ua) { + let terms = []; + let parts = ua.split(/\s+/g); + for (let part of parts) { + let _terms = part.split(/\//g); + terms = terms.concat(_terms); + } + let target = {}; + HostTargets.termsToTarget(target, terms); + return target; +} + +function assertField(ua, target, field, expected) { + let actual = target[field]; + if (actual !== expected) { + throw new Error( + `[${field}] expected '${expected}' but got '${actual}' for UA:\n ${ua}`, + ); + } +} + +function testCase(ua, expected) { + let target = parseUa(ua); + for (let [field, value] of Object.entries(expected)) { + assertField(ua, target, field, value); + } +} + +async function main() { + // Cygwin on 32-bit Windows (reports 'gnu') + testCase('webi/curl+wget i686/unknown CYGWIN_NT-10.0-WOW/3.3.5(0.341/5/3) gnu', { + os: 'windows', + arch: 'x86', + vendor: 'pc', + }); + + // Cygwin on 32-bit Windows, legacy curl-only UA + testCase('curl CYGWIN_NT-10.0-WOW/3.3.5(0.341/5/3) i686/unknown gnu', { + os: 'windows', + arch: 'x86', + vendor: 'pc', + }); + + // Cygwin on 64-bit Windows (reports 'libc'), "Cygwin" prefix variant + testCase('webi/curl x86_64/unknown Cygwin/CYGWIN_NT-10.0/3.3.4(0.341/5/3) libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // Cygwin on 64-bit Windows 10 (build 19045) + testCase('webi/curl+wget x86_64/unknown Cygwin/CYGWIN_NT-10.0-19045/3.4.10-1.x86_64 libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // Cygwin on 64-bit Windows 11 (build 26200) — from issue #1083 + testCase('webi/curl+wget x86_64/unknown Cygwin/CYGWIN_NT-10.0-26200/3.4.10-1.x86_64 libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // Cygwin UA with GNU/Linux prefix (webi.sh quirk on some Cygwin installs) + testCase('webi/curl+wget i686/unknown GNU/Linux/CYGWIN_NT-10.0-WOW/3.3.5(0.341/5/3) libc', { + os: 'windows', + arch: 'x86', + vendor: 'pc', + }); + + // MINGW (Git Bash) on 64-bit Windows, webi UA + testCase('webi/curl x86_64/unknown MINGW64_NT-10.0-19045/3.3.6-341.x86_64 libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // MINGW (Git Bash), legacy curl-only UA + testCase('curl MINGW64_NT-10.0-19045/3.3.6-341.x86_64 x86_64/unknown libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // MINGW with Linux prefix in UA path (older Git Bash format) + testCase('curl Linux/MINGW64_NT-10.0-19045/3.3.6-341.x86_64 x86_64/unknown libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // MINGW with Msys prefix (Msys2 variant) + testCase('webi/curl x86_64/unknown Msys/MINGW64_NT-10.0-19045/3.3.6-341.x86_64 libc', { + os: 'windows', + arch: 'x86_64', + vendor: 'pc', + }); + + // Sanity: real Linux should NOT be classified as Windows + testCase('webi/curl+wget x86_64/unknown Linux/6.2.0-1012-aws gnu', { + os: 'linux', + arch: 'x86_64', + vendor: 'unknown', + }); + + // Sanity: Darwin should NOT be classified as Windows + testCase('webi/curl arm64/unknown Darwin/22.6.0', { + os: 'darwin', + arch: 'aarch64', + vendor: 'apple', + }); +} + +main() + .then(function () { + console.error(''); + console.error('PASS'); + process.exit(0); + }) + .catch(function (e) { + console.error(e.stack); + process.exit(1); + }); diff --git a/host-targets.js b/host-targets.js index 90d0267..e60a3b7 100644 --- a/host-targets.js +++ b/host-targets.js @@ -26,6 +26,10 @@ HostTargets.WATERFALL = { windows: Object.assign( { aarch64: ['aarch64', 'x86_64'], + // Cygwin/MINGW report 'gnu' or 'libc'; prefer msvc packages first, + // then static ('none'), then the reported libc + gnu: ['msvc', 'none', 'gnu'], + libc: ['msvc', 'none', 'libc'], }, X86_64, ), @@ -78,8 +82,8 @@ HostTargets.TERMS = { // OS Android: T.ANDROID, Linux: T.LINUX, - MINGW: T.LINUX, - CYGWIN: T.LINUX, + MINGW: T.WINDOWS, + CYGWIN: T.WINDOWS, Darwin: T.DARWIN, Windows: T.WINDOWS, win32: { os: 'windows', vendor: 'unknown' }, diff --git a/package.json b/package.json index 4f21266..22c4a16 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "build-classifier", - "version": "1.0.3", + "version": "1.0.4", "description": "Determine Host Target Triplet (Arch, Libc, OS, Vendor) from a filenames / download URL and uname / User-Agent strings.", "main": "index.js", "scripts": { "bump": "npm version -m \"chore(release): bump to v%s\"", "fmt": "npm run prettier", "lint": "npm run jshint", - "test": "node lexver-test.js", + "test": "node lexver-test.js && node host-targets-test.js", "----": "------------------------------------", "jshint": "npx -p jshint@2.x -- jshint -c ./.jshintrc --exclude 'node_modules/**/*' *.js", "prettier": "npx -p prettier@3.x -- prettier -w '**/*.{js,md,html}'" diff --git a/uas.json b/uas.json index 18cce1c..14309a0 100644 --- a/uas.json +++ b/uas.json @@ -2046,6 +2046,7 @@ "curl Linux/5.15.131-1-pve x86_64/unknown gnu": 2, "webi/curl+wget x86_64/unknown GNU/Linux/5.10.0-8-amd64 libc": 1, "webi/curl+wget x86_64/unknown Cygwin/CYGWIN_NT-10.0-19045/3.4.10-1.x86_64 libc": 2, + "webi/curl+wget x86_64/unknown Cygwin/CYGWIN_NT-10.0-26200/3.4.10-1.x86_64 libc": 1, "webi/curl+wget x86_64/unknown GNU/Linux/3.10.0-1160.83.1.el7.x86_64 libc": 1, "webi/curl+wget x86_64/unknown GNU/Linux/5.4.250-2.1.ve1.x86_64 libc": 1, "curl Linux/5.4.250-2.1.ve1.x86_64 x86_64/unknown gnu": 1, From f9cc9f3e197c2b8b68ce506293599037fc1d68e8 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 14 May 2026 15:50:43 -0600 Subject: [PATCH 2/2] fix(host-targets): remove unnecessary windows libc waterfall additions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ANYOS already provides ['none', 'gnu'] for gnu hosts and the extended ['none', 'gnu', 'musl', 'libc'] for libc hosts, which reaches static ('none') Windows packages. Explicit msvc preference was also wrong since msvc requires vcredist — 'none' is always preferred. --- host-targets.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/host-targets.js b/host-targets.js index e60a3b7..acb5529 100644 --- a/host-targets.js +++ b/host-targets.js @@ -26,10 +26,6 @@ HostTargets.WATERFALL = { windows: Object.assign( { aarch64: ['aarch64', 'x86_64'], - // Cygwin/MINGW report 'gnu' or 'libc'; prefer msvc packages first, - // then static ('none'), then the reported libc - gnu: ['msvc', 'none', 'gnu'], - libc: ['msvc', 'none', 'libc'], }, X86_64, ),