first commit

This commit is contained in:
Artem Anufrij
2026-10-07 18:56:27 +02:00
commit 2a8a60b725
7768 changed files with 1550851 additions and 0 deletions
+614
View File
@@ -0,0 +1,614 @@
let { execFileSync, execSync } = require('child_process')
let escalade = require('escalade/sync')
let { existsSync, readFileSync, unlinkSync, writeFileSync } = require('fs')
let { dirname, join } = require('path')
let pico = require('picocolors')
let { detectEOL, detectIndent } = require('./utils')
function BrowserslistUpdateError(message) {
this.name = 'BrowserslistUpdateError'
this.message = message
this.browserslist = true
if (Error.captureStackTrace) {
Error.captureStackTrace(this, BrowserslistUpdateError)
}
}
BrowserslistUpdateError.prototype = Error.prototype
// Check if HADOOP_HOME is set to determine if this is running in a Hadoop environment
const YARN_CMD = process.env.HADOOP_HOME ? 'yarnpkg' : 'yarn'
/* c8 ignore next 3 */
function defaultPrint(str) {
process.stdout.write(str)
}
function detectLockfile() {
let packageDir = escalade('.', (dir, names) => {
return names.indexOf('package.json') !== -1 ? dir : ''
})
if (!packageDir) {
throw new BrowserslistUpdateError(
'Cannot find package.json. ' +
'Is this the right directory to run `npx update-browserslist-db` in?'
)
}
let lockfileNpm = join(packageDir, 'package-lock.json')
let lockfileShrinkwrap = join(packageDir, 'npm-shrinkwrap.json')
let lockfileYarn = join(packageDir, 'yarn.lock')
let lockfilePnpm = join(packageDir, 'pnpm-lock.yaml')
let lockfileBun = join(packageDir, 'bun.lock')
let lockfileBunBinary = join(packageDir, 'bun.lockb')
let lockfileDeno = join(packageDir, 'deno.lock')
if (existsSync(lockfilePnpm)) {
return { file: lockfilePnpm, mode: 'pnpm' }
} else if (existsSync(lockfileBun) || existsSync(lockfileBunBinary)) {
return { file: lockfileBun, mode: 'bun' }
} else if (existsSync(lockfileNpm)) {
return { file: lockfileNpm, mode: 'npm' }
} else if (existsSync(lockfileYarn)) {
let lock = { file: lockfileYarn, mode: 'yarn' }
lock.content = readFileSync(lock.file).toString()
lock.version = /# yarn lockfile v1/.test(lock.content) ? 1 : 2
return lock
} else if (existsSync(lockfileShrinkwrap)) {
return { file: lockfileShrinkwrap, mode: 'npm' }
} else if (existsSync(lockfileDeno)) {
return { file: lockfileDeno, mode: 'deno' }
}
throw new BrowserslistUpdateError(
'No lockfile found. Run "npm install", "yarn install", "pnpm install", "bun install" or "deno install"'
)
}
function getLatestInfo(lock) {
try {
if (lock.mode === 'yarn') {
if (lock.version === 1) {
return JSON.parse(
execSync(YARN_CMD + ' info caniuse-lite --json').toString()
).data
} else {
return JSON.parse(
execSync(YARN_CMD + ' npm info caniuse-lite --json').toString()
)
}
}
if (lock.mode === 'pnpm') {
return JSON.parse(execSync('pnpm info caniuse-lite --json').toString())
}
if (lock.mode === 'bun') {
return JSON.parse(execSync('bun info caniuse-lite --json').toString())
}
if (lock.mode === 'deno') {
let result = JSON.parse(
execSync(
'deno run -A npm:npm show caniuse-lite version --json'
).toString()
)
return { version: Array.isArray(result) ? result[0] : result }
}
return JSON.parse(execSync('npm show caniuse-lite --json').toString())
} catch (e) {
if (e.code === 'ENOENT' || e.status === 127) {
throw new BrowserslistUpdateError(
'Cannot find ' +
lock.mode +
' binary in PATH. Please install ' +
lock.mode +
' or run updates manually.'
)
}
throw e
}
}
function getBrowsers() {
let browserslist = require('browserslist')
return browserslist().reduce((result, entry) => {
if (!result[entry[0]]) {
result[entry[0]] = []
}
result[entry[0]].push(entry[1])
return result
}, {})
}
function diffBrowsers(old, current) {
let browsers = Object.keys(old).concat(
Object.keys(current).filter(browser => old[browser] === undefined)
)
return browsers
.map(browser => {
let oldVersions = old[browser] || []
let currentVersions = current[browser] || []
let common = oldVersions.filter(v => currentVersions.includes(v))
let added = currentVersions.filter(v => !common.includes(v))
let removed = oldVersions.filter(v => !common.includes(v))
return removed
.map(v => pico.red('- ' + browser + ' ' + v))
.concat(added.map(v => pico.green('+ ' + browser + ' ' + v)))
})
.reduce((result, array) => result.concat(array), [])
.join('\n')
}
function updateNpmLockfile(lock, latest) {
let metadata = { latest, versions: [] }
let content = deletePackage(JSON.parse(lock.content), metadata)
metadata.content = JSON.stringify(content, null, detectIndent(lock.content))
return metadata
}
function deletePackage(node, metadata) {
if (node.dependencies) {
if (node.dependencies['caniuse-lite']) {
let version = node.dependencies['caniuse-lite'].version
metadata.versions[version] = true
delete node.dependencies['caniuse-lite']
}
for (let i in node.dependencies) {
node.dependencies[i] = deletePackage(node.dependencies[i], metadata)
}
}
if (node.packages) {
for (let path in node.packages) {
if (path.endsWith('/caniuse-lite')) {
metadata.versions[node.packages[path].version] = true
delete node.packages[path]
}
}
}
return node
}
let yarnVersionRe = /version "(.*?)"/
function updateYarnLockfile(lock, latest) {
let blocks = lock.content.split(/(\n{2,})/).map(block => {
return block.split('\n')
})
let versions = {}
blocks.forEach(lines => {
if (lines[0].indexOf('caniuse-lite@') !== -1) {
let match = yarnVersionRe.exec(lines[1])
versions[match[1]] = true
if (match[1] !== latest.version) {
lines[1] = lines[1].replace(
/version "[^"]+"/,
'version "' + latest.version + '"'
)
lines[2] = lines[2].replace(
/resolved "[^"]+"/,
'resolved "' + latest.dist.tarball + '"'
)
if (lines.length === 4) {
lines[3] = latest.dist.integrity
? lines[3].replace(
/integrity .+/,
'integrity ' + latest.dist.integrity
)
: ''
}
}
}
})
let content = blocks.map(lines => lines.join('\n')).join('')
return { content, versions }
}
function updateLockfile(lock, latest) {
if (!lock.content) lock.content = readFileSync(lock.file).toString()
let updatedLockFile
if (lock.mode === 'yarn') {
updatedLockFile = updateYarnLockfile(lock, latest)
} else {
updatedLockFile = updateNpmLockfile(lock, latest)
}
updatedLockFile.content = updatedLockFile.content.replace(
/\n/g,
detectEOL(lock.content)
)
return updatedLockFile
}
function updatePackageManually(print, lock, latest) {
let lockfileData = updateLockfile(lock, latest)
let caniuseVersions = Object.keys(lockfileData.versions).sort()
if (caniuseVersions.length === 1 && caniuseVersions[0] === latest.version) {
print(
'Installed version: ' +
pico.bold(pico.green(caniuseVersions[0])) +
'\n' +
pico.bold(pico.green('caniuse-lite is up to date')) +
'\n'
)
return
}
if (caniuseVersions.length === 0) {
caniuseVersions[0] = 'none'
}
print(
'Installed version' +
(caniuseVersions.length === 1 ? ': ' : 's: ') +
pico.bold(pico.red(caniuseVersions.join(', '))) +
'\n' +
'Removing old caniuse-lite from lock file\n'
)
writeFileSync(lock.file, lockfileData.content)
let install =
lock.mode === 'yarn' ? YARN_CMD + ' add -W' : lock.mode + ' install'
print(
'Installing new caniuse-lite version\n' +
pico.yellow('$ ' + install + ' caniuse-lite baseline-browser-mapping') +
'\n'
)
try {
execSync(install + ' caniuse-lite baseline-browser-mapping')
} catch (e) /* c8 ignore start */ {
print(
pico.red(
'\n' +
e.stack +
'\n\n' +
'Problem with `' +
install +
' caniuse-lite` call. ' +
'Run it manually.\n'
)
)
process.exit(1)
} /* c8 ignore end */
let del =
lock.mode === 'yarn' ? YARN_CMD + ' remove -W' : lock.mode + ' uninstall'
print(
'Cleaning package.json dependencies from caniuse-lite\n' +
pico.yellow('$ ' + del + ' caniuse-lite baseline-browser-mapping') +
'\n'
)
execSync(del + ' caniuse-lite baseline-browser-mapping')
}
/**
* Bun cannot update a transitive dependency by name: `bun update caniuse-lite`
* adds caniuse-lite to package.json as a new direct dependency at the latest
* version and leaves every nested copy - the ones that actually get resolved at
* runtime - on the old version. A temporary `overrides` entry reaches those,
* and the resolutions survive once it is taken back out.
*/
function updateBun(print, lock, latest) {
let pkgFile = join(dirname(lock.file), 'package.json')
let original = readFileSync(pkgFile)
let pkg = JSON.parse(original.toString())
let withOverride = {
...pkg,
overrides: {
...pkg.overrides,
'baseline-browser-mapping': 'latest',
'caniuse-lite': latest.version
}
}
// The file is restored byte for byte below, so its formatting does not matter.
writeFileSync(pkgFile, JSON.stringify(withOverride, null, 2) + '\n')
print(
'Updating caniuse-lite version\n' +
pico.yellow('$ bun install') +
' (with a temporary caniuse-lite override)\n'
)
try {
execSync('bun install')
} catch (e) /* c8 ignore start */ {
writeFileSync(pkgFile, original)
print(pico.red(e.stdout.toString()))
print(
pico.red(
'\n' +
e.stack +
'\n\n' +
'Problem with `bun install` call. ' +
'Run it manually.\n'
)
)
process.exit(1)
} /* c8 ignore end */
writeFileSync(pkgFile, original)
updateWith(print, 'bun install', 'Removing the temporary override')
}
function commandErrorText(error) {
return [error.message, error.stdout, error.stderr]
.filter(Boolean)
.map(i => i.toString())
.join('\n')
}
function getPnpmConfig(name) {
let output = execFileSync('pnpm', [
'config',
'get',
name,
'--json'
]).toString()
return output.trim() === 'undefined' ? undefined : JSON.parse(output)
}
function getPnpmPackageTimes(name) {
let times
try {
times = JSON.parse(
execFileSync('pnpm', ['info', name, 'time', '--json']).toString()
)
} catch (error) /* c8 ignore start */ {
throw new BrowserslistUpdateError(
'Cannot read publication dates of ' +
name +
' from pnpm. Refusing to bypass pnpm minimumReleaseAge.\n' +
commandErrorText(error)
)
} /* c8 ignore end */
if (!times || typeof times !== 'object' || Array.isArray(times)) {
/* c8 ignore next 6 */
throw new BrowserslistUpdateError(
'pnpm returned no publication dates for ' +
name +
'. Refusing to bypass pnpm minimumReleaseAge.'
)
}
return times
}
function getMatureVersion(name, minimumReleaseAge) {
let cutoff = Date.now() - minimumReleaseAge * 60 * 1000
let times = getPnpmPackageTimes(name)
let mature, matureTime
for (let version of Object.keys(times)) {
let time = Date.parse(times[version])
if (!/^\d+\.\d+\.\d+$/.test(version) || !(time <= cutoff)) continue
if (matureTime === undefined || time > matureTime) {
mature = version
matureTime = time
}
}
if (!mature) {
throw new BrowserslistUpdateError(
'Cannot find a ' +
name +
' version old enough for pnpm minimumReleaseAge (' +
minimumReleaseAge +
' minutes). Wait for a release to pass the age gate and try again.'
)
}
return mature
}
function restoreFile(file, original) {
if (original === undefined) {
if (existsSync(file)) unlinkSync(file)
} else {
writeFileSync(file, original)
}
}
function updatePnpmStrict(print, lock, packages) {
let minimumReleaseAge = getPnpmConfig('minimumReleaseAge')
if (
typeof minimumReleaseAge !== 'number' ||
!Number.isFinite(minimumReleaseAge) ||
minimumReleaseAge <= 0
) {
throw new BrowserslistUpdateError(
'Cannot read a valid pnpm minimumReleaseAge. Refusing to disable pnpm security checks.'
)
}
let versions = {}
for (let name of packages) {
versions[name] = getMatureVersion(name, minimumReleaseAge)
}
let originalOverrides = getPnpmConfig('overrides')
if (
originalOverrides === undefined ||
originalOverrides === null ||
Array.isArray(originalOverrides) ||
typeof originalOverrides !== 'object'
) {
originalOverrides = {}
}
let temporaryOverrides = { ...originalOverrides, ...versions }
let packageDir = dirname(lock.file)
let workspaceFile = join(packageDir, 'pnpm-workspace.yaml')
let originalWorkspace = existsSync(workspaceFile)
? readFileSync(workspaceFile)
: undefined
let originalLockfile = readFileSync(lock.file)
let command = 'pnpm config set overrides'
print(
'Strict pnpm minimumReleaseAge detected\n' +
'Latest policy-compliant: ' +
pico.bold(pico.green(versions['caniuse-lite'])) +
'\n' +
Object.keys(versions)
.filter(name => name !== 'caniuse-lite')
.map(name => ' ' + name + ': ' + versions[name] + '\n')
.join('')
)
try {
execFileSync(
'pnpm',
[
'config',
'set',
'overrides',
JSON.stringify(temporaryOverrides),
'--json',
'--location',
'project'
],
{ cwd: packageDir }
)
command = 'pnpm install --lockfile-only'
print(
'Updating caniuse-lite version\n' +
pico.yellow('$ ' + command) +
' (with temporary policy-compliant overrides)\n'
)
execFileSync('pnpm', ['install', '--lockfile-only'], { cwd: packageDir })
restoreFile(workspaceFile, originalWorkspace)
// The lockfile no longer matches the restored overrides, and pnpm
// freezes the lockfile by default in CI
command = 'pnpm install --no-frozen-lockfile'
print(
'Removing temporary pnpm overrides\n' + pico.yellow('$ ' + command) + '\n'
)
execFileSync('pnpm', ['install', '--no-frozen-lockfile'], {
cwd: packageDir
})
} catch (error) /* c8 ignore start */ {
restoreFile(workspaceFile, originalWorkspace)
writeFileSync(lock.file, originalLockfile)
print(pico.red(commandErrorText(error)))
print(
pico.red('\n\nProblem with `' + command + '` call. Run it manually.\n')
)
process.exit(1)
} /* c8 ignore end */
}
function updatePnpm(print, lock) {
let lockContent = readFileSync(lock.file).toString()
let packages = lockContent.includes('baseline-browser-mapping')
? ['caniuse-lite', 'baseline-browser-mapping']
: ['caniuse-lite']
let command = 'pnpm up --depth=9999 --no-save ' + packages.join(' ')
print('Updating caniuse-lite version\n' + pico.yellow('$ ' + command) + '\n')
try {
execFileSync('pnpm', ['up', '--depth=9999', '--no-save', ...packages], {
stdio: 'pipe'
})
} catch (error) /* c8 ignore start */ {
let text = commandErrorText(error)
if (text.includes('ERR_PNPM_STRICT_MIN_RELEASE_AGE_REQUIRES_SAVE')) {
updatePnpmStrict(print, lock, packages)
} else {
print(pico.red(text))
print(
pico.red('\n\nProblem with `' + command + '` call. Run it manually.\n')
)
process.exit(1)
}
} /* c8 ignore end */
}
function updateWith(print, cmd, message = 'Updating caniuse-lite version') {
print(message + '\n' + pico.yellow('$ ' + cmd) + '\n')
try {
execSync(cmd)
} catch (e) /* c8 ignore start */ {
print(pico.red(e.stdout.toString()))
print(
pico.red(
'\n' +
e.stack +
'\n\n' +
'Problem with `' +
cmd +
'` call. ' +
'Run it manually.\n'
)
)
process.exit(1)
} /* c8 ignore end */
}
module.exports = function updateDB(print = defaultPrint) {
let lock = detectLockfile()
let latest = getLatestInfo(lock)
let listError
let oldList
try {
oldList = getBrowsers()
} catch (e) {
listError = e
}
print(
'Registry latest: ' + pico.bold(pico.green(latest.version)) + '\n'
)
if (lock.mode === 'yarn' && lock.version !== 1) {
updateWith(print, YARN_CMD + ' up -R caniuse-lite baseline-browser-mapping')
} else if (lock.mode === 'pnpm') {
updatePnpm(print, lock)
} else if (lock.mode === 'bun') {
updateBun(print, lock, latest)
} else if (lock.mode === 'deno') {
updateWith(print, 'deno add npm:caniuse-lite npm:baseline-browser-mapping')
updateWith(
print,
'deno remove caniuse-lite baseline-browser-mapping',
'Cleaning package.json dependencies from caniuse-lite'
)
} else {
updatePackageManually(print, lock, latest)
}
print('caniuse-lite has been successfully updated\n')
let newList
if (!listError) {
try {
newList = getBrowsers()
} catch (e) /* c8 ignore start */ {
listError = e
} /* c8 ignore end */
}
if (listError) {
if (listError.message.includes("Cannot find module 'browserslist'")) {
print(
pico.gray(
'Install `browserslist` to your direct dependencies ' +
'to see target browser changes\n'
)
)
} else {
print(
pico.gray(
'Problem with browser list retrieval.\n' +
'Target browser changes won’t be shown.\n'
)
)
}
} else {
let changes = diffBrowsers(oldList, newList)
if (changes) {
print('\nTarget browser changes:\n')
print(changes + '\n')
} else {
print('\n' + pico.green('No target browser changes') + '\n')
}
}
}