update-db.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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 this the right directory to run `npx browserslist --update-db` in?'
  19. )
  20. }
  21. var lockfileNpm = path.join(packageDir, 'package-lock.json')
  22. var lockfileShrinkwrap = path.join(packageDir, 'npm-shrinkwrap.json')
  23. var lockfileYarn = path.join(packageDir, 'yarn.lock')
  24. var lockfilePnpm = path.join(packageDir, 'pnpm-lock.yaml')
  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 if (fs.existsSync(lockfileShrinkwrap)) {
  32. return { mode: 'npm', file: lockfileShrinkwrap }
  33. }
  34. throw new BrowserslistError(
  35. 'No lockfile found. Run "npm install", "yarn install" or "pnpm install"'
  36. )
  37. }
  38. function getLatestInfo (lock) {
  39. if (lock.mode === 'yarn') {
  40. return JSON.parse(
  41. childProcess.execSync('yarn info caniuse-lite --json').toString()
  42. ).data
  43. }
  44. return JSON.parse(
  45. childProcess.execSync('npm show caniuse-lite --json').toString()
  46. )
  47. }
  48. function getBrowsersList () {
  49. return childProcess.execSync('npx browserslist').toString()
  50. .trim()
  51. .split('\n')
  52. .map(function (line) {
  53. return line.trim().split(' ')
  54. })
  55. .reduce(function (result, entry) {
  56. if (!result[entry[0]]) {
  57. result[entry[0]] = []
  58. }
  59. result[entry[0]].push(entry[1])
  60. return result
  61. }, {})
  62. }
  63. function diffBrowsersLists (old, current) {
  64. var browsers = Object.keys(old).concat(
  65. Object.keys(current).filter(function (browser) {
  66. return old[browser] === undefined
  67. })
  68. )
  69. return browsers.map(function (browser) {
  70. var oldVersions = old[browser] || []
  71. var currentVersions = current[browser] || []
  72. var intersection = oldVersions.filter(function (version) {
  73. return currentVersions.indexOf(version) !== -1
  74. })
  75. var addedVersions = currentVersions.filter(function (version) {
  76. return intersection.indexOf(version) === -1
  77. })
  78. var removedVersions = oldVersions.filter(function (version) {
  79. return intersection.indexOf(version) === -1
  80. })
  81. return removedVersions.map(function (version) {
  82. return red('- ' + browser + ' ' + version)
  83. }).concat(addedVersions.map(function (version) {
  84. return green('+ ' + browser + ' ' + version)
  85. }))
  86. })
  87. .reduce(function (result, array) {
  88. return result.concat(array)
  89. }, [])
  90. .join('\n')
  91. }
  92. function updateNpmLockfile (lock, latest) {
  93. var metadata = { latest: latest, versions: [] }
  94. var content = deletePackage(JSON.parse(lock.content), metadata)
  95. metadata.content = JSON.stringify(content, null, ' ')
  96. return metadata
  97. }
  98. function deletePackage (node, metadata) {
  99. if (node.dependencies) {
  100. if (node.dependencies['caniuse-lite']) {
  101. var version = node.dependencies['caniuse-lite'].version
  102. metadata.versions[version] = true
  103. delete node.dependencies['caniuse-lite']
  104. }
  105. for (var i in node.dependencies) {
  106. node.dependencies[i] = deletePackage(node.dependencies[i], metadata)
  107. }
  108. }
  109. return node
  110. }
  111. var yarnVersionRe = new RegExp('version "(.*?)"')
  112. function updateYarnLockfile (lock, latest) {
  113. var blocks = lock.content.split(/(\n{2,})/).map(function (block) {
  114. return block.split('\n')
  115. })
  116. var versions = {}
  117. blocks.forEach(function (lines) {
  118. if (lines[0].indexOf('caniuse-lite@') !== -1) {
  119. var match = yarnVersionRe.exec(lines[1])
  120. versions[match[1]] = true
  121. if (match[1] !== latest.version) {
  122. lines[1] = lines[1].replace(
  123. /version "[^"]+"/, 'version "' + latest.version + '"'
  124. )
  125. lines[2] = lines[2].replace(
  126. /resolved "[^"]+"/, 'resolved "' + latest.dist.tarball + '"'
  127. )
  128. lines[3] = latest.dist.integrity ? lines[3].replace(
  129. /integrity .+/, 'integrity ' + latest.dist.integrity
  130. ) : ''
  131. }
  132. }
  133. })
  134. var content = blocks.map(function (lines) {
  135. return lines.join('\n')
  136. }).join('')
  137. return { content: content, versions: versions }
  138. }
  139. function updatePnpmLockfile (lock, latest) {
  140. var versions = {}
  141. var lines = lock.content.split('\n')
  142. var i
  143. var j
  144. var lineParts
  145. for (i = 0; i < lines.length; i++) {
  146. if (lines[i].indexOf('caniuse-lite:') >= 0) {
  147. lineParts = lines[i].split(/:\s?/, 2)
  148. versions[lineParts[1]] = true
  149. lines[i] = lineParts[0] + ': ' + latest.version
  150. } else if (lines[i].indexOf('/caniuse-lite') >= 0) {
  151. lineParts = lines[i].split(/([/:])/)
  152. for (j = 0; j < lineParts.length; j++) {
  153. if (lineParts[j].indexOf('caniuse-lite') >= 0) {
  154. versions[lineParts[j + 2]] = true
  155. lineParts[j + 2] = latest.version
  156. break
  157. }
  158. }
  159. lines[i] = lineParts.join('')
  160. for (i = i + 1; i < lines.length; i++) {
  161. if (lines[i].indexOf('integrity: ') !== -1) {
  162. lines[i] = lines[i].replace(
  163. /integrity: .+/, 'integrity: ' + latest.dist.integrity
  164. )
  165. } else if (lines[i].indexOf(' /') !== -1) {
  166. break
  167. }
  168. }
  169. }
  170. }
  171. return { content: lines.join('\n'), versions: versions }
  172. }
  173. function updateLockfile (lock, latest) {
  174. lock.content = fs.readFileSync(lock.file).toString()
  175. if (lock.mode === 'npm') {
  176. return updateNpmLockfile(lock, latest)
  177. } else if (lock.mode === 'yarn') {
  178. return updateYarnLockfile(lock, latest)
  179. }
  180. return updatePnpmLockfile(lock, latest)
  181. }
  182. module.exports = function updateDB (print) {
  183. var lock = detectLockfile()
  184. var latest = getLatestInfo(lock)
  185. var browsersListRetrievalError
  186. var oldBrowsersList
  187. try {
  188. oldBrowsersList = getBrowsersList()
  189. } catch (e) {
  190. browsersListRetrievalError = e
  191. }
  192. print(
  193. 'Latest version: ' + bold(green(latest.version)) + '\n'
  194. )
  195. var lockfileData = updateLockfile(lock, latest)
  196. var caniuseVersions = Object.keys(lockfileData.versions).sort()
  197. if (caniuseVersions.length === 1 &&
  198. caniuseVersions[0] === latest.version) {
  199. print(
  200. 'Installed version: ' + bold(green(latest.version)) + '\n' +
  201. bold(green('caniuse-lite is up to date')) + '\n'
  202. )
  203. return
  204. }
  205. if (caniuseVersions.length === 0) {
  206. caniuseVersions[0] = 'none'
  207. }
  208. print(
  209. 'Installed version' +
  210. (caniuseVersions.length === 1 ? ': ' : 's: ') +
  211. bold(red(caniuseVersions.join(', '))) +
  212. '\n' +
  213. 'Removing old caniuse-lite from lock file\n'
  214. )
  215. fs.writeFileSync(lock.file, lockfileData.content)
  216. var install = lock.mode === 'yarn' ? 'yarn add -W' : lock.mode + ' install'
  217. print(
  218. 'Installing new caniuse-lite version\n' +
  219. yellow('$ ' + install + ' caniuse-lite') + '\n'
  220. )
  221. try {
  222. childProcess.execSync(install + ' caniuse-lite')
  223. } catch (e) /* istanbul ignore next */ {
  224. print(
  225. red(
  226. '\n' +
  227. e.stack + '\n\n' +
  228. 'Problem with `' + install + ' caniuse-lite` call. ' +
  229. 'Run it manually.\n'
  230. )
  231. )
  232. process.exit(1)
  233. }
  234. var del = lock.mode === 'yarn' ? 'yarn remove -W' : lock.mode + ' uninstall'
  235. print(
  236. 'Cleaning package.json dependencies from caniuse-lite\n' +
  237. yellow('$ ' + del + ' caniuse-lite') + '\n'
  238. )
  239. childProcess.execSync(del + ' caniuse-lite')
  240. print('caniuse-lite has been successfully updated\n')
  241. var currentBrowsersList
  242. if (!browsersListRetrievalError) {
  243. try {
  244. currentBrowsersList = getBrowsersList()
  245. } catch (e) /* istanbul ignore next */ {
  246. browsersListRetrievalError = e
  247. }
  248. }
  249. if (browsersListRetrievalError) {
  250. print(
  251. red(
  252. '\n' +
  253. browsersListRetrievalError.stack + '\n\n' +
  254. 'Problem with browser list retrieval.\n' +
  255. 'Target browser changes won’t be shown.\n'
  256. )
  257. )
  258. } else {
  259. var targetBrowserChanges = diffBrowsersLists(
  260. oldBrowsersList,
  261. currentBrowsersList
  262. )
  263. if (targetBrowserChanges) {
  264. print('\nTarget browser changes:\n')
  265. print(targetBrowserChanges + '\n')
  266. } else {
  267. print('\n' + green('No target browser changes') + '\n')
  268. }
  269. }
  270. }