2 * node-sass: lib/extensions.js
5 var eol = require('os').EOL,
7 pkg = require('../package.json'),
8 mkdir = require('mkdirp'),
9 path = require('path'),
10 defaultBinaryPath = path.join(__dirname, '..', 'vendor');
13 * Get the human readable name of the Platform that is running
15 * @param {string} platform - An OS platform to match, or null to fallback to
16 * the current process platform
17 * @return {Object} The name of the platform if matched, false otherwise
21 function getHumanPlatform(platform) {
22 switch (platform || process.platform) {
23 case 'darwin': return 'OS X';
24 case 'freebsd': return 'FreeBSD';
25 case 'linux': return 'Linux';
26 case 'linux_musl': return 'Linux/musl';
27 case 'win32': return 'Windows';
28 default: return false;
33 * Provides a more readable version of the architecture
35 * @param {string} arch - An instruction architecture name to match, or null to
36 * lookup the current process architecture
37 * @return {Object} The value of the process architecture, or false if unknown
41 function getHumanArchitecture(arch) {
42 switch (arch || process.arch) {
43 case 'ia32': return '32-bit';
44 case 'x86': return '32-bit';
45 case 'x64': return '64-bit';
46 default: return false;
51 * Get the friendly name of the Node environment being run
53 * @param {Object} abi - A Node Application Binary Interface value, or null to
54 * fallback to the current Node ABI
55 * @return {Object} Returns a string name of the Node environment or false if
60 function getHumanNodeVersion(abi) {
61 switch (parseInt(abi || process.versions.modules, 10)) {
62 case 11: return 'Node 0.10.x';
63 case 14: return 'Node 0.12.x';
64 case 42: return 'io.js 1.x';
65 case 43: return 'io.js 1.1.x';
66 case 44: return 'io.js 2.x';
67 case 45: return 'io.js 3.x';
68 case 46: return 'Node.js 4.x';
69 case 47: return 'Node.js 5.x';
70 case 48: return 'Node.js 6.x';
71 case 51: return 'Node.js 7.x';
72 default: return false;
77 * Get a human readable description of where node-sass is running to support
78 * user error reporting when something goes wrong
80 * @param {string} env - The name of the native bindings that is to be parsed
81 * @return {string} A description of what os, architecture, and Node version
86 function getHumanEnvironment(env) {
87 var binding = env.replace(/_binding\.node$/, ''),
88 parts = binding.split('-'),
89 platform = getHumanPlatform(parts[0]),
90 arch = getHumanArchitecture(parts[1]),
91 runtime = getHumanNodeVersion(parts[2]);
93 if (parts.length !== 3) {
94 return 'Unknown environment (' + binding + ')';
98 platform = 'Unsupported platform (' + parts[0] + ')';
102 arch = 'Unsupported architecture (' + parts[1] + ')';
106 runtime = 'Unsupported runtime (' + parts[2] + ')';
110 platform, arch, 'with', runtime,
115 * Get the value of the binaries under the default path
117 * @return {Array} The currently installed node-sass bindings
121 function getInstalledBinaries() {
122 return fs.readdirSync(defaultBinaryPath);
126 * Check that an environment matches the whitelisted values or the current
127 * environment if no parameters are passed
129 * @param {string} platform - The name of the OS platform(darwin, win32, etc...)
130 * @param {string} arch - The instruction set architecture of the Node environment
131 * @param {string} abi - The Node Application Binary Interface
132 * @return {Boolean} True, if node-sass supports the current platform, false otherwise
136 function isSupportedEnvironment(platform, arch, abi) {
138 false !== getHumanPlatform(platform) &&
139 false !== getHumanArchitecture(arch) &&
140 false !== getHumanNodeVersion(abi)
145 * Get the value of a CLI argument
147 * @param {String} name
148 * @param {Array} args
152 function getArgument(name, args) {
153 var flags = args || process.argv.slice(2),
154 index = flags.lastIndexOf(name);
156 if (index === -1 || index + 1 >= flags.length) {
160 return flags[index + 1];
165 * If environment variable SASS_BINARY_NAME,
166 * .npmrc variable sass_binary_name or
167 * process argument --binary-name is provided,
168 * return it as is, otherwise make default binary
169 * name: {platform}-{arch}-{v8 version}.node
174 function getBinaryName() {
177 platform = process.platform;
179 if (getArgument('--sass-binary-name')) {
180 binaryName = getArgument('--sass-binary-name');
181 } else if (process.env.SASS_BINARY_NAME) {
182 binaryName = process.env.SASS_BINARY_NAME;
183 } else if (process.env.npm_config_sass_binary_name) {
184 binaryName = process.env.npm_config_sass_binary_name;
185 } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryName) {
186 binaryName = pkg.nodeSassConfig.binaryName;
188 variant = getPlatformVariant();
190 platform += '_' + variant;
196 process.versions.modules
200 return [binaryName, 'binding.node'].join('_');
204 * Determine the URL to fetch binary file from.
205 * By default fetch from the node-sass distribution
208 * The default URL can be overriden using
209 * the environment variable SASS_BINARY_SITE,
210 * .npmrc variable sass_binary_site or
211 * or a command line option --sass-binary-site:
213 * node scripts/install.js --sass-binary-site http://example.com/
215 * The URL should to the mirror of the repository
216 * laid out as follows:
221 * v3.0.0/freebsd-x64-14_binding.node
224 * v3.0.0/freebsd-ia32-11_binding.node
225 * v3.0.0/freebsd-x64-42_binding.node
226 * ... etc. for all supported versions and platforms
231 function getBinaryUrl() {
232 var site = getArgument('--sass-binary-site') ||
233 process.env.SASS_BINARY_SITE ||
234 process.env.npm_config_sass_binary_site ||
235 (pkg.nodeSassConfig && pkg.nodeSassConfig.binarySite) ||
236 'https://github.com/sass/node-sass/releases/download';
238 return [site, 'v' + pkg.version, getBinaryName()].join('/');
243 * If environment variable SASS_BINARY_PATH,
244 * .npmrc variable sass_binary_path or
245 * process argument --sass-binary-path is provided,
246 * select it by appending binary name, otherwise
247 * make default binary path using binary name.
248 * Once the primary selection is made, check if
249 * callers wants to throw if file not exists before
255 function getBinaryPath() {
258 if (getArgument('--sass-binary-path')) {
259 binaryPath = getArgument('--sass-binary-path');
260 } else if (process.env.SASS_BINARY_PATH) {
261 binaryPath = process.env.SASS_BINARY_PATH;
262 } else if (process.env.npm_config_sass_binary_path) {
263 binaryPath = process.env.npm_config_sass_binary_path;
264 } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) {
265 binaryPath = pkg.nodeSassConfig.binaryPath;
267 binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_(?=binding\.node)/, '/'));
274 * An array of paths suitable for use as a local disk cache of the binding.
276 * @return {[]String} an array of paths
279 function getCachePathCandidates() {
281 process.env.npm_config_sass_binary_cache,
282 process.env.npm_config_cache,
283 ].filter(function(_) { return _; });
287 * The most suitable location for caching the binding on disk.
289 * Given the candidates directories provided by `getCachePathCandidates()` this
290 * returns the first writable directory. By treating the candidate directories
291 * as a prioritised list this method is deterministic, assuming no change to the
294 * @return {String} directory to cache binding
297 function getBinaryCachePath() {
300 cachePathCandidates = getCachePathCandidates();
302 for (i = 0; i < cachePathCandidates.length; i++) {
303 cachePath = path.join(cachePathCandidates[i], pkg.name, pkg.version);
306 mkdir.sync(cachePath);
309 // Directory is not writable, try another
319 * Check the candidates directories provided by `getCachePathCandidates()` for
320 * the binding file, if it exists. By treating the candidate directories
321 * as a prioritised list this method is deterministic, assuming no change to the
324 * @return {String} path to cached binary
327 function getCachedBinary() {
331 cachePathCandidates = getCachePathCandidates(),
332 binaryName = getBinaryName();
334 for (i = 0; i < cachePathCandidates.length; i++) {
335 cachePath = path.join(cachePathCandidates[i], pkg.name, pkg.version);
336 cacheBinary = path.join(cachePath, binaryName);
338 if (fs.existsSync(cacheBinary)) {
347 * Does the supplied binary path exist
349 * @param {String} binaryPath
353 function hasBinary(binaryPath) {
354 return fs.existsSync(binaryPath);
358 * Get Sass version information
363 function getVersionInfo(binding) {
365 ['node-sass', pkg.version, '(Wrapper)', '[JavaScript]'].join('\t'),
366 ['libsass ', binding.libsassVersion(), '(Sass Compiler)', '[C/C++]'].join('\t'),
371 * Gets the platform variant, currently either an empty string or 'musl' for Linux/musl platforms.
376 function getPlatformVariant() {
379 if (process.platform !== 'linux') {
384 contents = fs.readFileSync(process.execPath);
386 // Buffer.indexOf was added in v1.5.0 so cast to string for old node
387 // Delay contents.toStrings because it's expensive
388 if (!contents.indexOf) {
389 contents = contents.toString();
392 if (contents.indexOf('libc.musl-x86_64.so.1') !== -1) {
395 } catch (err) { } // eslint-disable-line no-empty
400 module.exports.hasBinary = hasBinary;
401 module.exports.getBinaryUrl = getBinaryUrl;
402 module.exports.getBinaryName = getBinaryName;
403 module.exports.getBinaryPath = getBinaryPath;
404 module.exports.getBinaryCachePath = getBinaryCachePath;
405 module.exports.getCachedBinary = getCachedBinary;
406 module.exports.getCachePathCandidates = getCachePathCandidates;
407 module.exports.getVersionInfo = getVersionInfo;
408 module.exports.getHumanEnvironment = getHumanEnvironment;
409 module.exports.getInstalledBinaries = getInstalledBinaries;
410 module.exports.isSupportedEnvironment = isSupportedEnvironment;