Skip to content
This repository was archived by the owner on Jul 24, 2024. It is now read-only.

Commit d76923b

Browse files
committed
Merge pull request #1428 from xzyfer/feat/better-binary-error-messages
Better error messages for missing binaries
2 parents 41db8b9 + cf87e0b commit d76923b

File tree

6 files changed

+295
-26
lines changed

6 files changed

+295
-26
lines changed

lib/errors.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*!
2+
* node-sass: lib/errors.js
3+
*/
4+
5+
var sass = require('./extensions');
6+
7+
function humanEnvironment() {
8+
return sass.getHumanEnvironment(sass.getBinaryName());
9+
}
10+
11+
function foundBinaries() {
12+
return [
13+
'Found bindings for the following environments:',
14+
foundBinariesList(),
15+
].join('\n');
16+
}
17+
18+
function foundBinariesList() {
19+
return sass.getInstalledBinaries().map(function(env) {
20+
return ' - ' + sass.getHumanEnvironment(env);
21+
}).join('\n');
22+
}
23+
24+
function missingBinaryFooter() {
25+
return [
26+
'This usually happens because your environment has changed since running `npm install`.',
27+
'Run `npm rebuild node-sass` to build the binding for your current environment.',
28+
].join('\n');
29+
}
30+
31+
module.exports.unsupportedEnvironment = function() {
32+
return [
33+
'Node Sass does not yet support your current environment: ' + humanEnvironment(),
34+
'For more information on which environments are supported please see:',
35+
'TODO URL'
36+
].join('\n');
37+
};
38+
39+
module.exports.missingBinary = function() {
40+
return [
41+
'Missing binding ' + sass.getBinaryPath(),
42+
'Node Sass could not find a binding for your current environment: ' + humanEnvironment(),
43+
'',
44+
foundBinaries(),
45+
'',
46+
missingBinaryFooter(),
47+
].join('\n');
48+
};

lib/extensions.js

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,68 @@
55
var eol = require('os').EOL,
66
fs = require('fs'),
77
pkg = require('../package.json'),
8-
path = require('path');
8+
path = require('path'),
9+
defaultBinaryPath = path.join(__dirname, '..', 'vendor');
10+
11+
function getHumanPlatform(arg) {
12+
switch (arg || process.platform) {
13+
case 'darwin': return 'OS X';
14+
case 'freebsd': return 'FreeBSD';
15+
case 'linux': return 'Linux';
16+
case 'win32': return 'Windows';
17+
default: return false;
18+
}
19+
}
20+
21+
function getHumanArchitecture(arg) {
22+
switch (arg || process.arch) {
23+
case 'ia32': return '32-bit';
24+
case 'x86': return '32-bit';
25+
case 'x64': return '64-bit';
26+
default: return false;
27+
}
28+
}
29+
30+
function getHumanNodeVersion(arg) {
31+
switch (parseInt(arg || process.versions.modules, 10)) {
32+
case 11: return 'Node 0.10.x';
33+
case 14: return 'Node 0.12.x';
34+
case 42: return 'io.js 1.x';
35+
case 43: return 'io.js 1.1.x';
36+
case 44: return 'io.js 2.x';
37+
case 45: return 'io.js 3.x';
38+
case 46: return 'Node.js 4.x';
39+
case 47: return 'Node.js 5.x';
40+
default: return false;
41+
}
42+
}
43+
44+
function getHumanEnvironment(env) {
45+
var parts = env.replace(/_binding\.node$/, '').split('-');
46+
47+
if (parts.length !== 3) {
48+
return 'Unknown environment';
49+
}
50+
51+
return [
52+
getHumanPlatform(parts[0]),
53+
getHumanArchitecture(parts[1]),
54+
'with',
55+
getHumanNodeVersion(parts[2]),
56+
].join(' ');
57+
}
58+
59+
function getInstalledBinaries() {
60+
return fs.readdirSync(defaultBinaryPath);
61+
}
62+
63+
function isSupportedEnvironment() {
64+
return (
65+
false !== getHumanPlatform() &&
66+
false !== getHumanArchitecture() &&
67+
false !== getHumanNodeVersion()
68+
);
69+
}
970

1071
/**
1172
* Get the value of a CLI argument
@@ -110,7 +171,7 @@ function getBinaryUrl() {
110171
* @api public
111172
*/
112173

113-
function getBinaryPath(throwIfNotExists) {
174+
function getBinaryPath() {
114175
var binaryPath;
115176

116177
if (getArgument('--sass-binary-path')) {
@@ -122,20 +183,16 @@ function getBinaryPath(throwIfNotExists) {
122183
} else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) {
123184
binaryPath = pkg.nodeSassConfig.binaryPath;
124185
} else {
125-
binaryPath = path.join(__dirname, '..', 'vendor', getBinaryName().replace(/_/, '/'));
126-
}
127-
128-
if (!fs.existsSync(binaryPath) && throwIfNotExists) {
129-
throw new Error([
130-
['The `libsass` binding was not found in', binaryPath].join(' '),
131-
['This usually happens because your node version has changed.'],
132-
['Run `npm rebuild node-sass` to build the binding for your current node version.'],
133-
].join('\n'));
186+
binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_/, '/'));
134187
}
135188

136189
return binaryPath;
137190
}
138191

192+
function hasBinary(binaryPath) {
193+
return fs.existsSync(binaryPath);
194+
}
195+
139196
/**
140197
* Get Sass version information
141198
*
@@ -149,7 +206,11 @@ function getVersionInfo(binding) {
149206
].join(eol);
150207
}
151208

209+
module.exports.hasBinary = hasBinary;
152210
module.exports.getBinaryUrl = getBinaryUrl;
153211
module.exports.getBinaryName = getBinaryName;
154212
module.exports.getBinaryPath = getBinaryPath;
155213
module.exports.getVersionInfo = getVersionInfo;
214+
module.exports.getHumanEnvironment = getHumanEnvironment;
215+
module.exports.getInstalledBinaries = getInstalledBinaries;
216+
module.exports.isSupportedEnvironment = isSupportedEnvironment;

lib/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,23 @@
44

55
var path = require('path'),
66
util = require('util'),
7+
errors = require('./errors'),
78
sass = require('./extensions');
89

10+
if (!sass.hasBinary(sass.getBinaryPath())) {
11+
if (!sass.isSupportedEnvironment()) {
12+
throw new Error(errors.unsupportedEnvironment());
13+
} else {
14+
throw new Error(errors.missingBinary());
15+
}
16+
}
17+
18+
919
/**
1020
* Require binding
1121
*/
1222

13-
var binding = require(sass.getBinaryPath(true));
23+
var binding = require(sass.getBinaryPath());
1424

1525
/**
1626
* Get input file

test/api.js

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ var assert = require('assert'),
22
fs = require('fs'),
33
path = require('path'),
44
read = fs.readFileSync,
5-
sass = process.env.NODESASS_COV ? require('../lib-cov') : require('../lib'),
5+
sassPath = process.env.NODESASS_COV
6+
? require.resolve('../lib-cov')
7+
: require.resolve('../lib'),
8+
sass = require(sassPath),
69
fixture = path.join.bind(null, __dirname, 'fixtures'),
710
resolveFixture = path.resolve.bind(null, __dirname, 'fixtures');
811

@@ -1777,4 +1780,99 @@ describe('api', function() {
17771780
done();
17781781
});
17791782
});
1783+
1784+
describe('binding', function() {
1785+
beforeEach(function() {
1786+
delete require.cache[sassPath];
1787+
});
1788+
1789+
afterEach(function() {
1790+
delete require.cache[sassPath];
1791+
});
1792+
1793+
describe('missing error', function() {
1794+
beforeEach(function() {
1795+
process.env.SASS_BINARY_NAME = [
1796+
(process.platform === 'win32' ? 'Linux' : 'Windows'), '-',
1797+
process.arch, '-',
1798+
process.versions.modules
1799+
].join('');
1800+
});
1801+
1802+
afterEach(function() {
1803+
delete process.env.SASS_BINARY_NAME;
1804+
});
1805+
1806+
it('should be useful', function() {
1807+
assert.throws(
1808+
function() { require(sassPath); },
1809+
new RegExp('Missing binding.*?\\' + path.sep + 'vendor\\' + path.sep)
1810+
);
1811+
});
1812+
1813+
it('should list currently installed bindings', function() {
1814+
assert.throws(
1815+
function() { require(sassPath); },
1816+
function(err) {
1817+
var etx = require('../lib/extensions');
1818+
1819+
delete process.env.SASS_BINARY_NAME;
1820+
1821+
if ((err instanceof Error)) {
1822+
return err.message.indexOf(
1823+
etx.getHumanEnvironment(etx.getBinaryName())
1824+
) !== -1;
1825+
}
1826+
}
1827+
);
1828+
});
1829+
});
1830+
1831+
describe('on unsupported environment', function() {
1832+
it('should error for unsupported architecture', function() {
1833+
var prevValue = process.arch;
1834+
1835+
Object.defineProperty(process, 'arch', {
1836+
get: function () { return 'foo'; }
1837+
});
1838+
1839+
assert.throws(
1840+
function() { require(sassPath); },
1841+
'Node Sass does not yet support your current environment'
1842+
);
1843+
1844+
process.arch = prevValue;
1845+
});
1846+
1847+
it('should error for unsupported platform', function() {
1848+
var prevValue = process.platform;
1849+
1850+
Object.defineProperty(process, 'platform', {
1851+
get: function () { return 'foo'; }
1852+
});
1853+
1854+
assert.throws(
1855+
function() { require(sassPath); },
1856+
'Node Sass does not yet support your current environment'
1857+
);
1858+
1859+
process.platform = prevValue;
1860+
});
1861+
1862+
it('should error for unsupported runtime', function() {
1863+
var prevValue = process.versions.modules;
1864+
1865+
Object.defineProperty(process.versions, 'modules', {
1866+
get: function () { return 'foo'; }
1867+
});
1868+
1869+
assert.throws(
1870+
function() { require(sassPath); },
1871+
'Node Sass does not yet support your current environment'
1872+
);
1873+
1874+
process.versions.modules = prevValue;
1875+
});
1876+
});
1877+
});
17801878
});

test/errors.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
var assert = require('assert'),
2+
path = require('path'),
3+
errors = require('../lib/errors');
4+
5+
describe('binary errors', function() {
6+
7+
function getCurrentPlatform() {
8+
if (process.platform === 'win32') {
9+
return 'Windows';
10+
} else if (process.platform === 'darwin') {
11+
return 'OS X';
12+
}
13+
return '';
14+
}
15+
16+
function getCurrentArchitecture() {
17+
if (process.arch === 'x86' || process.arch === 'ia32') {
18+
return '32-bit';
19+
} else if (process.arch === 'x64') {
20+
return '64-bit';
21+
}
22+
return '';
23+
}
24+
25+
function getCurrentEnvironment() {
26+
return getCurrentPlatform() + ' ' + getCurrentArchitecture();
27+
}
28+
29+
describe('for an unsupported environment', function() {
30+
it('identifies the current environment', function() {
31+
var message = errors.unsupportedEnvironment();
32+
assert.ok(message.indexOf(getCurrentEnvironment()) !== -1);
33+
});
34+
35+
it('links to supported environment documentation', function() {
36+
var message = errors.unsupportedEnvironment();
37+
assert.ok(message.indexOf('TODO URL') !== -1);
38+
});
39+
});
40+
41+
describe('for an missing binary', function() {
42+
it('identifies the current environment', function() {
43+
var message = errors.missingBinary();
44+
assert.ok(message.indexOf(getCurrentEnvironment()) !== -1);
45+
});
46+
47+
it('documents the expected binary location', function() {
48+
var message = errors.missingBinary();
49+
assert.ok(message.indexOf(path.sep + 'vendor' + path.sep) !== -1);
50+
});
51+
});
52+
53+
});

test/runtime.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
var assert = require('assert'),
2-
fs = require('fs'),
32
extensionsPath = process.env.NODESASS_COV
43
? require.resolve('../lib-cov/extensions')
54
: require.resolve('../lib/extensions');
@@ -137,17 +136,17 @@ describe('runtime parameters', function() {
137136
});
138137
});
139138

140-
describe('library detection', function() {
141-
it('should throw error when libsass binary is missing.', function() {
142-
var sass = require(extensionsPath),
143-
originalBin = sass.getBinaryPath(),
144-
renamedBin = [originalBin, '_moved'].join('');
139+
// describe('library detection', function() {
140+
// it('should throw error when libsass binary is missing.', function() {
141+
// var sass = require(extensionsPath),
142+
// originalBin = sass.getBinaryPath(),
143+
// renamedBin = [originalBin, '_moved'].join('');
145144

146-
assert.throws(function() {
147-
fs.renameSync(originalBin, renamedBin);
148-
sass.getBinaryPath(true);
149-
}, /The `libsass` binding was not found/);
145+
// assert.throws(function() {
146+
// fs.renameSync(originalBin, renamedBin);
147+
// sass.getBinaryPath(true);
148+
// }, /The `libsass` binding was not found/);
150149

151-
fs.renameSync(renamedBin, originalBin);
152-
});
153-
});
150+
// fs.renameSync(renamedBin, originalBin);
151+
// });
152+
// });

0 commit comments

Comments
 (0)