update-db.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. var childProcess = require('child_process')
  2. var escalade = require('escalade/sync')
  3. var pico = require('picocolors')
  4. var path = require('path')
  5. var fs = require('fs')
  6. var BrowserslistError = require('./error')
  7. function detectLockfile() {
  8. var packageDir = escalade('.', function (dir, names) {
  9. return names.indexOf('package.json') !== -1 ? dir : ''
  10. })
  11. if (!packageDir) {
  12. throw new BrowserslistError(
  13. 'Cannot find package.json. ' +
  14. 'Is this the right directory to run `npx browserslist --update-db` in?'
  15. )
  16. }
  17. var lockfileNpm = path.join(packageDir, 'package-lock.json')
  18. var lockfileShrinkwrap = path.join(packageDir, 'npm-shrinkwrap.json')
  19. var lockfileYarn = path.join(packageDir, 'yarn.lock')
  20. var lockfilePnpm = path.join(packageDir, 'pnpm-lock.yaml')
  21. if (fs.existsSync(lockfilePnpm)) {
  22. return { mode: 'pnpm', file: lockfilePnpm }
  23. } else if (fs.existsSync(lockfileNpm)) {
  24. return { mode: 'npm', file: lockfileNpm }
  25. } else if (fs.existsSync(lockfileYarn)) {
  26. var lock = { mode: 'yarn', file: lockfileYarn }
  27. lock.content = fs.readFileSync(lock.file).toString()
  28. lock.version = /# yarn lockfile v1/.test(lock.content) ? 1 : 2
  29. return lock
  30. } else if (fs.existsSync(lockfileShrinkwrap)) {
  31. return { mode: 'npm', file: lockfileShrinkwrap }
  32. }
  33. throw new BrowserslistError(
  34. 'No lockfile found. Run "npm install", "yarn install" or "pnpm install"'
  35. )
  36. }
  37. function getLatestInfo(lock) {
  38. if (lock.mode === 'yarn') {
  39. if (lock.version === 1) {
  40. return JSON.parse(
  41. childProcess.execSync('yarn info caniuse-lite --json').toString()
  42. ).data
  43. } else {
  44. return JSON.parse(
  45. childProcess.execSync('yarn npm info caniuse-lite --json').toString()
  46. )
  47. }
  48. }
  49. return JSON.parse(
  50. childProcess.execSync('npm show caniuse-lite --json').toString()
  51. )
  52. }
  53. function getBrowsersList() {
  54. return childProcess
  55. .execSync('npx browserslist')
  56. .toString()
  57. .trim()
  58. .split('\n')
  59. .map(function (line) {
  60. return line.trim().split(' ')
  61. })
  62. .reduce(function (result, entry) {
  63. if (!result[entry[0]]) {
  64. result[entry[0]] = []
  65. }
  66. result[entry[0]].push(entry[1])
  67. return result
  68. }, {})
  69. }
  70. function diffBrowsersLists(old, current) {
  71. var browsers = Object.keys(old).concat(
  72. Object.keys(current).filter(function (browser) {
  73. return old[browser] === undefined
  74. })
  75. )
  76. return browsers
  77. .map(function (browser) {
  78. var oldVersions = old[browser] || []
  79. var currentVersions = current[browser] || []
  80. var intersection = oldVersions.filter(function (version) {
  81. return currentVersions.indexOf(version) !== -1
  82. })
  83. var addedVersions = currentVersions.filter(function (version) {
  84. return intersection.indexOf(version) === -1
  85. })
  86. var removedVersions = oldVersions.filter(function (version) {
  87. return intersection.indexOf(version) === -1
  88. })
  89. return removedVersions
  90. .map(function (version) {
  91. return pico.red('- ' + browser + ' ' + version)
  92. })
  93. .concat(
  94. addedVersions.map(function (version) {
  95. return pico.green('+ ' + browser + ' ' + version)
  96. })
  97. )
  98. })
  99. .reduce(function (result, array) {
  100. return result.concat(array)
  101. }, [])
  102. .join('\n')
  103. }
  104. function updateNpmLockfile(lock, latest) {
  105. var metadata = { latest: latest, versions: [] }
  106. var content = deletePackage(JSON.parse(lock.content), metadata)
  107. metadata.content = JSON.stringify(content, null, ' ')
  108. return metadata
  109. }
  110. function deletePackage(node, metadata) {
  111. if (node.dependencies) {
  112. if (node.dependencies['caniuse-lite']) {
  113. var version = node.dependencies['caniuse-lite'].version
  114. metadata.versions[version] = true
  115. delete node.dependencies['caniuse-lite']
  116. }
  117. for (var i in node.dependencies) {
  118. node.dependencies[i] = deletePackage(node.dependencies[i], metadata)
  119. }
  120. }
  121. return node
  122. }
  123. var yarnVersionRe = /version "(.*?)"/
  124. function updateYarnLockfile(lock, latest) {
  125. var blocks = lock.content.split(/(\n{2,})/).map(function (block) {
  126. return block.split('\n')
  127. })
  128. var versions = {}
  129. blocks.forEach(function (lines) {
  130. if (lines[0].indexOf('caniuse-lite@') !== -1) {
  131. var match = yarnVersionRe.exec(lines[1])
  132. versions[match[1]] = true
  133. if (match[1] !== latest.version) {
  134. lines[1] = lines[1].replace(
  135. /version "[^"]+"/,
  136. 'version "' + latest.version + '"'
  137. )
  138. lines[2] = lines[2].replace(
  139. /resolved "[^"]+"/,
  140. 'resolved "' + latest.dist.tarball + '"'
  141. )
  142. if (lines.length === 4) {
  143. lines[3] = latest.dist.integrity
  144. ? lines[3].replace(
  145. /integrity .+/,
  146. 'integrity ' + latest.dist.integrity
  147. )
  148. : ''
  149. }
  150. }
  151. }
  152. })
  153. var content = blocks
  154. .map(function (lines) {
  155. return lines.join('\n')
  156. })
  157. .join('')
  158. return { content: content, versions: versions }
  159. }
  160. function updatePnpmLockfile(lock, latest) {
  161. var versions = {}
  162. var lines = lock.content.split('\n')
  163. var i
  164. var j
  165. var lineParts
  166. for (i = 0; i < lines.length; i++) {
  167. if (lines[i].indexOf('caniuse-lite:') >= 0) {
  168. lineParts = lines[i].split(/:\s?/, 2)
  169. if (lineParts[1].indexOf('/') >= 0) {
  170. /* c8 ignore start */
  171. var sublineParts = lineParts[1].split(/([/:])/)
  172. for (j = 0; j < sublineParts.length; j++) {
  173. if (sublineParts[j].indexOf('caniuse-lite') >= 0) {
  174. versions[sublineParts[j + 2]] = true
  175. sublineParts[j + 2] = latest.version
  176. break
  177. }
  178. }
  179. lineParts[1] = sublineParts.join('')
  180. /* c8 ignore stop */
  181. } else {
  182. versions[lineParts[1]] = true
  183. }
  184. lines[i] = lineParts[0] + ': ' + latest.version
  185. } else if (lines[i].indexOf('/caniuse-lite') >= 0) {
  186. lineParts = lines[i].split(/([/:])/)
  187. for (j = 0; j < lineParts.length; j++) {
  188. if (lineParts[j].indexOf('caniuse-lite') >= 0) {
  189. versions[lineParts[j + 2]] = true
  190. lineParts[j + 2] = latest.version
  191. break
  192. }
  193. }
  194. lines[i] = lineParts.join('')
  195. for (i = i + 1; i < lines.length; i++) {
  196. if (lines[i].indexOf('integrity: ') !== -1) {
  197. lines[i] = lines[i].replace(
  198. /integrity: .+/,
  199. 'integrity: ' + latest.dist.integrity
  200. )
  201. } else if (lines[i].indexOf(' /') !== -1) {
  202. break
  203. }
  204. }
  205. }
  206. }
  207. return { content: lines.join('\n'), versions: versions }
  208. }
  209. function updateLockfile(lock, latest) {
  210. if (!lock.content) lock.content = fs.readFileSync(lock.file).toString()
  211. if (lock.mode === 'yarn') {
  212. return updateYarnLockfile(lock, latest)
  213. } else if (lock.mode === 'pnpm') {
  214. return updatePnpmLockfile(lock, latest)
  215. } else {
  216. return updateNpmLockfile(lock, latest)
  217. }
  218. }
  219. function updatePackageManually(print, lock, latest) {
  220. var lockfileData = updateLockfile(lock, latest)
  221. var caniuseVersions = Object.keys(lockfileData.versions).sort()
  222. if (caniuseVersions.length === 1 && caniuseVersions[0] === latest.version) {
  223. print(
  224. 'Installed version: ' +
  225. pico.bold(pico.green(latest.version)) +
  226. '\n' +
  227. pico.bold(pico.green('caniuse-lite is up to date')) +
  228. '\n'
  229. )
  230. return
  231. }
  232. if (caniuseVersions.length === 0) {
  233. caniuseVersions[0] = 'none'
  234. }
  235. print(
  236. 'Installed version' +
  237. (caniuseVersions.length === 1 ? ': ' : 's: ') +
  238. pico.bold(pico.red(caniuseVersions.join(', '))) +
  239. '\n' +
  240. 'Removing old caniuse-lite from lock file\n'
  241. )
  242. fs.writeFileSync(lock.file, lockfileData.content)
  243. var install = lock.mode === 'yarn' ? 'yarn add -W' : lock.mode + ' install'
  244. print(
  245. 'Installing new caniuse-lite version\n' +
  246. pico.yellow('$ ' + install + ' caniuse-lite') +
  247. '\n'
  248. )
  249. try {
  250. childProcess.execSync(install + ' caniuse-lite')
  251. } catch (e) /* c8 ignore start */ {
  252. print(
  253. pico.red(
  254. '\n' +
  255. e.stack +
  256. '\n\n' +
  257. 'Problem with `' +
  258. install +
  259. ' caniuse-lite` call. ' +
  260. 'Run it manually.\n'
  261. )
  262. )
  263. process.exit(1)
  264. } /* c8 ignore end */
  265. var del = lock.mode === 'yarn' ? 'yarn remove -W' : lock.mode + ' uninstall'
  266. print(
  267. 'Cleaning package.json dependencies from caniuse-lite\n' +
  268. pico.yellow('$ ' + del + ' caniuse-lite') +
  269. '\n'
  270. )
  271. childProcess.execSync(del + ' caniuse-lite')
  272. }
  273. function updateWith(print, cmd) {
  274. print('Updating caniuse-lite version\n' + pico.yellow('$ ' + cmd) + '\n')
  275. try {
  276. childProcess.execSync(cmd)
  277. } catch (e) /* c8 ignore start */ {
  278. print(pico.red(e.stdout.toString()))
  279. print(
  280. pico.red(
  281. '\n' +
  282. e.stack +
  283. '\n\n' +
  284. 'Problem with `' +
  285. cmd +
  286. '` call. ' +
  287. 'Run it manually.\n'
  288. )
  289. )
  290. process.exit(1)
  291. } /* c8 ignore end */
  292. }
  293. module.exports = function updateDB(print) {
  294. var lock = detectLockfile()
  295. var latest = getLatestInfo(lock)
  296. var browsersListRetrievalError
  297. var oldBrowsersList
  298. try {
  299. oldBrowsersList = getBrowsersList()
  300. } catch (e) {
  301. browsersListRetrievalError = e
  302. }
  303. print('Latest version: ' + pico.bold(pico.green(latest.version)) + '\n')
  304. if (lock.mode === 'yarn' && lock.version !== 1) {
  305. updateWith(print, 'yarn up -R caniuse-lite')
  306. } else {
  307. updatePackageManually(print, lock, latest)
  308. }
  309. print('caniuse-lite has been successfully updated\n')
  310. var currentBrowsersList
  311. if (!browsersListRetrievalError) {
  312. try {
  313. currentBrowsersList = getBrowsersList()
  314. } catch (e) /* c8 ignore start */ {
  315. browsersListRetrievalError = e
  316. } /* c8 ignore end */
  317. }
  318. if (browsersListRetrievalError) {
  319. print(
  320. pico.red(
  321. '\n' +
  322. browsersListRetrievalError.stack +
  323. '\n\n' +
  324. 'Problem with browser list retrieval.\n' +
  325. 'Target browser changes won’t be shown.\n'
  326. )
  327. )
  328. } else {
  329. var targetBrowserChanges = diffBrowsersLists(
  330. oldBrowsersList,
  331. currentBrowsersList
  332. )
  333. if (targetBrowserChanges) {
  334. print('\nTarget browser changes:\n')
  335. print(targetBrowserChanges + '\n')
  336. } else {
  337. print('\n' + pico.green('No target browser changes') + '\n')
  338. }
  339. }
  340. }