node.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. var region = require('caniuse-lite/dist/unpacker/region').default
  2. var path = require('path')
  3. var fs = require('fs')
  4. var BrowserslistError = require('./error')
  5. var IS_SECTION = /^\s*\[(.+)\]\s*$/
  6. var CONFIG_PATTERN = /^browserslist-config-/
  7. var SCOPED_CONFIG__PATTERN = /@[^./]+\/browserslist-config(-|$|\/)/
  8. var TIME_TO_UPDATE_CANIUSE = 6 * 30 * 24 * 60 * 60 * 1000
  9. var FORMAT = 'Browserslist config should be a string or an array ' +
  10. 'of strings with browser queries'
  11. var dataTimeChecked = false
  12. var filenessCache = { }
  13. var configCache = { }
  14. function checkExtend (name) {
  15. var use = ' Use `dangerousExtend` option to disable.'
  16. if (!CONFIG_PATTERN.test(name) && !SCOPED_CONFIG__PATTERN.test(name)) {
  17. throw new BrowserslistError(
  18. 'Browserslist config needs `browserslist-config-` prefix. ' + use)
  19. }
  20. if (name.indexOf('.') !== -1) {
  21. throw new BrowserslistError(
  22. '`.` not allowed in Browserslist config name. ' + use)
  23. }
  24. if (name.indexOf('node_modules') !== -1) {
  25. throw new BrowserslistError(
  26. '`node_modules` not allowed in Browserslist config.' + use)
  27. }
  28. }
  29. function isFile (file) {
  30. if (file in filenessCache) {
  31. return filenessCache[file]
  32. }
  33. var result = fs.existsSync(file) && fs.statSync(file).isFile()
  34. if (!process.env.BROWSERSLIST_DISABLE_CACHE) {
  35. filenessCache[file] = result
  36. }
  37. return result
  38. }
  39. function eachParent (file, callback) {
  40. var loc = path.resolve(file)
  41. do {
  42. var result = callback(loc)
  43. if (typeof result !== 'undefined') return result
  44. } while (loc !== (loc = path.dirname(loc)))
  45. return undefined
  46. }
  47. function check (section) {
  48. if (Array.isArray(section)) {
  49. for (var i = 0; i < section.length; i++) {
  50. if (typeof section[i] !== 'string') {
  51. throw new BrowserslistError(FORMAT)
  52. }
  53. }
  54. } else if (typeof section !== 'string') {
  55. throw new BrowserslistError(FORMAT)
  56. }
  57. }
  58. function pickEnv (config, opts) {
  59. if (typeof config !== 'object') return config
  60. var name
  61. if (typeof opts.env === 'string') {
  62. name = opts.env
  63. } else if (process.env.BROWSERSLIST_ENV) {
  64. name = process.env.BROWSERSLIST_ENV
  65. } else if (process.env.NODE_ENV) {
  66. name = process.env.NODE_ENV
  67. } else {
  68. name = 'production'
  69. }
  70. return config[name] || config.defaults
  71. }
  72. function parsePackage (file) {
  73. var config = JSON.parse(fs.readFileSync(file))
  74. if (config.browserlist && !config.browserslist) {
  75. throw new BrowserslistError(
  76. '`browserlist` key instead of `browserslist` in ' + file)
  77. }
  78. var list = config.browserslist
  79. if (Array.isArray(list)) {
  80. list = { defaults: list }
  81. }
  82. for (var i in list) {
  83. check(list[i])
  84. }
  85. return list
  86. }
  87. function latestReleaseTime (agents) {
  88. var latest = 0
  89. for (var name in agents) {
  90. var dates = agents[name].releaseDate || { }
  91. for (var key in dates) {
  92. if (latest < dates[key]) {
  93. latest = dates[key]
  94. }
  95. }
  96. }
  97. return latest * 1000
  98. }
  99. module.exports = {
  100. loadQueries: function loadQueries (context, name) {
  101. if (!context.dangerousExtend) checkExtend(name)
  102. // eslint-disable-next-line security/detect-non-literal-require
  103. var queries = require(require.resolve(name, { paths: ['.'] }))
  104. if (!Array.isArray(queries)) {
  105. throw new BrowserslistError(
  106. '`' + name + '` config exports not an array of queries')
  107. }
  108. return queries
  109. },
  110. getStat: function getStat (opts, data) {
  111. var stats
  112. if (opts.stats) {
  113. stats = opts.stats
  114. } else if (process.env.BROWSERSLIST_STATS) {
  115. stats = process.env.BROWSERSLIST_STATS
  116. } else if (opts.path && path.resolve && fs.existsSync) {
  117. stats = eachParent(opts.path, function (dir) {
  118. var file = path.join(dir, 'browserslist-stats.json')
  119. return isFile(file) ? file : undefined
  120. })
  121. }
  122. if (typeof stats === 'string') {
  123. try {
  124. stats = JSON.parse(fs.readFileSync(stats))
  125. } catch (e) {
  126. throw new BrowserslistError('Can\'t read ' + stats)
  127. }
  128. }
  129. if (stats && 'dataByBrowser' in stats) {
  130. stats = stats.dataByBrowser
  131. }
  132. if (typeof stats !== 'object') return undefined
  133. var normalized = { }
  134. for (var i in stats) {
  135. var versions = Object.keys(stats[i])
  136. if (versions.length === 1 && data[i] && data[i].versions.length === 1) {
  137. var normal = Object.keys(data[i].versions)[0]
  138. normalized[i] = { }
  139. normalized[i][normal] = stats[i][versions[0]]
  140. } else {
  141. normalized[i] = stats[i]
  142. }
  143. }
  144. return normalized
  145. },
  146. loadConfig: function loadConfig (opts) {
  147. if (process.env.BROWSERSLIST) {
  148. return process.env.BROWSERSLIST
  149. } else if (opts.config || process.env.BROWSERSLIST_CONFIG) {
  150. var file = opts.config || process.env.BROWSERSLIST_CONFIG
  151. if (path.basename(file) === 'package.json') {
  152. return pickEnv(parsePackage(file), opts)
  153. } else {
  154. return pickEnv(module.exports.readConfig(file), opts)
  155. }
  156. } else if (opts.path) {
  157. return pickEnv(module.exports.findConfig(opts.path), opts)
  158. } else {
  159. return undefined
  160. }
  161. },
  162. loadCountry: function loadCountry (usage, country) {
  163. var code = country.replace(/[^\w-]/g, '')
  164. if (!usage[code]) {
  165. // eslint-disable-next-line security/detect-non-literal-require
  166. var compressed = require('caniuse-lite/data/regions/' + code + '.js')
  167. var data = region(compressed)
  168. usage[country] = { }
  169. for (var i in data) {
  170. for (var j in data[i]) {
  171. usage[country][i + ' ' + j] = data[i][j]
  172. }
  173. }
  174. }
  175. },
  176. parseConfig: function parseConfig (string) {
  177. var result = { defaults: [] }
  178. var sections = ['defaults']
  179. string.toString()
  180. .replace(/#[^\n]*/g, '')
  181. .split(/\n|,/)
  182. .map(function (line) {
  183. return line.trim()
  184. })
  185. .filter(function (line) {
  186. return line !== ''
  187. })
  188. .forEach(function (line) {
  189. if (IS_SECTION.test(line)) {
  190. sections = line.match(IS_SECTION)[1].trim().split(' ')
  191. sections.forEach(function (section) {
  192. if (result[section]) {
  193. throw new BrowserslistError(
  194. 'Duplicate section ' + section + ' in Browserslist config')
  195. }
  196. result[section] = []
  197. })
  198. } else {
  199. sections.forEach(function (section) {
  200. result[section].push(line)
  201. })
  202. }
  203. })
  204. return result
  205. },
  206. readConfig: function readConfig (file) {
  207. if (!isFile(file)) {
  208. throw new BrowserslistError('Can\'t read ' + file + ' config')
  209. }
  210. return module.exports.parseConfig(fs.readFileSync(file))
  211. },
  212. findConfig: function findConfig (from) {
  213. from = path.resolve(from)
  214. var cacheKey = isFile(from) ? path.dirname(from) : from
  215. if (cacheKey in configCache) {
  216. return configCache[cacheKey]
  217. }
  218. var resolved = eachParent(from, function (dir) {
  219. var config = path.join(dir, 'browserslist')
  220. var pkg = path.join(dir, 'package.json')
  221. var rc = path.join(dir, '.browserslistrc')
  222. var pkgBrowserslist
  223. if (isFile(pkg)) {
  224. try {
  225. pkgBrowserslist = parsePackage(pkg)
  226. } catch (e) {
  227. if (e.name === 'BrowserslistError') throw e
  228. console.warn(
  229. '[Browserslist] Could not parse ' + pkg + '. Ignoring it.')
  230. }
  231. }
  232. if (isFile(config) && pkgBrowserslist) {
  233. throw new BrowserslistError(
  234. dir + ' contains both browserslist and package.json with browsers')
  235. } else if (isFile(rc) && pkgBrowserslist) {
  236. throw new BrowserslistError(
  237. dir + ' contains both .browserslistrc and package.json with browsers')
  238. } else if (isFile(config) && isFile(rc)) {
  239. throw new BrowserslistError(
  240. dir + ' contains both .browserslistrc and browserslist')
  241. } else if (isFile(config)) {
  242. return module.exports.readConfig(config)
  243. } else if (isFile(rc)) {
  244. return module.exports.readConfig(rc)
  245. } else {
  246. return pkgBrowserslist
  247. }
  248. })
  249. if (!process.env.BROWSERSLIST_DISABLE_CACHE) {
  250. configCache[cacheKey] = resolved
  251. }
  252. return resolved
  253. },
  254. clearCaches: function clearCaches () {
  255. dataTimeChecked = false
  256. filenessCache = { }
  257. configCache = { }
  258. },
  259. oldDataWarning: function oldDataWarning (agentsObj) {
  260. if (dataTimeChecked) return
  261. dataTimeChecked = true
  262. var latest = latestReleaseTime(agentsObj)
  263. var halfYearAgo = Date.now() - TIME_TO_UPDATE_CANIUSE
  264. if (latest !== 0 && latest < halfYearAgo) {
  265. var command = 'npm update'
  266. eachParent(__filename, function (dir) {
  267. var pckg = path.join(dir, 'package.json')
  268. var yarnLock = path.join(dir, 'yarn.lock')
  269. if (isFile(pckg) && isFile(yarnLock)) {
  270. command = 'yarn upgrade'
  271. }
  272. })
  273. console.warn(
  274. 'Browserslist: caniuse-lite is outdated. ' +
  275. 'Please run next command `' + command + ' caniuse-lite browserslist`')
  276. }
  277. },
  278. currentNode: function currentNode () {
  279. return 'node ' + process.versions.node
  280. }
  281. }