├── .gitattributes ├── .gitignore ├── .npmignore ├── __tests__ ├── fixtures │ └── test.tar.gz └── index.js ├── jest.config.js ├── babel.config.json ├── .editorconfig ├── eslint.config.js ├── .github └── workflows │ ├── deploy.yml │ └── verify.yml ├── license ├── index.js ├── package.json └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | coverage 4 | *.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | .github 3 | coverage 4 | tmp 5 | *.tgz -------------------------------------------------------------------------------- /__tests__/fixtures/test.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/localnerve/bin-build/public-package/__tests__/fixtures/test.tar.gz -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | collectCoverage: true, 3 | coverageDirectory: 'coverage', 4 | verbose: true, 5 | testEnvironment: 'node' 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", { 5 | "targets": { 6 | "node": "18" 7 | } 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | import jest from 'eslint-plugin-jest'; 4 | 5 | export default [{ 6 | ignores: [ 7 | 'coverage/**', 8 | 'node_modules/**', 9 | '**/tmp/**' 10 | ] 11 | }, { 12 | files: [ 13 | 'index.js' 14 | ], 15 | ...js.configs.recommended, 16 | languageOptions: { 17 | globals: { 18 | ...globals.node 19 | } 20 | } 21 | }, { 22 | files: [ 23 | '__tests__/**' 24 | ], 25 | ...jest.configs['flat/recommended'] 26 | }]; 27 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: [ public-package ] 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-24.04 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1 14 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # 6.1.0 15 | with: 16 | node-version: '24.x' 17 | registry-url: 'https://registry.npmjs.org' 18 | - name: Update npm 19 | run: npm install -g npm@latest # ensure npm 11.5.1 or later 20 | - run: npm ci 21 | - name: Verify 22 | run: npm run lint && npm test 23 | - name: Publish 24 | if: ${{ success() }} 25 | run: npm publish --access public -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master, public-package ] 7 | 8 | jobs: 9 | verify: 10 | 11 | runs-on: ubuntu-24.04 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x, 20.x, 22.x, 24.x] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 17 | 18 | steps: 19 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # 6.1.0 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - name: Run Lint and Test 26 | run: npm run lint && npm test 27 | - name: Coverage Upload 28 | if: ${{ success() }} 29 | uses: coverallsapp/github-action@master 30 | with: 31 | github-token: ${{ secrets.GITHUB_TOKEN }} 32 | path-to-lcov: ./coverage/lcov.info -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Kevin Martensson (github.com/kevva) 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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import decompress from '@xhmikosr/decompress'; 2 | import download from '@xhmikosr/downloader'; 3 | import {execaCommand} from 'execa'; 4 | import pMapSeries from 'p-map-series'; 5 | import tempfile from 'tempfile'; 6 | 7 | function exec (cmd, cwd) { 8 | return pMapSeries(cmd, x => execaCommand(x, { 9 | cwd, 10 | shell: true 11 | })); 12 | } 13 | 14 | export function directory (dir, cmd) { 15 | if (typeof dir !== 'string') { 16 | return Promise.reject(new TypeError(`Expected a \`string\`, got \`${typeof dir}\``)); 17 | } 18 | 19 | return exec(cmd, dir); 20 | } 21 | 22 | export function file (file, cmd, options) { 23 | const optionsWithDefaults = { 24 | strip: 1, 25 | ...options 26 | }; 27 | 28 | if (typeof file !== 'string') { 29 | return Promise.reject(new TypeError(`Expected a \`string\`, got \`${typeof file}\``)); 30 | } 31 | 32 | const temporary = tempfile(); 33 | 34 | return decompress(file, temporary, optionsWithDefaults).then(() => exec(cmd, temporary)); 35 | } 36 | 37 | export function url (url, cmd, options) { 38 | const optionsWithDefaults = { 39 | extract: true, 40 | decompress: { 41 | strip: options?.strip ?? 1 42 | }, 43 | ...options 44 | }; 45 | 46 | if (typeof url !== 'string') { 47 | return Promise.reject(new TypeError(`Expected a \`string\`, got \`${typeof url}\``)); 48 | } 49 | 50 | const temporary = tempfile(); 51 | 52 | return download(url, temporary, optionsWithDefaults).then(() => exec(cmd, temporary)); 53 | } 54 | 55 | const all = { 56 | directory, 57 | file, 58 | url 59 | }; 60 | 61 | export default all; 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@localnerve/bin-build", 3 | "version": "7.8.3", 4 | "description": "Easily build binaries", 5 | "license": "MIT", 6 | "type": "module", 7 | "exports": { 8 | "default": "./index.js" 9 | }, 10 | "author": { 11 | "name": "Kevin Mårtensson", 12 | "email": "kevinmartensson@gmail.com", 13 | "url": "https://github.com/kevva" 14 | }, 15 | "maintainers": [{ 16 | "name": "Alex Grant", 17 | "email": "alex@localnerve.com", 18 | "url": "https://localnerve.com" 19 | }], 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/localnerve/bin-build.git" 23 | }, 24 | "scripts": { 25 | "lint": "eslint .", 26 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testTimeout=28000", 27 | "test:debug": "node --inspect-brk --experimental-vm-modules node_modules/jest/bin/jest.js --testTimeout=300000" 28 | }, 29 | "files": [ 30 | "index.js" 31 | ], 32 | "keywords": [ 33 | "binary", 34 | "build", 35 | "make" 36 | ], 37 | "dependencies": { 38 | "@xhmikosr/decompress": "^10.2.0", 39 | "@xhmikosr/downloader": "^15.2.0", 40 | "execa": "^9.6.1", 41 | "p-map-series": "^3.0.0", 42 | "tempfile": "^5.0.0" 43 | }, 44 | "devDependencies": { 45 | "@babel/preset-env": "^7.28.5", 46 | "@eslint/js": "^9.39.1", 47 | "eslint": "^9.39.1", 48 | "eslint-plugin-jest": "^29.2.1", 49 | "globals": "^16.5.0", 50 | "jest": "^30.2.0", 51 | "nock": "^14.0.10" 52 | }, 53 | "engines": { 54 | "node": ">=18" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # bin-build 2 | [![npm version](https://badge.fury.io/js/@localnerve%2Fbin-build.svg)](https://badge.fury.io/js/@localnerve%2Fbin-build) 3 | ![Verify](https://github.com/localnerve/bin-build/workflows/Verify/badge.svg) 4 | [![Coverage Status](https://coveralls.io/repos/github/localnerve/bin-build/badge.svg?branch=public-package)](https://coveralls.io/github/localnerve/bin-build?branch=public-package) 5 | 6 | > Easily build binaries 7 | 8 | **This is an updated fork of kevva/bin-build that is maintained** 9 | 10 | > Version 4.0.0+ on public-package branch is the forward maintained version under @localnerve/bin-build 11 | 12 | ## Install 13 | 14 | ``` 15 | $ npm install --save bin-build 16 | ``` 17 | 18 | 19 | ## Usage 20 | 21 | ```js 22 | const binBuild = require('bin-build'); 23 | 24 | binBuild.url('http://www.lcdf.org/gifsicle/gifsicle-1.80.tar.gz', [ 25 | './configure --disable-gifview --disable-gifdiff', 26 | 'make install' 27 | ]).then(() => { 28 | console.log('gifsicle built successfully'); 29 | }); 30 | 31 | binBuild.file('gifsicle-1.80.tar.gz', [ 32 | './configure --disable-gifview --disable-gifdiff', 33 | 'make install' 34 | ]).then(() => { 35 | console.log('gifsicle built successfully'); 36 | }); 37 | ``` 38 | 39 | 40 | ## API 41 | 42 | ### binBuild.directory(directory, commands) 43 | 44 | #### directory 45 | 46 | Type: `string` 47 | 48 | Path to a directory containing the source code. 49 | 50 | #### commands 51 | 52 | Type: `Array` 53 | 54 | Commands to run when building. 55 | 56 | ### binBuild.file(file, commands, [options]) 57 | 58 | #### file 59 | 60 | Type: `string` 61 | 62 | Path to a archive file containing the source code. 63 | 64 | #### commands 65 | 66 | Type: `Array` 67 | 68 | Commands to run when building. 69 | 70 | #### options 71 | 72 | Type: `Object` 73 | 74 | ##### strip 75 | 76 | Type: `number`
77 | Default: `1` 78 | 79 | Strip a number of leading paths from file names on extraction. 80 | 81 | ### binBuild.url(url, commands, [options]) 82 | 83 | #### url 84 | 85 | Type: `string` 86 | 87 | URL to a archive file containing the source code. 88 | 89 | #### commands 90 | 91 | Type: `Array` 92 | 93 | Commands to run when building. 94 | 95 | #### options 96 | 97 | Type: `Object` 98 | 99 | ##### strip 100 | 101 | Type: `number`
102 | Default: `1` 103 | 104 | Strip a number of leading paths from file names on extraction. 105 | 106 | 107 | ## License 108 | 109 | MIT © [Kevin Mårtensson](https://github.com/kevva) 110 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import path from 'node:path'; 3 | import url from 'node:url'; 4 | import fs from 'node:fs/promises'; 5 | import nock from 'nock'; 6 | import tempfile from 'tempfile'; 7 | import decompress from '@xhmikosr/decompress'; 8 | import m from '../index.js'; 9 | 10 | const thisDirname = url.fileURLToPath(new URL('.', import.meta.url)); 11 | 12 | async function pathExists (path) { 13 | let result; 14 | 15 | try { 16 | await fs.access(path); 17 | result = true; 18 | } catch { 19 | result = false; 20 | } 21 | 22 | return result; 23 | } 24 | 25 | function fixturePath (name) { 26 | return path.join(thisDirname, 'fixtures', name); 27 | } 28 | 29 | function autoCommand (temp) { 30 | return [ 31 | 'autoreconf -ivf', 32 | `./configure --disable-gifview --disable-gifdiff --prefix="${temp}" --bindir="${temp}"`, 33 | 'make install', 34 | ]; 35 | } 36 | 37 | describe('bin-build', () => { 38 | let temporaryFile; 39 | 40 | beforeEach(() => { 41 | temporaryFile = tempfile(); 42 | }); 43 | 44 | afterEach(async () => { 45 | let recursive = false; 46 | const exists = await pathExists(temporaryFile); 47 | if (exists) { 48 | const stats = await fs.stat(temporaryFile); 49 | recursive = stats.isDirectory(); 50 | } 51 | await fs.rm(temporaryFile, { 52 | force: true, 53 | recursive 54 | }); 55 | }); 56 | 57 | test('download and build source', async () => { 58 | nock('http://foo.com') 59 | .get('/gifsicle.tar.gz') 60 | .replyWithFile(200, fixturePath('test.tar.gz')); 61 | 62 | await m.url('http://foo.com/gifsicle.tar.gz', autoCommand(temporaryFile)); 63 | 64 | expect(await pathExists(path.join(temporaryFile, 'gifsicle'))).toBeTruthy(); 65 | }); 66 | 67 | test('build source from existing archive', async () => { 68 | await m.file(fixturePath('test.tar.gz'), autoCommand(temporaryFile)); 69 | 70 | expect(await pathExists(path.join(temporaryFile, 'gifsicle'))).toBeTruthy(); 71 | }); 72 | 73 | test('build source from directory', async () => { 74 | await fs.mkdir(temporaryFile, { 75 | recursive: true 76 | }); 77 | 78 | await decompress(fixturePath('test.tar.gz'), temporaryFile, {strip: 1}); 79 | await m.directory(temporaryFile, autoCommand(temporaryFile)); 80 | 81 | expect(await pathExists(path.join(temporaryFile, 'gifsicle'))).toBeTruthy(); 82 | }); 83 | 84 | test('directory accepts a string', () => { 85 | return expect(m.directory([])).rejects.toThrow('Expected a `string`, got `object`'); 86 | }); 87 | 88 | test('file accepts a string', () => { 89 | return expect(m.file([])).rejects.toThrow('Expected a `string`, got `object`'); 90 | }); 91 | 92 | test('url accepts a string', () => { 93 | return expect(m.url([])).rejects.toThrow('Expected a `string`, got `object`'); 94 | }); 95 | }); 96 | 97 | --------------------------------------------------------------------------------