Commit cf87e0bc by xzyfer

Better error messages for missing binaries

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.
parent 41db8b99
/*!
* 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');
};
...@@ -5,7 +5,68 @@ ...@@ -5,7 +5,68 @@
var eol = require('os').EOL, var eol = require('os').EOL,
fs = require('fs'), fs = require('fs'),
pkg = require('../package.json'), 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 * Get the value of a CLI argument
...@@ -110,7 +171,7 @@ function getBinaryUrl() { ...@@ -110,7 +171,7 @@ function getBinaryUrl() {
* @api public * @api public
*/ */
function getBinaryPath(throwIfNotExists) { function getBinaryPath() {
var binaryPath; var binaryPath;
if (getArgument('--sass-binary-path')) { if (getArgument('--sass-binary-path')) {
...@@ -122,20 +183,16 @@ function getBinaryPath(throwIfNotExists) { ...@@ -122,20 +183,16 @@ function getBinaryPath(throwIfNotExists) {
} else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) { } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) {
binaryPath = pkg.nodeSassConfig.binaryPath; binaryPath = pkg.nodeSassConfig.binaryPath;
} else { } else {
binaryPath = path.join(__dirname, '..', 'vendor', getBinaryName().replace(/_/, '/')); binaryPath = path.join(defaultBinaryPath, 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'));
} }
return binaryPath; return binaryPath;
} }
function hasBinary(binaryPath) {
return fs.existsSync(binaryPath);
}
/** /**
* Get Sass version information * Get Sass version information
* *
...@@ -149,7 +206,11 @@ function getVersionInfo(binding) { ...@@ -149,7 +206,11 @@ function getVersionInfo(binding) {
].join(eol); ].join(eol);
} }
module.exports.hasBinary = hasBinary;
module.exports.getBinaryUrl = getBinaryUrl; module.exports.getBinaryUrl = getBinaryUrl;
module.exports.getBinaryName = getBinaryName; module.exports.getBinaryName = getBinaryName;
module.exports.getBinaryPath = getBinaryPath; module.exports.getBinaryPath = getBinaryPath;
module.exports.getVersionInfo = getVersionInfo; module.exports.getVersionInfo = getVersionInfo;
module.exports.getHumanEnvironment = getHumanEnvironment;
module.exports.getInstalledBinaries = getInstalledBinaries;
module.exports.isSupportedEnvironment = isSupportedEnvironment;
...@@ -4,13 +4,23 @@ ...@@ -4,13 +4,23 @@
var path = require('path'), var path = require('path'),
util = require('util'), util = require('util'),
errors = require('./errors'),
sass = require('./extensions'); sass = require('./extensions');
if (!sass.hasBinary(sass.getBinaryPath())) {
if (!sass.isSupportedEnvironment()) {
throw new Error(errors.unsupportedEnvironment());
} else {
throw new Error(errors.missingBinary());
}
}
/** /**
* Require binding * Require binding
*/ */
var binding = require(sass.getBinaryPath(true)); var binding = require(sass.getBinaryPath());
/** /**
* Get input file * Get input file
......
...@@ -2,7 +2,10 @@ var assert = require('assert'), ...@@ -2,7 +2,10 @@ var assert = require('assert'),
fs = require('fs'), fs = require('fs'),
path = require('path'), path = require('path'),
read = fs.readFileSync, 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'), fixture = path.join.bind(null, __dirname, 'fixtures'),
resolveFixture = path.resolve.bind(null, __dirname, 'fixtures'); resolveFixture = path.resolve.bind(null, __dirname, 'fixtures');
...@@ -1777,4 +1780,99 @@ describe('api', function() { ...@@ -1777,4 +1780,99 @@ describe('api', function() {
done(); 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;
});
});
});
}); });
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);
});
});
});
var assert = require('assert'), var assert = require('assert'),
fs = require('fs'),
extensionsPath = process.env.NODESASS_COV extensionsPath = process.env.NODESASS_COV
? require.resolve('../lib-cov/extensions') ? require.resolve('../lib-cov/extensions')
: require.resolve('../lib/extensions'); : require.resolve('../lib/extensions');
...@@ -137,17 +136,17 @@ describe('runtime parameters', function() { ...@@ -137,17 +136,17 @@ describe('runtime parameters', function() {
}); });
}); });
describe('library detection', function() { // describe('library detection', function() {
it('should throw error when libsass binary is missing.', function() { // it('should throw error when libsass binary is missing.', function() {
var sass = require(extensionsPath), // var sass = require(extensionsPath),
originalBin = sass.getBinaryPath(), // originalBin = sass.getBinaryPath(),
renamedBin = [originalBin, '_moved'].join(''); // renamedBin = [originalBin, '_moved'].join('');
assert.throws(function() { // assert.throws(function() {
fs.renameSync(originalBin, renamedBin); // fs.renameSync(originalBin, renamedBin);
sass.getBinaryPath(true); // sass.getBinaryPath(true);
}, /The `libsass` binding was not found/); // }, /The `libsass` binding was not found/);
fs.renameSync(renamedBin, originalBin); // fs.renameSync(renamedBin, originalBin);
}); // });
}); // });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment