From 176b9c50fcf06e46fbf9ae27ad58bf8f39c380f3 Mon Sep 17 00:00:00 2001 From: Isaac Murchie Date: Mon, 26 Aug 2019 10:07:57 -0400 Subject: [PATCH] switch to linking package --- .travis.yml | 23 ++++++++++-------- index.js | 5 ++-- lib/image-util.js | 38 ++--------------------------- lib/mjpeg.js | 9 ++++--- lib/node.js | 54 +++++++++++++++++++++++++++++++++++++++++ test/.eslintrc | 5 ++++ test/mjpeg-e2e-specs.js | 4 +-- test/node-e2e-specs.js | 21 ++++++++++++++++ 8 files changed, 105 insertions(+), 54 deletions(-) create mode 100644 lib/node.js create mode 100644 test/.eslintrc create mode 100644 test/node-e2e-specs.js diff --git a/.travis.yml b/.travis.yml index dccee4ee..30571c29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -os: linux dist: xenial language: node_js os: @@ -22,19 +21,23 @@ env: - _FORCE_LOGS=1 install: # on node 12 opencv4nodejs and mjpeg-consumer cannot be installed + # also handle possible travis caching - if [[ `node --version` != v12* ]]; then - printf "while [ true ]; do\nsleep 30\necho 'Building OpenCV'\ndone" > ping.sh; - bash ping.sh & - echo $! > ping.pid; - npm install -g opencv4nodejs > /dev/null 2>&1; - kill `cat ping.pid`; + if [[ $(npm ls --depth 1 -g opencv4nodejs) =~ "── opencv4nodejs@" ]]; then + printf "while [ true ]; do\nsleep 30\necho 'Building OpenCV'\ndone" > ping.sh; + bash ping.sh & + echo $! > ping.pid; + npm install -g opencv4nodejs > /dev/null 2>&1; + kill `cat ping.pid`; + fi - npm install --no-save mjpeg-consumer; + if [[ $(npm ls --depth 1 -g mjpeg-consumer) =~ "── mjpeg-consumer@" ]]; then + npm install -g mjpeg-consumer; + fi fi - npm install script: - - npm ls -g opencv4nodejs -j - - ls -la "C:\ProgramData\nvs\node\10.15.2\x64\node_modules\opencv4nodejs" - - _FORCE_LOGS=1 npm run e2e-test + - npm run test + - npm run e2e-test after_success: - npm run coverage diff --git a/index.js b/index.js index 9f84948b..155810ee 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ import * as process from './lib/process'; import * as zip from './lib/zip'; import * as imageUtil from './lib/image-util'; import * as mjpeg from './lib/mjpeg'; +import * as node from './lib/node'; const { fs } = fsIndex; @@ -18,9 +19,9 @@ const { mkdirp } = mkdirpIndex; export { tempDir, system, util, fs, cancellableDelay, plist, mkdirp, logger, process, - zip, imageUtil, net, mjpeg + zip, imageUtil, net, mjpeg, node, }; export default { tempDir, system, util, fs, cancellableDelay, plist, mkdirp, logger, process, - zip, imageUtil, net, mjpeg + zip, imageUtil, net, mjpeg, node, }; diff --git a/lib/image-util.js b/lib/image-util.js index 6f2f9894..f1108f88 100644 --- a/lib/image-util.js +++ b/lib/image-util.js @@ -4,10 +4,8 @@ import { Buffer } from 'buffer'; import { PNG } from 'pngjs'; import B from 'bluebird'; import { hasValue } from './util'; -import { exec } from 'teen_process'; -import path from 'path'; import log from './logger'; -import { isWindows } from './system'; +import { requirePackage } from './node'; const { MIME_JPEG, MIME_PNG, MIME_BMP } = Jimp; @@ -92,38 +90,6 @@ async function getJimpImage (data) { }); } -/** - * Utility function to extend node functionality, allowing us to require - * modules that are installed globally - * - * @param {string} packageName - the name of the package to be required - * @returns {object} - the package object - */ -async function requirePackage (packageName) { - // see if we can get it locally - try { - log.debug(`Loading local package '${packageName}'`); - return require(packageName); - } catch (err) { - log.debug(`Unable to load local package '${packageName}': ${err.message}`); - } - - // find the npm global root - const cmd = isWindows() ? 'npm.cmd' : 'npm'; - const {stdout} = await exec(cmd, ['root', '-g'], {timeout: 20000}); - const globalNPMRoot = stdout.trim(); - - // get the global package root - const packageDir = path.join(globalNPMRoot, packageName); - log.debug(`Loading global package '${packageName}' from '${packageDir}'`); - - try { - return require(packageDir); - } catch (err) { - throw new Error(`Unable to find global package '${packageName}' at '${packageDir}': ${err.message}`); - } -} - /** * @throws {Error} If opencv4nodejs module is not installed or cannot be loaded */ @@ -136,7 +102,7 @@ async function initOpenCV () { try { cv = await requirePackage('opencv4nodejs'); } catch (err) { - log.debug(`Unable to load 'opencv4nodejs': ${err.message}`); + log.warn(`Unable to load 'opencv4nodejs': ${err.message}`); } if (!cv) { diff --git a/lib/mjpeg.js b/lib/mjpeg.js index e131f70d..0e0f6218 100644 --- a/lib/mjpeg.js +++ b/lib/mjpeg.js @@ -6,6 +6,7 @@ import B from 'bluebird'; import { getJimpImage, MIME_PNG } from './image-util'; import mJpegServer from 'mjpeg-server'; import { Writable } from 'stream'; +import { requirePackage } from './node'; // lazy load this, as it might not be available @@ -14,10 +15,10 @@ let MJpegConsumer = null; /** * @throws {Error} If `mjpeg-consumer` module is not installed or cannot be loaded */ -function initMJpegConsumer () { +async function initMJpegConsumer () { if (!MJpegConsumer) { try { - MJpegConsumer = require('mjpeg-consumer'); + MJpegConsumer = await requirePackage('mjpeg-consumer'); } catch (ign) {} } if (!MJpegConsumer) { @@ -44,8 +45,6 @@ class MJpegStream extends Writable { constructor (mJpegUrl, errorHandler = _.noop, options = {}) { super(options); - initMJpegConsumer(); - this.errorHandler = errorHandler; this.url = mJpegUrl; this.clear(); @@ -107,6 +106,8 @@ class MJpegStream extends Writable { // ensure we're not started already this.stop(); + await initMJpegConsumer(); + this.consumer = new MJpegConsumer(); // use the deferred pattern so we can wait for the start of the stream diff --git a/lib/node.js b/lib/node.js new file mode 100644 index 00000000..d7e83047 --- /dev/null +++ b/lib/node.js @@ -0,0 +1,54 @@ +import { isWindows } from './system'; +import log from './logger'; +import { exec } from 'teen_process'; + + +/** + * Internal utility to link global package to local context + * + * @returns {string} - name of the package to link + * @throws {Error} If the command fails + */ +async function linkGlobalPackage (packageName) { + try { + log.debug(`Linking package '${packageName}'`); + const cmd = isWindows() ? 'npm.cmd' : 'npm'; + await exec(cmd, ['link', packageName], {timeout: 20000}); + } catch (err) { + const msg = `Unable to load package '${packageName}', linking failed: ${err.message}`; + log.debug(msg); + if (err.stderr) { + // log the stderr if there, but do not add to thrown error as it is + // _very_ verbose + log.debug(err.stderr); + } + throw new Error(msg); + } +} + +/** + * Utility function to extend node functionality, allowing us to require + * modules that are installed globally. If the package cannot be required, + * this will attempt to link the package and then re-require it + * + * @param {string} packageName - the name of the package to be required + * @returns {object} - the package object + * @throws {Error} If the package is not found locally or globally + */ +async function requirePackage (packageName) { + try { + log.debug(`Loading local package '${packageName}'`); + return require(packageName); + } catch (err) { + log.debug(`Failed to load package '${packageName}': ${err.message}`); + await linkGlobalPackage(packageName); + } + try { + log.debug(`Retrying load of local package '${packageName}'`); + return require(packageName); + } catch (err) { + log.errorAndThrow(`Unable to load package '${packageName}': ${err.message}`); + } +} + +export { requirePackage }; diff --git a/test/.eslintrc b/test/.eslintrc new file mode 100644 index 00000000..6c8f75a1 --- /dev/null +++ b/test/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "func-names": 0 + } +} diff --git a/test/mjpeg-e2e-specs.js b/test/mjpeg-e2e-specs.js index 9fbdbcf7..11c97389 100644 --- a/test/mjpeg-e2e-specs.js +++ b/test/mjpeg-e2e-specs.js @@ -15,13 +15,13 @@ const MJPEG_SERVER_URL = `http://localhost:${MJPEG_SERVER_PORT}`; describe('MJpeg Stream (e2e)', function () { let mJpegServer, stream; - before(function () { + before(async function () { // TODO: remove when buffertools can handle v12 if (process.version.startsWith('v12')) { return this.skip(); } - mJpegServer = initMJpegServer(MJPEG_SERVER_PORT); + mJpegServer = await initMJpegServer(MJPEG_SERVER_PORT); }); after(function () { diff --git a/test/node-e2e-specs.js b/test/node-e2e-specs.js new file mode 100644 index 00000000..8fd11110 --- /dev/null +++ b/test/node-e2e-specs.js @@ -0,0 +1,21 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { node } from '..'; + + +chai.should(); +chai.use(chaiAsPromised); + +describe('node utilities', function () { + describe('requirePackage', function () { + it('should be able to require a local package', async function () { + await node.requirePackage('chai').should.not.be.rejected; + }); + it('should be able to require a global package', async function () { + await node.requirePackage('npm').should.not.be.rejected; + }); + it('should fail to find uninstalled package', async function () { + await node.requirePackage('appium-foo-driver').should.eventually.be.rejectedWith(/Unable to load package/); + }); + }); +});