├── .github └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── SECURITY.md ├── bin.js ├── build-test.js ├── index.js ├── node-gyp-build.js ├── optional.js ├── package.json └── test.js /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | os: [ubuntu-latest, macos-latest, windows-latest] 8 | node: [18, 20, 22, latest] # https://nodejs.org/en/about/previous-releases 9 | runs-on: ${{ matrix.os }} 10 | name: ${{ matrix.os }} / Node ${{ matrix.node }} 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Use node ${{ matrix.node }} 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - name: Install 19 | run: npm install 20 | - name: Test 21 | run: npm test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test.js 2 | .github 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-gyp-build 2 | 3 | > Build tool and bindings loader for [`node-gyp`][node-gyp] that supports prebuilds. 4 | 5 | ``` 6 | npm install node-gyp-build 7 | ``` 8 | 9 | [![Test](https://github.com/prebuild/node-gyp-build/actions/workflows/test.yml/badge.svg)](https://github.com/prebuild/node-gyp-build/actions/workflows/test.yml) 10 | 11 | Use together with [`prebuildify`][prebuildify] to easily support prebuilds for your native modules. 12 | 13 | ## Usage 14 | 15 | > **Note.** Prebuild names have changed in [`prebuildify@3`][prebuildify] and `node-gyp-build@4`. Please see the documentation below. 16 | 17 | `node-gyp-build` works similar to [`node-gyp build`][node-gyp] except that it will check if a build or prebuild is present before rebuilding your project. 18 | 19 | It's main intended use is as an npm install script and bindings loader for native modules that bundle prebuilds using [`prebuildify`][prebuildify]. 20 | 21 | First add `node-gyp-build` as an install script to your native project 22 | 23 | ``` js 24 | { 25 | ... 26 | "scripts": { 27 | "install": "node-gyp-build" 28 | } 29 | } 30 | ``` 31 | 32 | Then in your `index.js`, instead of using the [`bindings`](https://www.npmjs.com/package/bindings) module use `node-gyp-build` to load your binding. 33 | 34 | ``` js 35 | var binding = require('node-gyp-build')(__dirname) 36 | ``` 37 | 38 | If you do these two things and bundle prebuilds with [`prebuildify`][prebuildify] your native module will work for most platforms 39 | without having to compile on install time AND will work in both node and electron without the need to recompile between usage. 40 | 41 | Users can override `node-gyp-build` and force compiling by doing `npm install --build-from-source`. 42 | 43 | Prebuilds will be attempted loaded from `MODULE_PATH/prebuilds/...` and then next `EXEC_PATH/prebuilds/...` (the latter allowing use with `zeit/pkg`) 44 | 45 | ## Supported prebuild names 46 | 47 | If so desired you can bundle more specific flavors, for example `musl` builds to support Alpine, or targeting a numbered ARM architecture version. 48 | 49 | These prebuilds can be bundled in addition to generic prebuilds; `node-gyp-build` will try to find the most specific flavor first. Prebuild filenames are composed of _tags_. The runtime tag takes precedence, as does an `abi` tag over `napi`. For more details on tags, please see [`prebuildify`][prebuildify]. 50 | 51 | Values for the `libc` and `armv` tags are auto-detected but can be overridden through the `LIBC` and `ARM_VERSION` environment variables, respectively. 52 | 53 | ## License 54 | 55 | MIT 56 | 57 | [prebuildify]: https://github.com/prebuild/prebuildify 58 | [node-gyp]: https://www.npmjs.com/package/node-gyp 59 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var proc = require('child_process') 4 | var os = require('os') 5 | var path = require('path') 6 | 7 | if (!buildFromSource()) { 8 | proc.exec('node-gyp-build-test', function (err, stdout, stderr) { 9 | if (err) { 10 | if (verbose()) console.error(stderr) 11 | preinstall() 12 | } 13 | }) 14 | } else { 15 | preinstall() 16 | } 17 | 18 | function build () { 19 | var win32 = os.platform() === 'win32' 20 | var shell = win32 21 | var args = [win32 ? 'node-gyp.cmd' : 'node-gyp', 'rebuild'] 22 | 23 | try { 24 | var pkg = require('node-gyp/package.json') 25 | args = [ 26 | process.execPath, 27 | path.join(require.resolve('node-gyp/package.json'), '..', typeof pkg.bin === 'string' ? pkg.bin : pkg.bin['node-gyp']), 28 | 'rebuild' 29 | ] 30 | shell = false 31 | } catch (_) {} 32 | 33 | proc.spawn(args[0], args.slice(1), { stdio: 'inherit', shell, windowsHide: true }).on('exit', function (code) { 34 | if (code || !process.argv[3]) process.exit(code) 35 | exec(process.argv[3]).on('exit', function (code) { 36 | process.exit(code) 37 | }) 38 | }) 39 | } 40 | 41 | function preinstall () { 42 | if (!process.argv[2]) return build() 43 | exec(process.argv[2]).on('exit', function (code) { 44 | if (code) process.exit(code) 45 | build() 46 | }) 47 | } 48 | 49 | function exec (cmd) { 50 | if (process.platform !== 'win32') { 51 | var shell = os.platform() === 'android' ? 'sh' : true 52 | return proc.spawn(cmd, [], { 53 | shell, 54 | stdio: 'inherit' 55 | }) 56 | } 57 | 58 | return proc.spawn(cmd, [], { 59 | windowsVerbatimArguments: true, 60 | stdio: 'inherit', 61 | shell: true, 62 | windowsHide: true 63 | }) 64 | } 65 | 66 | function buildFromSource () { 67 | return hasFlag('--build-from-source') || process.env.npm_config_build_from_source === 'true' 68 | } 69 | 70 | function verbose () { 71 | return hasFlag('--verbose') || process.env.npm_config_loglevel === 'verbose' 72 | } 73 | 74 | // TODO (next major): remove in favor of env.npm_config_* which works since npm 75 | // 0.1.8 while npm_config_argv will stop working in npm 7. See npm/rfcs#90 76 | function hasFlag (flag) { 77 | if (!process.env.npm_config_argv) return false 78 | 79 | try { 80 | return JSON.parse(process.env.npm_config_argv).original.indexOf(flag) !== -1 81 | } catch (_) { 82 | return false 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /build-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.env.NODE_ENV = 'test' 4 | 5 | var path = require('path') 6 | var test = null 7 | 8 | try { 9 | var pkg = require(path.join(process.cwd(), 'package.json')) 10 | if (pkg.name && process.env[pkg.name.toUpperCase().replace(/-/g, '_')]) { 11 | process.exit(0) 12 | } 13 | test = pkg.prebuild.test 14 | } catch (err) { 15 | // do nothing 16 | } 17 | 18 | if (test) require(path.join(process.cwd(), test)) 19 | else require('./')() 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line 2 | if (typeof runtimeRequire.addon === 'function') { // if the platform supports native resolving prefer that 3 | module.exports = runtimeRequire.addon.bind(runtimeRequire) 4 | } else { // else use the runtime version here 5 | module.exports = require('./node-gyp-build.js') 6 | } 7 | -------------------------------------------------------------------------------- /node-gyp-build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var os = require('os') 4 | 5 | // Workaround to fix webpack's build warnings: 'the request of a dependency is an expression' 6 | var runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line 7 | 8 | var vars = (process.config && process.config.variables) || {} 9 | var prebuildsOnly = !!process.env.PREBUILDS_ONLY 10 | var abi = process.versions.modules // TODO: support old node where this is undef 11 | var runtime = isElectron() ? 'electron' : (isNwjs() ? 'node-webkit' : 'node') 12 | 13 | var arch = process.env.npm_config_arch || os.arch() 14 | var platform = process.env.npm_config_platform || os.platform() 15 | var libc = process.env.LIBC || (isAlpine(platform) ? 'musl' : 'glibc') 16 | var armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || '' 17 | var uv = (process.versions.uv || '').split('.')[0] 18 | 19 | module.exports = load 20 | 21 | function load (dir) { 22 | return runtimeRequire(load.resolve(dir)) 23 | } 24 | 25 | load.resolve = load.path = function (dir) { 26 | dir = path.resolve(dir || '.') 27 | 28 | try { 29 | var name = runtimeRequire(path.join(dir, 'package.json')).name.toUpperCase().replace(/-/g, '_') 30 | if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD'] 31 | } catch (err) {} 32 | 33 | if (!prebuildsOnly) { 34 | var release = getFirst(path.join(dir, 'build/Release'), matchBuild) 35 | if (release) return release 36 | 37 | var debug = getFirst(path.join(dir, 'build/Debug'), matchBuild) 38 | if (debug) return debug 39 | } 40 | 41 | var prebuild = resolve(dir) 42 | if (prebuild) return prebuild 43 | 44 | var nearby = resolve(path.dirname(process.execPath)) 45 | if (nearby) return nearby 46 | 47 | var target = [ 48 | 'platform=' + platform, 49 | 'arch=' + arch, 50 | 'runtime=' + runtime, 51 | 'abi=' + abi, 52 | 'uv=' + uv, 53 | armv ? 'armv=' + armv : '', 54 | 'libc=' + libc, 55 | 'node=' + process.versions.node, 56 | process.versions.electron ? 'electron=' + process.versions.electron : '', 57 | typeof __webpack_require__ === 'function' ? 'webpack=true' : '' // eslint-disable-line 58 | ].filter(Boolean).join(' ') 59 | 60 | throw new Error('No native build was found for ' + target + '\n loaded from: ' + dir + '\n') 61 | 62 | function resolve (dir) { 63 | // Find matching "prebuilds/-" directory 64 | var tuples = readdirSync(path.join(dir, 'prebuilds')).map(parseTuple) 65 | var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0] 66 | if (!tuple) return 67 | 68 | // Find most specific flavor first 69 | var prebuilds = path.join(dir, 'prebuilds', tuple.name) 70 | var parsed = readdirSync(prebuilds).map(parseTags) 71 | var candidates = parsed.filter(matchTags(runtime, abi)) 72 | var winner = candidates.sort(compareTags(runtime))[0] 73 | if (winner) return path.join(prebuilds, winner.file) 74 | } 75 | } 76 | 77 | function readdirSync (dir) { 78 | try { 79 | return fs.readdirSync(dir) 80 | } catch (err) { 81 | return [] 82 | } 83 | } 84 | 85 | function getFirst (dir, filter) { 86 | var files = readdirSync(dir).filter(filter) 87 | return files[0] && path.join(dir, files[0]) 88 | } 89 | 90 | function matchBuild (name) { 91 | return /\.node$/.test(name) 92 | } 93 | 94 | function parseTuple (name) { 95 | // Example: darwin-x64+arm64 96 | var arr = name.split('-') 97 | if (arr.length !== 2) return 98 | 99 | var platform = arr[0] 100 | var architectures = arr[1].split('+') 101 | 102 | if (!platform) return 103 | if (!architectures.length) return 104 | if (!architectures.every(Boolean)) return 105 | 106 | return { name, platform, architectures } 107 | } 108 | 109 | function matchTuple (platform, arch) { 110 | return function (tuple) { 111 | if (tuple == null) return false 112 | if (tuple.platform !== platform) return false 113 | return tuple.architectures.includes(arch) 114 | } 115 | } 116 | 117 | function compareTuples (a, b) { 118 | // Prefer single-arch prebuilds over multi-arch 119 | return a.architectures.length - b.architectures.length 120 | } 121 | 122 | function parseTags (file) { 123 | var arr = file.split('.') 124 | var extension = arr.pop() 125 | var tags = { file: file, specificity: 0 } 126 | 127 | if (extension !== 'node') return 128 | 129 | for (var i = 0; i < arr.length; i++) { 130 | var tag = arr[i] 131 | 132 | if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') { 133 | tags.runtime = tag 134 | } else if (tag === 'napi') { 135 | tags.napi = true 136 | } else if (tag.slice(0, 3) === 'abi') { 137 | tags.abi = tag.slice(3) 138 | } else if (tag.slice(0, 2) === 'uv') { 139 | tags.uv = tag.slice(2) 140 | } else if (tag.slice(0, 4) === 'armv') { 141 | tags.armv = tag.slice(4) 142 | } else if (tag === 'glibc' || tag === 'musl') { 143 | tags.libc = tag 144 | } else { 145 | continue 146 | } 147 | 148 | tags.specificity++ 149 | } 150 | 151 | return tags 152 | } 153 | 154 | function matchTags (runtime, abi) { 155 | return function (tags) { 156 | if (tags == null) return false 157 | if (tags.runtime && tags.runtime !== runtime && !runtimeAgnostic(tags)) return false 158 | if (tags.abi && tags.abi !== abi && !tags.napi) return false 159 | if (tags.uv && tags.uv !== uv) return false 160 | if (tags.armv && tags.armv !== armv) return false 161 | if (tags.libc && tags.libc !== libc) return false 162 | 163 | return true 164 | } 165 | } 166 | 167 | function runtimeAgnostic (tags) { 168 | return tags.runtime === 'node' && tags.napi 169 | } 170 | 171 | function compareTags (runtime) { 172 | // Precedence: non-agnostic runtime, abi over napi, then by specificity. 173 | return function (a, b) { 174 | if (a.runtime !== b.runtime) { 175 | return a.runtime === runtime ? -1 : 1 176 | } else if (a.abi !== b.abi) { 177 | return a.abi ? -1 : 1 178 | } else if (a.specificity !== b.specificity) { 179 | return a.specificity > b.specificity ? -1 : 1 180 | } else { 181 | return 0 182 | } 183 | } 184 | } 185 | 186 | function isNwjs () { 187 | return !!(process.versions && process.versions.nw) 188 | } 189 | 190 | function isElectron () { 191 | if (process.versions && process.versions.electron) return true 192 | if (process.env.ELECTRON_RUN_AS_NODE) return true 193 | return typeof window !== 'undefined' && window.process && window.process.type === 'renderer' 194 | } 195 | 196 | function isAlpine (platform) { 197 | return platform === 'linux' && fs.existsSync('/etc/alpine-release') 198 | } 199 | 200 | // Exposed for unit tests 201 | // TODO: move to lib 202 | load.parseTags = parseTags 203 | load.matchTags = matchTags 204 | load.compareTags = compareTags 205 | load.parseTuple = parseTuple 206 | load.matchTuple = matchTuple 207 | load.compareTuples = compareTuples 208 | -------------------------------------------------------------------------------- /optional.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | I am only useful as an install script to make node-gyp not compile for purely optional native deps 5 | */ 6 | 7 | process.exit(0) 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-gyp-build", 3 | "version": "4.8.4", 4 | "description": "Build tool and bindings loader for node-gyp that supports prebuilds", 5 | "main": "index.js", 6 | "imports": { 7 | "fs": { 8 | "bare": "builtin:fs", 9 | "default": "fs" 10 | }, 11 | "path": { 12 | "bare": "builtin:path", 13 | "default": "path" 14 | }, 15 | "os": { 16 | "bare": "builtin:os", 17 | "default": "os" 18 | } 19 | }, 20 | "devDependencies": { 21 | "array-shuffle": "^1.0.1", 22 | "standard": "^14.0.0", 23 | "tape": "^5.0.0" 24 | }, 25 | "scripts": { 26 | "test": "standard && node test" 27 | }, 28 | "bin": { 29 | "node-gyp-build": "./bin.js", 30 | "node-gyp-build-optional": "./optional.js", 31 | "node-gyp-build-test": "./build-test.js" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/prebuild/node-gyp-build.git" 36 | }, 37 | "author": "Mathias Buus (@mafintosh)", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/prebuild/node-gyp-build/issues" 41 | }, 42 | "homepage": "https://github.com/prebuild/node-gyp-build" 43 | } 44 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var shuffle = require('array-shuffle') 5 | var parseTags = require('.').parseTags 6 | var matchTags = require('.').matchTags 7 | var compareTags = require('.').compareTags 8 | var parseTuple = require('.').parseTuple 9 | var matchTuple = require('.').matchTuple 10 | var compareTuples = require('.').compareTuples 11 | 12 | test('parse tags', function (t) { 13 | t.is(parseTags('ignored'), undefined) 14 | t.is(parseTags('node.ignored'), undefined) 15 | 16 | t.is(parseTags('node.node').runtime, 'node') 17 | t.is(parseTags('electron.node').runtime, 'electron') 18 | t.is(parseTags('node-webkit.node').runtime, 'node-webkit') 19 | 20 | t.same(parseTags('node.napi.node'), { 21 | file: 'node.napi.node', 22 | runtime: 'node', 23 | napi: true, 24 | specificity: 2 25 | }) 26 | 27 | t.same(parseTags('node.abi64.uv1.armv7.musl.node'), { 28 | file: 'node.abi64.uv1.armv7.musl.node', 29 | runtime: 'node', 30 | abi: '64', 31 | uv: '1', 32 | armv: '7', 33 | libc: 'musl', 34 | specificity: 5 35 | }) 36 | 37 | t.end() 38 | }) 39 | 40 | test('sort tags', function (t) { 41 | check('electron', [ 42 | 'electron.abi64.uv1.node', 43 | 'electron.napi.uv1.armv7.musl.node', 44 | 'electron.napi.uv1.musl.node', 45 | 'node.abi64.uv1.node', 46 | 'node.abi64.node', 47 | 'node.napi.uv1.armv7.musl.node', 48 | 'node.napi.uv1.node' 49 | ], 'sort order ok') 50 | 51 | check('node', [ 52 | 'node.abi64.uv1.node', 53 | 'node.abi64.node', 54 | 'node.napi.uv1.armv7.musl.node', 55 | 'node.napi.uv1.node', 56 | 'electron.abi64.uv1.node', 57 | 'electron.napi.uv1.armv7.musl.node', 58 | 'electron.napi.uv1.musl.node' 59 | ], 'same files, different runtime') 60 | 61 | function check (runtime, sorted, message) { 62 | t.same( 63 | shuffle(sorted) 64 | .map(parseTags) 65 | .sort(compareTags(runtime)) 66 | .map(getFile), 67 | sorted, 68 | message 69 | ) 70 | } 71 | 72 | t.end() 73 | }) 74 | 75 | test('match and sort tags', function (t) { 76 | check('electron', '64', [ 77 | 'node.abi64.node', 78 | 'electron.napi.node' 79 | ], [ 80 | 'electron.napi.node' 81 | ], 'dont use a node abi for electron') 82 | 83 | check('electron', '64', [ 84 | 'node.abi64.node', 85 | 'node.napi.node' 86 | ], [ 87 | 'node.napi.node' 88 | ], 'but do use a node napi for electron') 89 | 90 | check('electron', '64', [ 91 | 'node.abi64.node', 92 | 'node.napi.node', 93 | 'electron.abi64.node', 94 | 'electron.abi99.node', 95 | 'electron.napi.node' 96 | ], [ 97 | 'electron.abi64.node', 98 | 'electron.napi.node', 99 | 'node.napi.node' 100 | ], 'except if an electron abi is available') 101 | 102 | check('node', '64', [ 103 | 'electron.napi.node', 104 | 'electron.abi64.node', 105 | 'node.abi64.node' 106 | ], [ 107 | 'node.abi64.node' 108 | ], 'never use electron for node') 109 | 110 | function check (runtime, abi, files, expected, message) { 111 | t.same( 112 | files 113 | .map(parseTags) 114 | .filter(matchTags(runtime, abi)) 115 | .sort(compareTags(runtime)) 116 | .map(getFile), 117 | expected, 118 | message 119 | ) 120 | } 121 | 122 | t.end() 123 | }) 124 | 125 | test('parse tuples', function (t) { 126 | t.same(parseTuple('linux-arm64'), { 127 | name: 'linux-arm64', 128 | platform: 'linux', 129 | architectures: ['arm64'] 130 | }) 131 | 132 | t.same(parseTuple('darwin-x64+arm64'), { 133 | name: 'darwin-x64+arm64', 134 | platform: 'darwin', 135 | architectures: ['x64', 'arm64'] 136 | }) 137 | 138 | // Should skip invalid tuples 139 | t.is(parseTuple(''), undefined) 140 | t.is(parseTuple('linux-'), undefined) 141 | t.is(parseTuple('-arm64'), undefined) 142 | t.is(parseTuple('linux-arm64+'), undefined) 143 | t.is(parseTuple('linux-arm64++x64'), undefined) 144 | t.is(parseTuple('linux-+arm64'), undefined) 145 | 146 | t.end() 147 | }) 148 | 149 | test('sort tuples', function (t) { 150 | var tuples = ['darwin-arm64+x64+ia32', 'darwin-x64', 'darwin-x64+arm64'] 151 | var sorted = tuples.map(parseTuple).sort(compareTuples).map(getTupleName) 152 | 153 | t.same(sorted, ['darwin-x64', 'darwin-x64+arm64', 'darwin-arm64+x64+ia32']) 154 | t.end() 155 | }) 156 | 157 | test('match tuples', function (t) { 158 | var tuples = ['linux-arm64', 'darwin-x64+arm64', 'darwin-x64'].map(parseTuple) 159 | 160 | t.is(tuples.filter(matchTuple('darwin', 'x64')).sort(compareTuples)[0].name, 'darwin-x64') 161 | t.is(tuples.filter(matchTuple('darwin', 'arm64')).sort(compareTuples)[0].name, 'darwin-x64+arm64') 162 | t.is(tuples.filter(matchTuple('linux', 'arm64')).sort(compareTuples)[0].name, 'linux-arm64') 163 | t.is(tuples.some(matchTuple('linux', 'other')), false) 164 | t.is(tuples.some(matchTuple('other', 'arm64')), false) 165 | t.end() 166 | }) 167 | 168 | function getTupleName (tuple) { 169 | return tuple.name 170 | } 171 | 172 | function getFile (tags) { 173 | return tags.file 174 | } 175 | --------------------------------------------------------------------------------