var childProcess = require('child_process') var colorette = require('colorette') var escalade = require('escalade/sync') var path = require('path') var fs = require('fs') var BrowserslistError = require('./error') var red = colorette.red var bold = colorette.bold var green = colorette.green var yellow = colorette.yellow function detectLockfile () { var packageDir = escalade('.', function (dir, names) { return names.indexOf('package.json') !== -1 ? dir : '' }) if (!packageDir) { throw new BrowserslistError( 'Cannot find package.json. ' + 'Is it a right project to run npx browserslist --update-db?' ) } var lockfileNpm = path.join(packageDir, 'package-lock.json') var lockfileYarn = path.join(packageDir, 'yarn.lock') var lockfilePnpm = path.join(packageDir, 'pnpm-lock.yaml') /* istanbul ignore next */ if (fs.existsSync(lockfilePnpm)) { return { mode: 'pnpm', file: lockfilePnpm } } else if (fs.existsSync(lockfileNpm)) { return { mode: 'npm', file: lockfileNpm } } else if (fs.existsSync(lockfileYarn)) { return { mode: 'yarn', file: lockfileYarn } } else { throw new BrowserslistError( 'No lockfile found. Run "npm install", "yarn install" or "pnpm install"' ) } } function getCurrentVersion (lock) { var match /* istanbul ignore if */ if (lock.mode === 'pnpm') { match = /\/caniuse-lite\/([^:]+):/.exec(lock.content) if (match[1]) return match[1] } else if (lock.mode === 'npm') { var dependencies = JSON.parse(lock.content).dependencies if (dependencies && dependencies['caniuse-lite']) { return dependencies['caniuse-lite'].version } } else if (lock.mode === 'yarn') { match = /caniuse-lite@[^:]+:\r?\n\s+version\s+"([^"]+)"/.exec(lock.content) if (match[1]) return match[1] } return null } function getLatestInfo (lock) { if (lock.mode !== 'yarn') { return JSON.parse( childProcess.execSync('npm show caniuse-lite --json').toString() ) } else { return JSON.parse( childProcess.execSync('yarn info caniuse-lite --json').toString() ).data } } function getBrowsersList () { return childProcess.execSync('npx browserslist').toString() .trim() .split('\n') .map(function (line) { return line.trim().split(' ') }) .reduce(function (result, entry) { if (!result[entry[0]]) { result[entry[0]] = [] } result[entry[0]].push(entry[1]) return result }, {}) } function diffBrowsersLists (old, current) { var browsers = Object.keys(old).concat( Object.keys(current).filter(function (browser) { return old[browser] === undefined }) ) return browsers.map(function (browser) { var oldVersions = old[browser] || [] var currentVersions = current[browser] || [] var intersection = oldVersions.filter(function (version) { return currentVersions.indexOf(version) !== -1 }) var addedVersions = currentVersions.filter(function (version) { return intersection.indexOf(version) === -1 }) var removedVersions = oldVersions.filter(function (version) { return intersection.indexOf(version) === -1 }) return removedVersions.map(function (version) { return red('- ' + browser + ' ' + version) }).concat(addedVersions.map(function (version) { return green('+ ' + browser + ' ' + version) })) }) .reduce(function (result, array) { return result.concat(array) }, []) .join('\n') } function updateLockfile (lock, latest) { if (lock.mode === 'npm') { var fixed = deletePackage(JSON.parse(lock.content)) return JSON.stringify(fixed, null, ' ') } else { var lines = lock.content.split('\n') var i /* istanbul ignore if */ if (lock.mode === 'pnpm') { for (i = 0; i < lines.length; i++) { if (lines[i].indexOf('caniuse-lite:') >= 0) { lines[i] = lines[i].replace(/: .*$/, ': ' + latest.version) } else if (lines[i].indexOf('/caniuse-lite') >= 0) { lines[i] = lines[i].replace(/\/[^/:]+:/, '/' + latest.version + ':') for (i = i + 1; i < lines.length; i++) { if (lines[i].indexOf('integrity: ') !== -1) { lines[i] = lines[i].replace( /integrity: .+/, 'integrity: ' + latest.dist.integrity ) } else if (lines[i].indexOf(' /') !== -1) { break } } } } } else if (lock.mode === 'yarn') { for (i = 0; i < lines.length; i++) { if (lines[i].indexOf('caniuse-lite@') !== -1) { lines[i + 1] = lines[i + 1].replace( /version "[^"]+"/, 'version "' + latest.version + '"' ) lines[i + 2] = lines[i + 2].replace( /resolved "[^"]+"/, 'resolved "' + latest.dist.tarball + '"' ) lines[i + 3] = lines[i + 3].replace( /integrity .+/, 'integrity ' + latest.dist.integrity ) i += 4 } } } return lines.join('\n') } } function deletePackage (node) { if (node.dependencies) { delete node.dependencies['caniuse-lite'] for (var i in node.dependencies) { node.dependencies[i] = deletePackage(node.dependencies[i]) } } return node } module.exports = function updateDB (print) { var lock = detectLockfile() lock.content = fs.readFileSync(lock.file).toString() var current = getCurrentVersion(lock) var latest = getLatestInfo(lock) var browsersListRetrievalError var oldBrowsersList try { oldBrowsersList = getBrowsersList() } catch (e) { browsersListRetrievalError = e } if (typeof current === 'string') { print('Current version: ' + bold(red(current)) + '\n') } print( 'New version: ' + bold(green(latest.version)) + '\n' + 'Removing old caniuse-lite from lock file\n' ) fs.writeFileSync(lock.file, updateLockfile(lock, latest)) var install = lock.mode === 'yarn' ? 'yarn add -W' : lock.mode + ' install' print( 'Installing new caniuse-lite version\n' + yellow('$ ' + install + ' caniuse-lite') + '\n' ) try { childProcess.execSync(install + ' caniuse-lite') } catch (e) /* istanbul ignore next */ { print( red( '\n' + e.stack + '\n\n' + 'Problem with `' + install + ' caniuse-lite` call. ' + 'Run it manually.\n' ) ) process.exit(1) } var del = lock.mode === 'yarn' ? 'yarn remove -W' : lock.mode + ' uninstall' print( 'Cleaning package.json dependencies from caniuse-lite\n' + yellow('$ ' + del + ' caniuse-lite') + '\n' ) childProcess.execSync(del + ' caniuse-lite') print('caniuse-lite has been successfully updated\n') var currentBrowsersList if (!browsersListRetrievalError) { try { currentBrowsersList = getBrowsersList() } catch (e) /* istanbul ignore next */ { browsersListRetrievalError = e } } if (browsersListRetrievalError) { print( red( '\n' + browsersListRetrievalError.stack + '\n\n' + 'Problem with browsers list retrieval.\n' + 'Target browser changes won’t be shown.\n' ) ) } else { var targetBrowserChanges = diffBrowsersLists( oldBrowsersList, currentBrowsersList ) if (targetBrowserChanges) { print('\nTarget browser changes:\n') print(targetBrowserChanges + '\n') } else { print('\n' + green('No target browser changes') + '\n') } } }