diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..79fb78d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,35 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+env:
+ FORCE_COLOR: 2
+
+jobs:
+ test:
+ name: Node ${{ matrix.node }} on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ node: [12, 14, 16, 18]
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v3
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node }}
+
+ - name: Install npm dependencies
+ run: npm install
+
+ - name: Run tests
+ run: npm run test-ci
diff --git a/.gitignore b/.gitignore
index 239ecff..9eb6759 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
yarn.lock
+/coverage
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 2ae9d62..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-language: node_js
-node_js:
- - '10'
- - '8'
- - '6'
diff --git a/index.js b/index.js
index 1aba001..7639e0d 100644
--- a/index.js
+++ b/index.js
@@ -1,17 +1,9 @@
-'use strict';
-const fs = require('fs');
-const path = require('path');
-const url = require('url');
-const pify = require('pify');
-const importLazy = require('import-lazy')(require);
-
-const binCheck = importLazy('bin-check');
-const binVersionCheck = importLazy('bin-version-check');
-const download = importLazy('download');
-const osFilterObj = importLazy('os-filter-obj');
-
-const statAsync = pify(fs.stat);
-const chmodAsync = pify(fs.chmod);
+import {promises as fs} from 'node:fs';
+import path from 'node:path';
+import binCheck from 'bin-check';
+import binVersionCheck from 'bin-version-check';
+import download from 'download';
+import osFilterObject from 'os-filter-obj';
/**
* Initialize a new `BinWrapper`
@@ -19,7 +11,7 @@ const chmodAsync = pify(fs.chmod);
* @param {Object} options
* @api public
*/
-module.exports = class BinWrapper {
+export default class BinWrapper {
constructor(options = {}) {
this.options = options;
@@ -47,7 +39,7 @@ module.exports = class BinWrapper {
this._src.push({
url: src,
os,
- arch
+ arch,
});
return this;
@@ -138,8 +130,6 @@ module.exports = class BinWrapper {
if (this.version()) {
return binVersionCheck(this.path(), this.version());
}
-
- return Promise.resolve();
});
}
@@ -149,12 +139,12 @@ module.exports = class BinWrapper {
* @api private
*/
findExisting() {
- return statAsync(this.path()).catch(error => {
+ return fs.stat(this.path()).catch(error => {
if (error && error.code === 'ENOENT') {
return this.download();
}
- return Promise.reject(error);
+ throw error;
});
}
@@ -164,45 +154,33 @@ module.exports = class BinWrapper {
* @api private
*/
download() {
- const files = osFilterObj(this.src() || []);
- const urls = [];
+ const files = osFilterObject(this.src() || []);
if (files.length === 0) {
return Promise.reject(new Error('No binary found matching your system. It\'s probably not supported.'));
}
- files.forEach(file => urls.push(file.url));
+ const urls = [];
+ for (const file of files) {
+ urls.push(file.url);
+ }
return Promise.all(urls.map(url => download(url, this.dest(), {
extract: true,
- strip: this.options.strip
+ strip: this.options.strip,
}))).then(result => {
- const resultingFiles = flatten(result.map((item, index) => {
+ const resultingFiles = result.flatMap((item, index) => {
if (Array.isArray(item)) {
return item.map(file => file.path);
}
- const parsedUrl = url.parse(files[index].url);
+ const parsedUrl = new URL(files[index].url);
const parsedPath = path.parse(parsedUrl.pathname);
return parsedPath.base;
- }));
+ });
- return Promise.all(resultingFiles.map(fileName => {
- return chmodAsync(path.join(this.dest(), fileName), 0o755);
- }));
+ return Promise.all(resultingFiles.map(fileName => fs.chmod(path.join(this.dest(), fileName), 0o755)));
});
}
-};
-
-function flatten(arr) {
- return arr.reduce((acc, elem) => {
- if (Array.isArray(elem)) {
- acc.push(...elem);
- } else {
- acc.push(elem);
- }
-
- return acc;
- }, []);
}
diff --git a/package.json b/package.json
index e49e305..4176148 100644
--- a/package.json
+++ b/package.json
@@ -10,10 +10,18 @@
"url": "https://github.com/kevva"
},
"engines": {
- "node": ">=6"
+ "node": "^12.20.0 || ^14.14.0 || >=16.0.0"
},
"scripts": {
- "test": "xo && ava"
+ "ava": "ava",
+ "xo": "xo",
+ "test": "npm run xo && npm run ava",
+ "test-ci": "npm run xo && c8 ava"
+ },
+ "main": "index.js",
+ "type": "module",
+ "exports": {
+ ".": "./index.js"
},
"files": [
"index.js"
@@ -24,21 +32,26 @@
"local",
"wrapper"
],
+ "c8": {
+ "reporter": [
+ "lcovonly",
+ "text"
+ ]
+ },
"dependencies": {
"bin-check": "^4.1.0",
- "bin-version-check": "^4.0.0",
- "download": "^7.1.0",
- "import-lazy": "^3.1.0",
- "os-filter-obj": "^2.0.0",
- "pify": "^4.0.1"
+ "bin-version-check": "^5.0.0",
+ "download": "^8.0.0",
+ "os-filter-obj": "^2.0.0"
},
"devDependencies": {
- "ava": "*",
+ "ava": "^4.3.0",
+ "c8": "^7.11.3",
"executable": "^4.1.1",
- "nock": "^10.0.2",
- "path-exists": "^3.0.0",
- "rimraf": "^2.6.2",
- "tempy": "^0.2.1",
- "xo": "*"
+ "nock": "^13.2.6",
+ "path-exists": "^5.0.0",
+ "rimraf": "^3.0.2",
+ "tempy": "^2.0.0",
+ "xo": "^0.49.0"
}
}
diff --git a/readme.md b/readme.md
index e57de2a..909cf6c 100644
--- a/readme.md
+++ b/readme.md
@@ -1,21 +1,22 @@
-# bin-wrapper [](https://travis-ci.org/kevva/bin-wrapper)
+# bin-wrapper [](https://github.com/kevva/bin-wrapper/actions/workflows/ci.yml)
> Binary wrapper that makes your programs seamlessly available as local dependencies
## Install
-```
-$ npm install bin-wrapper
+```sh
+npm install bin-wrapper
```
## Usage
```js
-const BinWrapper = require('bin-wrapper');
+import path from 'node:path';
+import BinWrapper from 'bin-wrapper';
-const base = 'https://github.com/imagemin/gifsicle-bin/raw/master/vendor';
+const base = 'https://github.com/imagemin/gifsicle-bin/raw/main/vendor';
const bin = new BinWrapper()
.src(`${base}/macos/gifsicle`, 'darwin')
.src(`${base}/linux/x64/gifsicle`, 'linux', 'x64')
@@ -50,15 +51,15 @@ Type: `Object`
##### skipCheck
-Type: `boolean`
-Default: `false`
+* Type: `boolean`
+* Default: `false`
Whether to skip the binary check or not.
##### strip
-Type: `number`
-Default: `1`
+* Type: `number`
+* Default: `1`
Strip a number of leading paths from file names on extraction.
@@ -110,7 +111,7 @@ Returns the full path to your binary.
Type: `string`
-Define a [semver range](https://github.com/isaacs/node-semver#ranges) to check
+Define a [semver range](https://github.com/npm/node-semver#ranges) to check
the binary against.
### .run([arguments])
@@ -120,8 +121,8 @@ using the URL provided in `.src()`.
#### arguments
-Type: `Array`
-Default: `['--version']`
+* Type: `Array`
+* Default: `['--version']`
Command to run the binary with. If it exits with code `0` it means that the
binary is working.
diff --git a/test.js b/test.js
index 5248cd7..042f208 100644
--- a/test.js
+++ b/test.js
@@ -1,21 +1,26 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
+import process from 'node:process';
+import {promisify} from 'node:util';
+import {fileURLToPath} from 'node:url';
+import executable from 'executable';
import nock from 'nock';
-import pathExists from 'path-exists';
-import pify from 'pify';
+import {pathExists} from 'path-exists';
import rimraf from 'rimraf';
-import test from 'ava';
import tempy from 'tempy';
-import executable from 'executable';
-import Fn from '.';
+import test from 'ava';
+import BinWrapper from './index.js';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const rimrafP = pify(rimraf);
+const rimrafP = promisify(rimraf);
const fixture = path.join.bind(path, __dirname, 'fixtures');
test.beforeEach(() => {
nock('http://foo.com')
.get('/gifsicle.tar.gz')
- .replyWithFile(200, fixture('gifsicle-' + process.platform + '.tar.gz'))
+ .replyWithFile(200, fixture(`gifsicle-${process.platform}.tar.gz`))
.get('/gifsicle-darwin.tar.gz')
.replyWithFile(200, fixture('gifsicle-darwin.tar.gz'))
.get('/gifsicle-win32.tar.gz')
@@ -25,36 +30,36 @@ test.beforeEach(() => {
});
test('expose a constructor', t => {
- t.is(typeof Fn, 'function');
+ t.is(typeof BinWrapper, 'function');
});
test('add a source', t => {
- const bin = new Fn().src('http://foo.com/bar.tar.gz');
+ const bin = new BinWrapper().src('http://foo.com/bar.tar.gz');
t.is(bin._src[0].url, 'http://foo.com/bar.tar.gz');
});
test('add a source to a specific os', t => {
- const bin = new Fn().src('http://foo.com', process.platform);
+ const bin = new BinWrapper().src('http://foo.com', process.platform);
t.is(bin._src[0].os, process.platform);
});
test('set destination directory', t => {
- const bin = new Fn().dest(path.join(__dirname, 'foo'));
+ const bin = new BinWrapper().dest(path.join(__dirname, 'foo'));
t.is(bin._dest, path.join(__dirname, 'foo'));
});
test('set which file to use as the binary', t => {
- const bin = new Fn().use('foo');
+ const bin = new BinWrapper().use('foo');
t.is(bin._use, 'foo');
});
test('set a version range to test against', t => {
- const bin = new Fn().version('1.0.0');
+ const bin = new BinWrapper().version('1.0.0');
t.is(bin._version, '1.0.0');
});
test('get the binary path', t => {
- const bin = new Fn()
+ const bin = new BinWrapper()
.dest('tmp')
.use('foo');
@@ -62,7 +67,7 @@ test('get the binary path', t => {
});
test('verify that a binary is working', async t => {
- const bin = new Fn()
+ const bin = new BinWrapper()
.src('http://foo.com/gifsicle.tar.gz')
.dest(tempy.directory())
.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
@@ -73,7 +78,7 @@ test('verify that a binary is working', async t => {
});
test('meet the desired version', async t => {
- const bin = new Fn()
+ const bin = new BinWrapper()
.src('http://foo.com/gifsicle.tar.gz')
.dest(tempy.directory())
.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle')
@@ -85,7 +90,7 @@ test('meet the desired version', async t => {
});
test('download files even if they are not used', async t => {
- const bin = new Fn({strip: 0, skipCheck: true})
+ const bin = new BinWrapper({strip: 0, skipCheck: true})
.src('http://foo.com/gifsicle-darwin.tar.gz')
.src('http://foo.com/gifsicle-win32.tar.gz')
.src('http://foo.com/test.js')
@@ -104,7 +109,7 @@ test('download files even if they are not used', async t => {
});
test('skip running binary check', async t => {
- const bin = new Fn({skipCheck: true})
+ const bin = new BinWrapper({skipCheck: true})
.src('http://foo.com/gifsicle.tar.gz')
.dest(tempy.directory())
.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
@@ -115,15 +120,15 @@ test('skip running binary check', async t => {
});
test('error if no binary is found and no source is provided', async t => {
- const bin = new Fn()
+ const bin = new BinWrapper()
.dest(tempy.directory())
.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
- await t.throws(bin.run(), 'No binary found matching your system. It\'s probably not supported.');
+ await t.throwsAsync(bin.run(), undefined, 'No binary found matching your system. It\'s probably not supported.');
});
test('downloaded files are set to be executable', async t => {
- const bin = new Fn({strip: 0, skipCheck: true})
+ const bin = new BinWrapper({strip: 0, skipCheck: true})
.src('http://foo.com/gifsicle-darwin.tar.gz')
.src('http://foo.com/gifsicle-win32.tar.gz')
.src('http://foo.com/test.js')
@@ -134,7 +139,7 @@ test('downloaded files are set to be executable', async t => {
const files = fs.readdirSync(bin.dest());
- files.forEach(fileName => {
+ for (const fileName of files) {
t.true(executable.sync(path.join(bin.dest(), fileName)));
- });
+ }
});