Skip to content

Commit

Permalink
Better error messages for missing binaries
Browse files Browse the repository at this point in the history
This is another iteration on improving the infamous
>The `libsass` binding was not found

Messages will now provide more useful information which will
- give users a chance to resolve the problem themselves
- give us more debug information from the error message alone

Error messages produce now will look like:

>Node Sass does not yet support your current environment: OS X 64-bit with Node.js 4.x
>For more information on which environments are supported please see:
>http://....

>Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 4.x
>Found bindings for the following environments:
>  - OS X 64-bit with io.js 3.x
>  - OS X 64-bit with Node.js 5.x
>This usually happens because your environment has changed since running `npm install`.
>Run `npm rebuild node-sass` to build the binding for your current environment.

>Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 4.x
>This usually happens because your environment has changed since running `npm install`.
>Run `npm rebuild node-sass` to build the binding for your current environment.
  • Loading branch information
xzyfer committed Mar 26, 2016
1 parent 41db8b9 commit cf87e0b
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 26 deletions.
48 changes: 48 additions & 0 deletions lib/errors.js
@@ -0,0 +1,48 @@
/*!
* node-sass: lib/errors.js
*/

var sass = require('./extensions');

function humanEnvironment() {
return sass.getHumanEnvironment(sass.getBinaryName());
}

function foundBinaries() {
return [
'Found bindings for the following environments:',
foundBinariesList(),
].join('\n');
}

function foundBinariesList() {
return sass.getInstalledBinaries().map(function(env) {
return ' - ' + sass.getHumanEnvironment(env);
}).join('\n');
}

function missingBinaryFooter() {
return [
'This usually happens because your environment has changed since running `npm install`.',
'Run `npm rebuild node-sass` to build the binding for your current environment.',
].join('\n');
}

module.exports.unsupportedEnvironment = function() {
return [
'Node Sass does not yet support your current environment: ' + humanEnvironment(),
'For more information on which environments are supported please see:',
'TODO URL'
].join('\n');
};

module.exports.missingBinary = function() {
return [
'Missing binding ' + sass.getBinaryPath(),
'Node Sass could not find a binding for your current environment: ' + humanEnvironment(),
'',
foundBinaries(),
'',
missingBinaryFooter(),
].join('\n');
};
83 changes: 72 additions & 11 deletions lib/extensions.js
Expand Up @@ -5,7 +5,68 @@
var eol = require('os').EOL,
fs = require('fs'),
pkg = require('../package.json'),
path = require('path');
path = require('path'),
defaultBinaryPath = path.join(__dirname, '..', 'vendor');

function getHumanPlatform(arg) {
switch (arg || process.platform) {
case 'darwin': return 'OS X';
case 'freebsd': return 'FreeBSD';
case 'linux': return 'Linux';
case 'win32': return 'Windows';
default: return false;
}
}

function getHumanArchitecture(arg) {
switch (arg || process.arch) {
case 'ia32': return '32-bit';
case 'x86': return '32-bit';
case 'x64': return '64-bit';
default: return false;
}
}

function getHumanNodeVersion(arg) {
switch (parseInt(arg || process.versions.modules, 10)) {
case 11: return 'Node 0.10.x';
case 14: return 'Node 0.12.x';
case 42: return 'io.js 1.x';
case 43: return 'io.js 1.1.x';
case 44: return 'io.js 2.x';
case 45: return 'io.js 3.x';
case 46: return 'Node.js 4.x';
case 47: return 'Node.js 5.x';
default: return false;
}
}

function getHumanEnvironment(env) {
var parts = env.replace(/_binding\.node$/, '').split('-');

if (parts.length !== 3) {
return 'Unknown environment';
}

return [
getHumanPlatform(parts[0]),
getHumanArchitecture(parts[1]),
'with',
getHumanNodeVersion(parts[2]),
].join(' ');
}

function getInstalledBinaries() {
return fs.readdirSync(defaultBinaryPath);
}

function isSupportedEnvironment() {
return (
false !== getHumanPlatform() &&
false !== getHumanArchitecture() &&
false !== getHumanNodeVersion()
);
}

/**
* Get the value of a CLI argument
Expand Down Expand Up @@ -110,7 +171,7 @@ function getBinaryUrl() {
* @api public
*/

function getBinaryPath(throwIfNotExists) {
function getBinaryPath() {
var binaryPath;

if (getArgument('--sass-binary-path')) {
Expand All @@ -122,20 +183,16 @@ function getBinaryPath(throwIfNotExists) {
} else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) {
binaryPath = pkg.nodeSassConfig.binaryPath;
} else {
binaryPath = path.join(__dirname, '..', 'vendor', getBinaryName().replace(/_/, '/'));
}

if (!fs.existsSync(binaryPath) && throwIfNotExists) {
throw new Error([
['The `libsass` binding was not found in', binaryPath].join(' '),
['This usually happens because your node version has changed.'],
['Run `npm rebuild node-sass` to build the binding for your current node version.'],
].join('\n'));
binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_/, '/'));
}

return binaryPath;
}

function hasBinary(binaryPath) {
return fs.existsSync(binaryPath);
}

/**
* Get Sass version information
*
Expand All @@ -149,7 +206,11 @@ function getVersionInfo(binding) {
].join(eol);
}

module.exports.hasBinary = hasBinary;
module.exports.getBinaryUrl = getBinaryUrl;
module.exports.getBinaryName = getBinaryName;
module.exports.getBinaryPath = getBinaryPath;
module.exports.getVersionInfo = getVersionInfo;
module.exports.getHumanEnvironment = getHumanEnvironment;
module.exports.getInstalledBinaries = getInstalledBinaries;
module.exports.isSupportedEnvironment = isSupportedEnvironment;
12 changes: 11 additions & 1 deletion lib/index.js
Expand Up @@ -4,13 +4,23 @@

var path = require('path'),
util = require('util'),
errors = require('./errors'),
sass = require('./extensions');

if (!sass.hasBinary(sass.getBinaryPath())) {
if (!sass.isSupportedEnvironment()) {
throw new Error(errors.unsupportedEnvironment());
} else {
throw new Error(errors.missingBinary());
}
}


/**
* Require binding
*/

var binding = require(sass.getBinaryPath(true));
var binding = require(sass.getBinaryPath());

/**
* Get input file
Expand Down
100 changes: 99 additions & 1 deletion test/api.js
Expand Up @@ -2,7 +2,10 @@ var assert = require('assert'),
fs = require('fs'),
path = require('path'),
read = fs.readFileSync,
sass = process.env.NODESASS_COV ? require('../lib-cov') : require('../lib'),
sassPath = process.env.NODESASS_COV
? require.resolve('../lib-cov')
: require.resolve('../lib'),
sass = require(sassPath),
fixture = path.join.bind(null, __dirname, 'fixtures'),
resolveFixture = path.resolve.bind(null, __dirname, 'fixtures');

Expand Down Expand Up @@ -1777,4 +1780,99 @@ describe('api', function() {
done();
});
});

describe('binding', function() {
beforeEach(function() {
delete require.cache[sassPath];
});

afterEach(function() {
delete require.cache[sassPath];
});

describe('missing error', function() {
beforeEach(function() {
process.env.SASS_BINARY_NAME = [
(process.platform === 'win32' ? 'Linux' : 'Windows'), '-',
process.arch, '-',
process.versions.modules
].join('');
});

afterEach(function() {
delete process.env.SASS_BINARY_NAME;
});

it('should be useful', function() {
assert.throws(
function() { require(sassPath); },
new RegExp('Missing binding.*?\\' + path.sep + 'vendor\\' + path.sep)
);
});

it('should list currently installed bindings', function() {
assert.throws(
function() { require(sassPath); },
function(err) {
var etx = require('../lib/extensions');

delete process.env.SASS_BINARY_NAME;

if ((err instanceof Error)) {
return err.message.indexOf(
etx.getHumanEnvironment(etx.getBinaryName())
) !== -1;
}
}
);
});
});

describe('on unsupported environment', function() {
it('should error for unsupported architecture', function() {
var prevValue = process.arch;

Object.defineProperty(process, 'arch', {
get: function () { return 'foo'; }
});

assert.throws(
function() { require(sassPath); },
'Node Sass does not yet support your current environment'
);

process.arch = prevValue;
});

it('should error for unsupported platform', function() {
var prevValue = process.platform;

Object.defineProperty(process, 'platform', {
get: function () { return 'foo'; }
});

assert.throws(
function() { require(sassPath); },
'Node Sass does not yet support your current environment'
);

process.platform = prevValue;
});

it('should error for unsupported runtime', function() {
var prevValue = process.versions.modules;

Object.defineProperty(process.versions, 'modules', {
get: function () { return 'foo'; }
});

assert.throws(
function() { require(sassPath); },
'Node Sass does not yet support your current environment'
);

process.versions.modules = prevValue;
});
});
});
});
53 changes: 53 additions & 0 deletions test/errors.js
@@ -0,0 +1,53 @@
var assert = require('assert'),
path = require('path'),
errors = require('../lib/errors');

describe('binary errors', function() {

function getCurrentPlatform() {
if (process.platform === 'win32') {
return 'Windows';
} else if (process.platform === 'darwin') {
return 'OS X';
}
return '';
}

function getCurrentArchitecture() {
if (process.arch === 'x86' || process.arch === 'ia32') {
return '32-bit';
} else if (process.arch === 'x64') {
return '64-bit';
}
return '';
}

function getCurrentEnvironment() {
return getCurrentPlatform() + ' ' + getCurrentArchitecture();
}

describe('for an unsupported environment', function() {
it('identifies the current environment', function() {
var message = errors.unsupportedEnvironment();
assert.ok(message.indexOf(getCurrentEnvironment()) !== -1);
});

it('links to supported environment documentation', function() {
var message = errors.unsupportedEnvironment();
assert.ok(message.indexOf('TODO URL') !== -1);
});
});

describe('for an missing binary', function() {
it('identifies the current environment', function() {
var message = errors.missingBinary();
assert.ok(message.indexOf(getCurrentEnvironment()) !== -1);
});

it('documents the expected binary location', function() {
var message = errors.missingBinary();
assert.ok(message.indexOf(path.sep + 'vendor' + path.sep) !== -1);
});
});

});
25 changes: 12 additions & 13 deletions test/runtime.js
@@ -1,5 +1,4 @@
var assert = require('assert'),
fs = require('fs'),
extensionsPath = process.env.NODESASS_COV
? require.resolve('../lib-cov/extensions')
: require.resolve('../lib/extensions');
Expand Down Expand Up @@ -137,17 +136,17 @@ describe('runtime parameters', function() {
});
});

describe('library detection', function() {
it('should throw error when libsass binary is missing.', function() {
var sass = require(extensionsPath),
originalBin = sass.getBinaryPath(),
renamedBin = [originalBin, '_moved'].join('');
// describe('library detection', function() {
// it('should throw error when libsass binary is missing.', function() {
// var sass = require(extensionsPath),
// originalBin = sass.getBinaryPath(),
// renamedBin = [originalBin, '_moved'].join('');

assert.throws(function() {
fs.renameSync(originalBin, renamedBin);
sass.getBinaryPath(true);
}, /The `libsass` binding was not found/);
// assert.throws(function() {
// fs.renameSync(originalBin, renamedBin);
// sass.getBinaryPath(true);
// }, /The `libsass` binding was not found/);

fs.renameSync(renamedBin, originalBin);
});
});
// fs.renameSync(renamedBin, originalBin);
// });
// });

0 comments on commit cf87e0b

Please sign in to comment.