extensions.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /*!
  2. * node-sass: lib/extensions.js
  3. */
  4. var eol = require('os').EOL,
  5. fs = require('fs'),
  6. pkg = require('../package.json'),
  7. mkdir = require('mkdirp'),
  8. path = require('path'),
  9. defaultBinaryDir = path.join(__dirname, '..', 'vendor'),
  10. trueCasePathSync = require('true-case-path');
  11. /**
  12. * Get the human readable name of the Platform that is running
  13. *
  14. * @param {string} platform - An OS platform to match, or null to fallback to
  15. * the current process platform
  16. * @return {Object} The name of the platform if matched, false otherwise
  17. *
  18. * @api public
  19. */
  20. function getHumanPlatform(platform) {
  21. switch (platform || process.platform) {
  22. case 'darwin': return 'OS X';
  23. case 'freebsd': return 'FreeBSD';
  24. case 'linux': return 'Linux';
  25. case 'linux_musl': return 'Linux/musl';
  26. case 'win32': return 'Windows';
  27. default: return false;
  28. }
  29. }
  30. /**
  31. * Provides a more readable version of the architecture
  32. *
  33. * @param {string} arch - An instruction architecture name to match, or null to
  34. * lookup the current process architecture
  35. * @return {Object} The value of the process architecture, or false if unknown
  36. *
  37. * @api public
  38. */
  39. function getHumanArchitecture(arch) {
  40. switch (arch || process.arch) {
  41. case 'ia32': return '32-bit';
  42. case 'x86': return '32-bit';
  43. case 'x64': return '64-bit';
  44. default: return false;
  45. }
  46. }
  47. /**
  48. * Get the friendly name of the Node environment being run
  49. *
  50. * @param {Object} abi - A Node Application Binary Interface value, or null to
  51. * fallback to the current Node ABI
  52. * @return {Object} Returns a string name of the Node environment or false if
  53. * unmatched
  54. *
  55. * @api public
  56. */
  57. function getHumanNodeVersion(abi) {
  58. switch (parseInt(abi || process.versions.modules, 10)) {
  59. case 11: return 'Node 0.10.x';
  60. case 14: return 'Node 0.12.x';
  61. case 42: return 'io.js 1.x';
  62. case 43: return 'io.js 1.1.x';
  63. case 44: return 'io.js 2.x';
  64. case 45: return 'io.js 3.x';
  65. case 46: return 'Node.js 4.x';
  66. case 47: return 'Node.js 5.x';
  67. case 48: return 'Node.js 6.x';
  68. case 49: return 'Electron 1.3.x';
  69. case 50: return 'Electron 1.4.x';
  70. case 51: return 'Node.js 7.x';
  71. case 53: return 'Electron 1.6.x';
  72. case 57: return 'Node.js 8.x';
  73. case 59: return 'Node.js 9.x';
  74. case 64: return 'Node.js 10.x';
  75. case 67: return 'Node.js 11.x';
  76. case 72: return 'Node.js 12.x';
  77. case 79: return 'Node.js 13.x';
  78. case 83: return 'Node.js 14.x';
  79. default: return false;
  80. }
  81. }
  82. /**
  83. * Get a human readable description of where node-sass is running to support
  84. * user error reporting when something goes wrong
  85. *
  86. * @param {string} env - The name of the native bindings that is to be parsed
  87. * @return {string} A description of what os, architecture, and Node version
  88. * that is being run
  89. *
  90. * @api public
  91. */
  92. function getHumanEnvironment(env) {
  93. var binding = env.replace(/_binding\.node$/, ''),
  94. parts = binding.split('-'),
  95. platform = getHumanPlatform(parts[0]),
  96. arch = getHumanArchitecture(parts[1]),
  97. runtime = getHumanNodeVersion(parts[2]);
  98. if (parts.length !== 3) {
  99. return 'Unknown environment (' + binding + ')';
  100. }
  101. if (!platform) {
  102. platform = 'Unsupported platform (' + parts[0] + ')';
  103. }
  104. if (!arch) {
  105. arch = 'Unsupported architecture (' + parts[1] + ')';
  106. }
  107. if (!runtime) {
  108. runtime = 'Unsupported runtime (' + parts[2] + ')';
  109. }
  110. return [
  111. platform, arch, 'with', runtime,
  112. ].join(' ');
  113. }
  114. /**
  115. * Get the value of the binaries under the default path
  116. *
  117. * @return {Array} The currently installed node-sass bindings
  118. *
  119. * @api public
  120. */
  121. function getInstalledBinaries() {
  122. return fs.readdirSync(getBinaryDir());
  123. }
  124. /**
  125. * Check that an environment matches the whitelisted values or the current
  126. * environment if no parameters are passed
  127. *
  128. * @param {string} platform - The name of the OS platform(darwin, win32, etc...)
  129. * @param {string} arch - The instruction set architecture of the Node environment
  130. * @param {string} abi - The Node Application Binary Interface
  131. * @return {Boolean} True, if node-sass supports the current platform, false otherwise
  132. *
  133. * @api public
  134. */
  135. function isSupportedEnvironment(platform, arch, abi) {
  136. return (
  137. false !== getHumanPlatform(platform) &&
  138. false !== getHumanArchitecture(arch) &&
  139. false !== getHumanNodeVersion(abi)
  140. );
  141. }
  142. /**
  143. * Get the value of a CLI argument
  144. *
  145. * @param {String} name
  146. * @param {Array} args
  147. * @api private
  148. */
  149. function getArgument(name, args) {
  150. var flags = args || process.argv.slice(2),
  151. index = flags.lastIndexOf(name);
  152. if (index === -1 || index + 1 >= flags.length) {
  153. return null;
  154. }
  155. return flags[index + 1];
  156. }
  157. /**
  158. * Get binary name.
  159. * If environment variable SASS_BINARY_NAME,
  160. * .npmrc variable sass_binary_name or
  161. * process argument --binary-name is provided,
  162. * return it as is, otherwise make default binary
  163. * name: {platform}-{arch}-{v8 version}.node
  164. *
  165. * @api public
  166. */
  167. function getBinaryName() {
  168. var binaryName,
  169. variant,
  170. platform = process.platform;
  171. if (getArgument('--sass-binary-name')) {
  172. binaryName = getArgument('--sass-binary-name');
  173. } else if (process.env.SASS_BINARY_NAME) {
  174. binaryName = process.env.SASS_BINARY_NAME;
  175. } else if (process.env.npm_config_sass_binary_name) {
  176. binaryName = process.env.npm_config_sass_binary_name;
  177. } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryName) {
  178. binaryName = pkg.nodeSassConfig.binaryName;
  179. } else {
  180. variant = getPlatformVariant();
  181. if (variant) {
  182. platform += '_' + variant;
  183. }
  184. binaryName = [
  185. platform, '-',
  186. process.arch, '-',
  187. process.versions.modules
  188. ].join('');
  189. }
  190. return [binaryName, 'binding.node'].join('_');
  191. }
  192. /**
  193. * Determine the URL to fetch binary file from.
  194. * By default fetch from the node-sass distribution
  195. * site on GitHub.
  196. *
  197. * The default URL can be overridden using
  198. * the environment variable SASS_BINARY_SITE,
  199. * .npmrc variable sass_binary_site or
  200. * or a command line option --sass-binary-site:
  201. *
  202. * node scripts/install.js --sass-binary-site http://example.com/
  203. *
  204. * The URL should to the mirror of the repository
  205. * laid out as follows:
  206. *
  207. * SASS_BINARY_SITE/
  208. *
  209. * v3.0.0
  210. * v3.0.0/freebsd-x64-14_binding.node
  211. * ....
  212. * v3.0.0
  213. * v3.0.0/freebsd-ia32-11_binding.node
  214. * v3.0.0/freebsd-x64-42_binding.node
  215. * ... etc. for all supported versions and platforms
  216. *
  217. * @api public
  218. */
  219. function getBinaryUrl() {
  220. var site = getArgument('--sass-binary-site') ||
  221. process.env.SASS_BINARY_SITE ||
  222. process.env.npm_config_sass_binary_site ||
  223. (pkg.nodeSassConfig && pkg.nodeSassConfig.binarySite) ||
  224. 'https://github.com/sass/node-sass/releases/download';
  225. return [site, 'v' + pkg.version, getBinaryName()].join('/');
  226. }
  227. /**
  228. * Get binary dir.
  229. * If environment variable SASS_BINARY_DIR,
  230. * .npmrc variable sass_binary_dir or
  231. * process argument --sass-binary-dir is provided,
  232. * select it by appending binary name, otherwise
  233. * use default binary dir.
  234. * Once the primary selection is made, check if
  235. * callers wants to throw if file not exists before
  236. * returning.
  237. *
  238. * @api public
  239. */
  240. function getBinaryDir() {
  241. var binaryDir;
  242. if (getArgument('--sass-binary-dir')) {
  243. binaryDir = getArgument('--sass-binary-dir');
  244. } else if (process.env.SASS_BINARY_DIR) {
  245. binaryDir = process.env.SASS_BINARY_DIR;
  246. } else if (process.env.npm_config_sass_binary_dir) {
  247. binaryDir = process.env.npm_config_sass_binary_dir;
  248. } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryDir) {
  249. binaryDir = pkg.nodeSassConfig.binaryDir;
  250. } else {
  251. binaryDir = defaultBinaryDir;
  252. }
  253. return binaryDir;
  254. }
  255. /**
  256. * Get binary path.
  257. * If environment variable SASS_BINARY_PATH,
  258. * .npmrc variable sass_binary_path or
  259. * process argument --sass-binary-path is provided,
  260. * select it by appending binary name, otherwise
  261. * make default binary path using binary name.
  262. * Once the primary selection is made, check if
  263. * callers wants to throw if file not exists before
  264. * returning.
  265. *
  266. * @api public
  267. */
  268. function getBinaryPath() {
  269. var binaryPath;
  270. if (getArgument('--sass-binary-path')) {
  271. binaryPath = getArgument('--sass-binary-path');
  272. } else if (process.env.SASS_BINARY_PATH) {
  273. binaryPath = process.env.SASS_BINARY_PATH;
  274. } else if (process.env.npm_config_sass_binary_path) {
  275. binaryPath = process.env.npm_config_sass_binary_path;
  276. } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) {
  277. binaryPath = pkg.nodeSassConfig.binaryPath;
  278. } else {
  279. binaryPath = path.join(getBinaryDir(), getBinaryName().replace(/_(?=binding\.node)/, '/'));
  280. }
  281. if (process.versions.modules < 46) {
  282. return binaryPath;
  283. }
  284. try {
  285. return trueCasePathSync(binaryPath) || binaryPath;
  286. } catch (e) {
  287. return binaryPath;
  288. }
  289. }
  290. /**
  291. * An array of paths suitable for use as a local disk cache of the binding.
  292. *
  293. * @return {[]String} an array of paths
  294. * @api public
  295. */
  296. function getCachePathCandidates() {
  297. return [
  298. process.env.npm_config_sass_binary_cache,
  299. process.env.npm_config_cache,
  300. ].filter(function(_) { return _; });
  301. }
  302. /**
  303. * The most suitable location for caching the binding on disk.
  304. *
  305. * Given the candidates directories provided by `getCachePathCandidates()` this
  306. * returns the first writable directory. By treating the candidate directories
  307. * as a prioritised list this method is deterministic, assuming no change to the
  308. * local environment.
  309. *
  310. * @return {String} directory to cache binding
  311. * @api public
  312. */
  313. function getBinaryCachePath() {
  314. var i,
  315. cachePath,
  316. cachePathCandidates = getCachePathCandidates();
  317. for (i = 0; i < cachePathCandidates.length; i++) {
  318. cachePath = path.join(cachePathCandidates[i], pkg.name, pkg.version);
  319. try {
  320. mkdir.sync(cachePath);
  321. return cachePath;
  322. } catch (e) {
  323. // Directory is not writable, try another
  324. }
  325. }
  326. return '';
  327. }
  328. /**
  329. * The cached binding
  330. *
  331. * Check the candidates directories provided by `getCachePathCandidates()` for
  332. * the binding file, if it exists. By treating the candidate directories
  333. * as a prioritised list this method is deterministic, assuming no change to the
  334. * local environment.
  335. *
  336. * @return {String} path to cached binary
  337. * @api public
  338. */
  339. function getCachedBinary() {
  340. var i,
  341. cachePath,
  342. cacheBinary,
  343. cachePathCandidates = getCachePathCandidates(),
  344. binaryName = getBinaryName();
  345. for (i = 0; i < cachePathCandidates.length; i++) {
  346. cachePath = path.join(cachePathCandidates[i], pkg.name, pkg.version);
  347. cacheBinary = path.join(cachePath, binaryName);
  348. if (fs.existsSync(cacheBinary)) {
  349. return cacheBinary;
  350. }
  351. }
  352. return '';
  353. }
  354. /**
  355. * Does the supplied binary path exist
  356. *
  357. * @param {String} binaryPath
  358. * @api public
  359. */
  360. function hasBinary(binaryPath) {
  361. return fs.existsSync(binaryPath);
  362. }
  363. /**
  364. * Get Sass version information
  365. *
  366. * @api public
  367. */
  368. function getVersionInfo(binding) {
  369. return [
  370. ['node-sass', pkg.version, '(Wrapper)', '[JavaScript]'].join('\t'),
  371. ['libsass ', binding.libsassVersion(), '(Sass Compiler)', '[C/C++]'].join('\t'),
  372. ].join(eol);
  373. }
  374. /**
  375. * Gets the platform variant, currently either an empty string or 'musl' for Linux/musl platforms.
  376. *
  377. * @api public
  378. */
  379. function getPlatformVariant() {
  380. var contents = '';
  381. if (process.platform !== 'linux') {
  382. return '';
  383. }
  384. try {
  385. 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();
  390. }
  391. if (contents.indexOf('libc.musl-x86_64.so.1') !== -1) {
  392. return 'musl';
  393. }
  394. } catch (err) { } // eslint-disable-line no-empty
  395. return '';
  396. }
  397. module.exports.hasBinary = hasBinary;
  398. module.exports.getBinaryUrl = getBinaryUrl;
  399. module.exports.getBinaryName = getBinaryName;
  400. module.exports.getBinaryDir = getBinaryDir;
  401. module.exports.getBinaryPath = getBinaryPath;
  402. module.exports.getBinaryCachePath = getBinaryCachePath;
  403. module.exports.getCachedBinary = getCachedBinary;
  404. module.exports.getCachePathCandidates = getCachePathCandidates;
  405. module.exports.getVersionInfo = getVersionInfo;
  406. module.exports.getHumanEnvironment = getHumanEnvironment;
  407. module.exports.getInstalledBinaries = getInstalledBinaries;
  408. module.exports.isSupportedEnvironment = isSupportedEnvironment;