update-db.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. var childProcess = require('child_process')
  2. var colorette = require('colorette')
  3. var escalade = require('escalade/sync')
  4. var path = require('path')
  5. var fs = require('fs')
  6. var BrowserslistError = require('./error')
  7. var red = colorette.red
  8. var bold = colorette.bold
  9. var green = colorette.green
  10. var yellow = colorette.yellow
  11. function detectLockfile () {
  12. var packageDir = escalade('.', function (dir, names) {
  13. return names.indexOf('package.json') !== -1 ? dir : ''
  14. })
  15. if (!packageDir) {
  16. throw new BrowserslistError(
  17. 'Cannot find package.json. ' +
  18. 'Is it a right project to run npx browserslist --update-db?'
  19. )
  20. }
  21. var lockfileNpm = path.join(packageDir, 'package-lock.json')
  22. var lockfileYarn = path.join(packageDir, 'yarn.lock')
  23. var lockfilePnpm = path.join(packageDir, 'pnpm-lock.yaml')
  24. /* istanbul ignore next */
  25. if (fs.existsSync(lockfilePnpm)) {
  26. return { mode: 'pnpm', file: lockfilePnpm }
  27. } else if (fs.existsSync(lockfileNpm)) {
  28. return { mode: 'npm', file: lockfileNpm }
  29. } else if (fs.existsSync(lockfileYarn)) {
  30. return { mode: 'yarn', file: lockfileYarn }
  31. } else {
  32. throw new BrowserslistError(
  33. 'No lockfile found. Run "npm install", "yarn install" or "pnpm install"'
  34. )
  35. }
  36. }
  37. function getCurrentVersion (lock) {
  38. var match
  39. /* istanbul ignore if */
  40. if (lock.mode === 'pnpm') {
  41. match = /\/caniuse-lite\/([^:]+):/.exec(lock.content)
  42. if (match[1]) return match[1]
  43. } else if (lock.mode === 'npm') {
  44. var dependencies = JSON.parse(lock.content).dependencies
  45. if (dependencies && dependencies['caniuse-lite']) {
  46. return dependencies['caniuse-lite'].version
  47. }
  48. } else if (lock.mode === 'yarn') {
  49. match = /caniuse-lite@[^:]+:\r?\n\s+version\s+"([^"]+)"/.exec(lock.content)
  50. if (match[1]) return match[1]
  51. }
  52. return null
  53. }
  54. function getLatestInfo (lock) {
  55. if (lock.mode !== 'yarn') {
  56. return JSON.parse(
  57. childProcess.execSync('npm show caniuse-lite --json').toString()
  58. )
  59. } else {
  60. return JSON.parse(
  61. childProcess.execSync('yarn info caniuse-lite --json').toString()
  62. ).data
  63. }
  64. }
  65. function getBrowsersList () {
  66. return childProcess.execSync('npx browserslist').toString()
  67. .trim()
  68. .split('\n')
  69. .map(function (line) {
  70. return line.trim().split(' ')
  71. })
  72. .reduce(function (result, entry) {
  73. if (!result[entry[0]]) {
  74. result[entry[0]] = []
  75. }
  76. result[entry[0]].push(entry[1])
  77. return result
  78. }, {})
  79. }
  80. function diffBrowsersLists (old, current) {
  81. var browsers = Object.keys(old).concat(
  82. Object.keys(current).filter(function (browser) {
  83. return old[browser] === undefined
  84. })
  85. )
  86. return browsers.map(function (browser) {
  87. var oldVersions = old[browser] || []
  88. var currentVersions = current[browser] || []
  89. var intersection = oldVersions.filter(function (version) {
  90. return currentVersions.indexOf(version) !== -1
  91. })
  92. var addedVersions = currentVersions.filter(function (version) {
  93. return intersection.indexOf(version) === -1
  94. })
  95. var removedVersions = oldVersions.filter(function (version) {
  96. return intersection.indexOf(version) === -1
  97. })
  98. return removedVersions.map(function (version) {
  99. return red('- ' + browser + ' ' + version)
  100. }).concat(addedVersions.map(function (version) {
  101. return green('+ ' + browser + ' ' + version)
  102. }))
  103. })
  104. .reduce(function (result, array) {
  105. return result.concat(array)
  106. }, [])
  107. .join('\n')
  108. }
  109. function updateLockfile (lock, latest) {
  110. if (lock.mode === 'npm') {
  111. var fixed = deletePackage(JSON.parse(lock.content))
  112. return JSON.stringify(fixed, null, ' ')
  113. } else {
  114. var lines = lock.content.split('\n')
  115. var i
  116. /* istanbul ignore if */
  117. if (lock.mode === 'pnpm') {
  118. for (i = 0; i < lines.length; i++) {
  119. if (lines[i].indexOf('caniuse-lite:') >= 0) {
  120. lines[i] = lines[i].replace(/: .*$/, ': ' + latest.version)
  121. } else if (lines[i].indexOf('/caniuse-lite') >= 0) {
  122. lines[i] = lines[i].replace(/\/[^/:]+:/, '/' + latest.version + ':')
  123. for (i = i + 1; i < lines.length; i++) {
  124. if (lines[i].indexOf('integrity: ') !== -1) {
  125. lines[i] = lines[i].replace(
  126. /integrity: .+/, 'integrity: ' + latest.dist.integrity
  127. )
  128. } else if (lines[i].indexOf(' /') !== -1) {
  129. break
  130. }
  131. }
  132. }
  133. }
  134. } else if (lock.mode === 'yarn') {
  135. for (i = 0; i < lines.length; i++) {
  136. if (lines[i].indexOf('caniuse-lite@') !== -1) {
  137. lines[i + 1] = lines[i + 1].replace(
  138. /version "[^"]+"/, 'version "' + latest.version + '"'
  139. )
  140. lines[i + 2] = lines[i + 2].replace(
  141. /resolved "[^"]+"/, 'resolved "' + latest.dist.tarball + '"'
  142. )
  143. lines[i + 3] = lines[i + 3].replace(
  144. /integrity .+/, 'integrity ' + latest.dist.integrity
  145. )
  146. i += 4
  147. }
  148. }
  149. }
  150. return lines.join('\n')
  151. }
  152. }
  153. function deletePackage (node) {
  154. if (node.dependencies) {
  155. delete node.dependencies['caniuse-lite']
  156. for (var i in node.dependencies) {
  157. node.dependencies[i] = deletePackage(node.dependencies[i])
  158. }
  159. }
  160. return node
  161. }
  162. module.exports = function updateDB (print) {
  163. var lock = detectLockfile()
  164. lock.content = fs.readFileSync(lock.file).toString()
  165. var current = getCurrentVersion(lock)
  166. var latest = getLatestInfo(lock)
  167. var browsersListRetrievalError
  168. var oldBrowsersList
  169. try {
  170. oldBrowsersList = getBrowsersList()
  171. } catch (e) {
  172. browsersListRetrievalError = e
  173. }
  174. if (typeof current === 'string') {
  175. print('Current version: ' + bold(red(current)) + '\n')
  176. }
  177. print(
  178. 'New version: ' + bold(green(latest.version)) + '\n' +
  179. 'Removing old caniuse-lite from lock file\n'
  180. )
  181. fs.writeFileSync(lock.file, updateLockfile(lock, latest))
  182. var install = lock.mode === 'yarn' ? 'yarn add -W' : lock.mode + ' install'
  183. print(
  184. 'Installing new caniuse-lite version\n' +
  185. yellow('$ ' + install + ' caniuse-lite') + '\n'
  186. )
  187. try {
  188. childProcess.execSync(install + ' caniuse-lite')
  189. } catch (e) /* istanbul ignore next */ {
  190. print(
  191. red(
  192. '\n' +
  193. e.stack + '\n\n' +
  194. 'Problem with `' + install + ' caniuse-lite` call. ' +
  195. 'Run it manually.\n'
  196. )
  197. )
  198. process.exit(1)
  199. }
  200. var del = lock.mode === 'yarn' ? 'yarn remove -W' : lock.mode + ' uninstall'
  201. print(
  202. 'Cleaning package.json dependencies from caniuse-lite\n' +
  203. yellow('$ ' + del + ' caniuse-lite') + '\n'
  204. )
  205. childProcess.execSync(del + ' caniuse-lite')
  206. print('caniuse-lite has been successfully updated\n')
  207. var currentBrowsersList
  208. if (!browsersListRetrievalError) {
  209. try {
  210. currentBrowsersList = getBrowsersList()
  211. } catch (e) /* istanbul ignore next */ {
  212. browsersListRetrievalError = e
  213. }
  214. }
  215. if (browsersListRetrievalError) {
  216. print(
  217. red(
  218. '\n' +
  219. browsersListRetrievalError.stack + '\n\n' +
  220. 'Problem with browsers list retrieval.\n' +
  221. 'Target browser changes won’t be shown.\n'
  222. )
  223. )
  224. } else {
  225. var targetBrowserChanges = diffBrowsersLists(
  226. oldBrowsersList,
  227. currentBrowsersList
  228. )
  229. if (targetBrowserChanges) {
  230. print('\nTarget browser changes:\n')
  231. print(targetBrowserChanges + '\n')
  232. } else {
  233. print('\n' + green('No target browser changes') + '\n')
  234. }
  235. }
  236. }