chore:升级截图使用的SDK工具包

This commit is contained in:
冯普
2026-05-26 18:09:16 +08:00
parent 263b625015
commit eb29942f5d
2774 changed files with 26453 additions and 442377 deletions
-16
View File
@@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools.js" "$@"
else
exec node "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools.js" "$@"
fi
-16
View File
@@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" "$@"
else
exec node "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\chrome-devtools-mcp\build\src\bin\chrome-devtools-mcp.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" $args
} else {
& "$basedir/node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" $args
} else {
& "node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\chrome-devtools-mcp\build\src\bin\chrome-devtools.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools.js" $args
} else {
& "$basedir/node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools.js" $args
} else {
& "node$exe" "$basedir/../chrome-devtools-mcp/build/src/bin/chrome-devtools.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-16
View File
@@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../playwright/cli.js" "$@"
else
exec node "$basedir/../playwright/cli.js" "$@"
fi
-16
View File
@@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../playwright-core/cli.js" "$@"
else
exec node "$basedir/../playwright-core/cli.js" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\playwright-core\cli.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../playwright-core/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../playwright-core/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../playwright-core/cli.js" $args
} else {
& "node$exe" "$basedir/../playwright-core/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\playwright\cli.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../playwright/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../playwright/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../playwright/cli.js" $args
} else {
& "node$exe" "$basedir/../playwright/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+238 -64
View File
@@ -1,12 +1,38 @@
{
"name": "platform-prototype",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@puppeteer/browsers": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.1.tgz",
"integrity": "sha512-zmS4RTK9fbrc++WlAJhxYbfz3IjDeOmkK/CwwbLmk7ydfS9e2CiEeRJHEPvjDVElO/bwXbidwGA37Bsm6LzCnQ==",
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz",
"integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -33,14 +59,14 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz",
"integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==",
"version": "25.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"undici-types": "~7.19.0"
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@types/yauzl": {
@@ -90,6 +116,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/ast-types": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
@@ -119,9 +152,9 @@
}
},
"node_modules/bare-events": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz",
"integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==",
"dev": true,
"license": "Apache-2.0",
"peerDependencies": {
@@ -235,17 +268,14 @@
"node": "*"
}
},
"node_modules/chrome-devtools-mcp": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/chrome-devtools-mcp/-/chrome-devtools-mcp-0.21.0.tgz",
"integrity": "sha512-d+iqrRmcwpRFV3Q4DRCF2LCoq+WCRU3GhISKQ9v8g+1C2Uh8upj3urkjxNO4QIjhBMIYei/VQ1OQLFceby80Og==",
"license": "Apache-2.0",
"bin": {
"chrome-devtools": "build/src/bin/chrome-devtools.js",
"chrome-devtools-mcp": "build/src/bin/chrome-devtools-mcp.js"
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
"node": ">=6"
}
},
"node_modules/chromium-bidi": {
@@ -297,6 +327,33 @@
"dev": true,
"license": "MIT"
},
"node_modules/cosmiconfig": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz",
"integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
@@ -364,6 +421,26 @@
"once": "^1.4.0"
}
},
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -547,6 +624,23 @@
"node": ">= 14"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ip-address": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
@@ -557,6 +651,13 @@
"node": ">= 12"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true,
"license": "MIT"
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -567,6 +668,40 @@
"node": ">=8"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true,
"license": "MIT"
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
@@ -645,6 +780,38 @@
"node": ">= 14"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -652,37 +819,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/playwright": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.59.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
"license": "ISC"
},
"node_modules/progress": {
"version": "2.0.3",
@@ -732,14 +874,36 @@
"once": "^1.3.1"
}
},
"node_modules/puppeteer-core": {
"version": "24.43.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.0.tgz",
"integrity": "sha512-cCRNXsUlhyPoKDz6+TiSpfZpRS3mD6Y1YFKhkdr6ik6TMfuJb7fAtXq9ThUFc4sphxObDk3BuAvdxc1Y6YOnqQ==",
"node_modules/puppeteer": {
"version": "24.43.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.1.tgz",
"integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.13.2",
"chromium-bidi": "14.0.0",
"cosmiconfig": "^9.0.0",
"devtools-protocol": "0.0.1608973",
"puppeteer-core": "24.43.1",
"typed-query-selector": "^2.12.2"
},
"bin": {
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/puppeteer/node_modules/puppeteer-core": {
"version": "24.43.1",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz",
"integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.13.1",
"@puppeteer/browsers": "2.13.2",
"chromium-bidi": "14.0.0",
"debug": "^4.4.3",
"devtools-protocol": "0.0.1608973",
@@ -761,10 +925,20 @@
"node": ">=0.10.0"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/semver": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
"dev": true,
"license": "ISC",
"bin": {
@@ -929,9 +1103,9 @@
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT",
"optional": true
@@ -969,9 +1143,9 @@
"license": "ISC"
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"dev": true,
"license": "MIT",
"engines": {
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```
+217
View File
@@ -0,0 +1,217 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
const tokenValue = token.value;
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
return "keyword";
}
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
if (firstChar !== firstChar.toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts, startLineBaseZero) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line - startLineBaseZero;
const startColumn = startLoc.column;
const endLine = endLoc.line - startLineBaseZero;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const startLineBaseZero = (opts.startLine || 1) - 1;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end + startLineBaseZero).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@babel/code-frame",
"version": "7.29.7",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"charcodes": "^0.2.0",
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/helper-validator-identifier
> Validate identifier/keywords name
See our website [@babel/helper-validator-identifier](https://babeljs.io/docs/babel-helper-validator-identifier) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-validator-identifier
```
or using yarn:
```sh
yarn add @babel/helper-validator-identifier
```
+70
View File
@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isIdentifierChar = isIdentifierChar;
exports.isIdentifierName = isIdentifierName;
exports.isIdentifierStart = isIdentifierStart;
let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088f\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5c\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdc-\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7dc\ua7f1-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1add\u1ae0-\u1aeb\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65";
const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 7, 25, 39, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 5, 57, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 24, 43, 261, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 33, 24, 3, 24, 45, 74, 6, 0, 67, 12, 65, 1, 2, 0, 15, 4, 10, 7381, 42, 31, 98, 114, 8702, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 208, 30, 2, 2, 2, 1, 2, 6, 3, 4, 10, 1, 225, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4381, 3, 5773, 3, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 8489];
const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 78, 5, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 199, 7, 137, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 55, 9, 266, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 233, 0, 3, 0, 8, 1, 6, 0, 475, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
function isInAstralSet(code, set) {
let pos = 0x10000;
for (let i = 0, length = set.length; i < length; i += 2) {
pos += set[i];
if (pos > code) return false;
pos += set[i + 1];
if (pos >= code) return true;
}
return false;
}
function isIdentifierStart(code) {
if (code < 65) return code === 36;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes);
}
function isIdentifierChar(code) {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
}
function isIdentifierName(name) {
let isFirst = true;
for (let i = 0; i < name.length; i++) {
let cp = name.charCodeAt(i);
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) {
const trail = name.charCodeAt(++i);
if ((trail & 0xfc00) === 0xdc00) {
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff);
}
}
if (isFirst) {
isFirst = false;
if (!isIdentifierStart(cp)) {
return false;
}
} else if (!isIdentifierChar(cp)) {
return false;
}
}
return !isFirst;
}
//# sourceMappingURL=identifier.js.map
File diff suppressed because one or more lines are too long
+57
View File
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "isIdentifierChar", {
enumerable: true,
get: function () {
return _identifier.isIdentifierChar;
}
});
Object.defineProperty(exports, "isIdentifierName", {
enumerable: true,
get: function () {
return _identifier.isIdentifierName;
}
});
Object.defineProperty(exports, "isIdentifierStart", {
enumerable: true,
get: function () {
return _identifier.isIdentifierStart;
}
});
Object.defineProperty(exports, "isKeyword", {
enumerable: true,
get: function () {
return _keyword.isKeyword;
}
});
Object.defineProperty(exports, "isReservedWord", {
enumerable: true,
get: function () {
return _keyword.isReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindOnlyReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindOnlyReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindReservedWord;
}
});
Object.defineProperty(exports, "isStrictReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictReservedWord;
}
});
var _identifier = require("./identifier.js");
var _keyword = require("./keyword.js");
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"names":["_identifier","require","_keyword"],"sources":["../src/index.ts"],"sourcesContent":["export {\n isIdentifierName,\n isIdentifierChar,\n isIdentifierStart,\n} from \"./identifier.ts\";\nexport {\n isReservedWord,\n isStrictBindOnlyReservedWord,\n isStrictBindReservedWord,\n isStrictReservedWord,\n isKeyword,\n} from \"./keyword.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA","ignoreList":[]}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isKeyword = isKeyword;
exports.isReservedWord = isReservedWord;
exports.isStrictBindOnlyReservedWord = isStrictBindOnlyReservedWord;
exports.isStrictBindReservedWord = isStrictBindReservedWord;
exports.isStrictReservedWord = isStrictReservedWord;
const reservedWords = {
keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"],
strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"],
strictBind: ["eval", "arguments"]
};
const keywords = new Set(reservedWords.keyword);
const reservedWordsStrictSet = new Set(reservedWords.strict);
const reservedWordsStrictBindSet = new Set(reservedWords.strictBind);
function isReservedWord(word, inModule) {
return inModule && word === "await" || word === "enum";
}
function isStrictReservedWord(word, inModule) {
return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);
}
function isStrictBindOnlyReservedWord(word) {
return reservedWordsStrictBindSet.has(word);
}
function isStrictBindReservedWord(word, inModule) {
return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word);
}
function isKeyword(word) {
return keywords.has(word);
}
//# sourceMappingURL=keyword.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"names":["reservedWords","keyword","strict","strictBind","keywords","Set","reservedWordsStrictSet","reservedWordsStrictBindSet","isReservedWord","word","inModule","isStrictReservedWord","has","isStrictBindOnlyReservedWord","isStrictBindReservedWord","isKeyword"],"sources":["../src/keyword.ts"],"sourcesContent":["const reservedWords = {\n keyword: [\n \"break\",\n \"case\",\n \"catch\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"do\",\n \"else\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"return\",\n \"switch\",\n \"throw\",\n \"try\",\n \"var\",\n \"const\",\n \"while\",\n \"with\",\n \"new\",\n \"this\",\n \"super\",\n \"class\",\n \"extends\",\n \"export\",\n \"import\",\n \"null\",\n \"true\",\n \"false\",\n \"in\",\n \"instanceof\",\n \"typeof\",\n \"void\",\n \"delete\",\n ],\n strict: [\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n ],\n strictBind: [\"eval\", \"arguments\"],\n};\nconst keywords = new Set(reservedWords.keyword);\nconst reservedWordsStrictSet = new Set(reservedWords.strict);\nconst reservedWordsStrictBindSet = new Set(reservedWords.strictBind);\n\n/**\n * Checks if word is a reserved word in non-strict mode\n */\nexport function isReservedWord(word: string, inModule: boolean): boolean {\n return (inModule && word === \"await\") || word === \"enum\";\n}\n\n/**\n * Checks if word is a reserved word in non-binding strict mode\n *\n * Includes non-strict reserved words\n */\nexport function isStrictReservedWord(word: string, inModule: boolean): boolean {\n return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode, but it is allowed as\n * a normal identifier.\n */\nexport function isStrictBindOnlyReservedWord(word: string): boolean {\n return reservedWordsStrictBindSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode\n *\n * Includes non-strict reserved words and non-binding strict reserved words\n */\nexport function isStrictBindReservedWord(\n word: string,\n inModule: boolean,\n): boolean {\n return (\n isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word)\n );\n}\n\nexport function isKeyword(word: string): boolean {\n return keywords.has(word);\n}\n"],"mappings":";;;;;;;;;;AAAA,MAAMA,aAAa,GAAG;EACpBC,OAAO,EAAE,CACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAAU,EACV,SAAS,EACT,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,QAAQ,CACT;EACDC,MAAM,EAAE,CACN,YAAY,EACZ,WAAW,EACX,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,CACR;EACDC,UAAU,EAAE,CAAC,MAAM,EAAE,WAAW;AAClC,CAAC;AACD,MAAMC,QAAQ,GAAG,IAAIC,GAAG,CAACL,aAAa,CAACC,OAAO,CAAC;AAC/C,MAAMK,sBAAsB,GAAG,IAAID,GAAG,CAACL,aAAa,CAACE,MAAM,CAAC;AAC5D,MAAMK,0BAA0B,GAAG,IAAIF,GAAG,CAACL,aAAa,CAACG,UAAU,CAAC;AAK7D,SAASK,cAAcA,CAACC,IAAY,EAAEC,QAAiB,EAAW;EACvE,OAAQA,QAAQ,IAAID,IAAI,KAAK,OAAO,IAAKA,IAAI,KAAK,MAAM;AAC1D;AAOO,SAASE,oBAAoBA,CAACF,IAAY,EAAEC,QAAiB,EAAW;EAC7E,OAAOF,cAAc,CAACC,IAAI,EAAEC,QAAQ,CAAC,IAAIJ,sBAAsB,CAACM,GAAG,CAACH,IAAI,CAAC;AAC3E;AAMO,SAASI,4BAA4BA,CAACJ,IAAY,EAAW;EAClE,OAAOF,0BAA0B,CAACK,GAAG,CAACH,IAAI,CAAC;AAC7C;AAOO,SAASK,wBAAwBA,CACtCL,IAAY,EACZC,QAAiB,EACR;EACT,OACEC,oBAAoB,CAACF,IAAI,EAAEC,QAAQ,CAAC,IAAIG,4BAA4B,CAACJ,IAAI,CAAC;AAE9E;AAEO,SAASM,SAASA,CAACN,IAAY,EAAW;EAC/C,OAAOL,QAAQ,CAACQ,GAAG,CAACH,IAAI,CAAC;AAC3B","ignoreList":[]}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@babel/helper-validator-identifier",
"version": "7.29.7",
"description": "Validate identifier/keywords name",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-validator-identifier"
},
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"devDependencies": {
"@unicode/unicode-17.0.0": "^1.6.10",
"charcodes": "^0.2.0"
},
"engines": {
"node": ">=6.9.0"
},
"author": "The Babel Team (https://babel.dev/team)",
"type": "commonjs"
}
+1 -1
View File
@@ -54,7 +54,7 @@ function isValidPlatform(platform) {
}
// If moved update release-please config
// x-release-please-start-version
const packageVersion = '2.13.1';
const packageVersion = '2.13.2';
// x-release-please-end
/**
* @public
+1 -1
View File
@@ -266,7 +266,7 @@ async function runSetup(installedBrowser) {
return;
}
(0, node_child_process_1.spawnSync)(node_path_1.default.join(browserDir, 'setup.exe'), [`--configure-browser-in-directory=` + browserDir], {
shell: true,
shell: false,
});
// TODO: Handle error here. Currently the setup.exe sometimes
// errors although it sets the permissions correctly.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -18,7 +18,7 @@ function isValidPlatform(platform) {
}
// If moved update release-please config
// x-release-please-start-version
const packageVersion = '2.13.1';
const packageVersion = '2.13.2';
// x-release-please-end
/**
* @public
+1 -1
View File
@@ -255,7 +255,7 @@ async function runSetup(installedBrowser) {
return;
}
spawnSync(path.join(browserDir, 'setup.exe'), [`--configure-browser-in-directory=` + browserDir], {
shell: true,
shell: false,
});
// TODO: Handle error here. Currently the setup.exe sometimes
// errors although it sets the permissions correctly.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@puppeteer/browsers",
"version": "2.13.1",
"version": "2.13.2",
"description": "Download and launch browsers",
"scripts": {
"build:docs": "wireit",
+1 -1
View File
@@ -47,7 +47,7 @@ function isValidPlatform(platform: unknown): platform is BrowserPlatform {
// If moved update release-please config
// x-release-please-start-version
const packageVersion = '2.13.1';
const packageVersion = '2.13.2';
// x-release-please-end
/**
+1 -1
View File
@@ -492,7 +492,7 @@ async function runSetup(installedBrowser: InstalledBrowser): Promise<void> {
path.join(browserDir, 'setup.exe'),
[`--configure-browser-in-directory=` + browserDir],
{
shell: true,
shell: false,
},
);
// TODO: Handle error here. Currently the setup.exe sometimes
+1 -1
View File
@@ -8,7 +8,7 @@ This package contains type definitions for node (https://nodejs.org/).
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node.
### Additional Details
* Last updated: Thu, 07 May 2026 22:21:35 GMT
* Last updated: Tue, 19 May 2026 17:48:56 GMT
* Dependencies: [undici-types](https://npmjs.com/package/undici-types)
# Credits
+108
View File
@@ -491,6 +491,75 @@ declare module "node:async_hooks" {
* @experimental
*/
exit<R, TArgs extends any[]>(callback: (...args: TArgs) => R, ...args: TArgs): R;
/**
* Creates a disposable scope that enters the given store and automatically
* restores the previous store value when the scope is disposed. This method is
* designed to work with JavaScript's explicit resource management (`using` syntax).
*
* Example:
*
* ```js
* import { AsyncLocalStorage } from 'node:async_hooks';
*
* const asyncLocalStorage = new AsyncLocalStorage();
*
* {
* using _ = asyncLocalStorage.withScope('my-store');
* console.log(asyncLocalStorage.getStore()); // Prints: my-store
* }
*
* console.log(asyncLocalStorage.getStore()); // Prints: undefined
* ```
*
* The `withScope()` method is particularly useful for managing context in
* synchronous code where you want to ensure the previous store value is restored
* when exiting a block, even if an error is thrown.
*
* ```js
* import { AsyncLocalStorage } from 'node:async_hooks';
*
* const asyncLocalStorage = new AsyncLocalStorage();
*
* try {
* using _ = asyncLocalStorage.withScope('my-store');
* console.log(asyncLocalStorage.getStore()); // Prints: my-store
* throw new Error('test');
* } catch (e) {
* // Store is automatically restored even after error
* console.log(asyncLocalStorage.getStore()); // Prints: undefined
* }
* ```
*
* **Important:** When using `withScope()` in async functions before the first
* `await`, be aware that the scope change will affect the caller's context. The
* synchronous portion of an async function (before the first `await`) runs
* immediately when called, and when it reaches the first `await`, it returns the
* promise to the caller. At that point, the scope change becomes visible in the
* caller's context and will persist in subsequent synchronous code until something
* else changes the scope value. For async operations, prefer using `run()` which
* properly isolates context across async boundaries.
*
* ```js
* import { AsyncLocalStorage } from 'node:async_hooks';
*
* const asyncLocalStorage = new AsyncLocalStorage();
*
* async function example() {
* using _ = asyncLocalStorage.withScope('my-store');
* console.log(asyncLocalStorage.getStore()); // Prints: my-store
* await someAsyncOperation(); // Function pauses here and returns promise
* console.log(asyncLocalStorage.getStore()); // Prints: my-store
* }
*
* // Calling without await
* example(); // Synchronous portion runs, then pauses at first await
* // After the promise is returned, the scope 'my-store' is now active in caller!
* console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
* ```
* @since v25.9.0
* @experimental
*/
withScope(store: T): RunScope;
/**
* Transitions into the context for the remainder of the current
* synchronous execution and then persists the store through any following
@@ -533,6 +602,45 @@ declare module "node:async_hooks" {
*/
enterWith(store: T): void;
}
/**
* A disposable scope returned by `asyncLocalStorage.withScope()` that
* automatically restores the previous store value when disposed. This class
* implements the [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) protocol and is designed to work
* with JavaScript's `using` syntax.
*
* The scope automatically restores the previous store value when the `using` block
* exits, whether through normal completion or by throwing an error.
* @since v25.9.0
* @experimental
*/
interface RunScope extends Disposable {
/**
* Explicitly ends the scope and restores the previous store value. This method
* is idempotent: calling it multiple times has the same effect as calling it once.
*
* The `[Symbol.dispose]()` method defers to `dispose()`.
*
* If `withScope()` is called without the `using` keyword, `dispose()` must be
* called manually to restore the previous store value. Forgetting to call
* `dispose()` will cause the store value to persist for the remainder of the
* current execution context:
*
* ```js
* import { AsyncLocalStorage } from 'node:async_hooks';
*
* const storage = new AsyncLocalStorage();
*
* // Without using, the scope must be disposed manually
* const scope = storage.withScope('my-store');
* // storage.getStore() === 'my-store' here
*
* scope.dispose(); // Restore previous value
* // storage.getStore() === undefined here
* ```
* @since v25.9.0
*/
dispose(): void;
}
/**
* @since v17.2.0, v16.14.0
* @return A map of provider types to the corresponding numeric id.
+14 -3
View File
@@ -3778,7 +3778,7 @@ declare module "node:crypto" {
interface CShakeParams extends Algorithm {
customization?: NodeJS.BufferSource;
functionName?: NodeJS.BufferSource;
length: number;
outputLength: number;
}
interface ContextParams extends Algorithm {
context?: NodeJS.BufferSource;
@@ -3815,6 +3815,10 @@ declare module "node:crypto" {
hash: HashAlgorithmIdentifier;
length?: number;
}
interface KangarooTwelveParams {
customization?: NodeJS.BufferSource;
outputLength: number;
}
interface JsonWebKey {
alg?: string;
crv?: string;
@@ -3849,7 +3853,7 @@ declare module "node:crypto" {
}
interface KmacParams extends Algorithm {
customization?: NodeJS.BufferSource;
length: number;
outputLength: number;
}
interface Pbkdf2Params extends Algorithm {
hash: HashAlgorithmIdentifier;
@@ -3884,6 +3888,10 @@ declare module "node:crypto" {
interface RsaPssParams extends Algorithm {
saltLength: number;
}
interface TurboShakeParams {
domainSeparation?: number;
outputLength: number;
}
interface Crypto {
readonly subtle: SubtleCrypto;
getRandomValues<
@@ -3945,7 +3953,10 @@ declare module "node:crypto" {
extractable: boolean,
keyUsages: readonly KeyUsage[],
): Promise<CryptoKey>;
digest(algorithm: AlgorithmIdentifier | CShakeParams, data: NodeJS.BufferSource): Promise<ArrayBuffer>;
digest(
algorithm: AlgorithmIdentifier | CShakeParams | TurboShakeParams | KangarooTwelveParams,
data: NodeJS.BufferSource,
): Promise<ArrayBuffer>;
encapsulateBits(
encapsulationAlgorithm: AlgorithmIdentifier,
encapsulationKey: CryptoKey,
+6 -9
View File
@@ -528,15 +528,12 @@ declare module "node:events" {
* import { addAbortListener } from 'node:events';
*
* function example(signal) {
* let disposable;
* try {
* signal.addEventListener('abort', (e) => e.stopImmediatePropagation());
* disposable = addAbortListener(signal, (e) => {
* // Do something when signal is aborted.
* });
* } finally {
* disposable?.[Symbol.dispose]();
* }
* signal.addEventListener('abort', (e) => e.stopImmediatePropagation());
* // addAbortListener() returns a disposable, so the `using` keyword ensures
* // the abort listener is automatically removed when this scope exits.
* using _ = addAbortListener(signal, (e) => {
* // Do something when signal is aborted.
* });
* }
* ```
* @since v20.5.0
+130 -8
View File
@@ -380,7 +380,8 @@ declare module "node:fs" {
"ready": [];
}
/**
* Instances of `fs.ReadStream` are created and returned using the {@link createReadStream} function.
* Instances of `fs.ReadStream` cannot be constructed directly. They are created and
* returned using the `fs.createReadStream()` function.
* @since v0.1.93
*/
class ReadStream extends stream.Readable {
@@ -643,7 +644,8 @@ declare module "node:fs" {
"ready": [];
}
/**
* Instances of `fs.WriteStream` are created and returned using the {@link createWriteStream} function.
* Instances of `fs.WriteStream` cannot be constructed directly. They are created and
* returned using the `fs.createWriteStream()` function.
* @since v0.1.93
*/
class WriteStream extends stream.Writable {
@@ -1144,6 +1146,7 @@ declare module "node:fs" {
options:
| (StatOptions & {
bigint?: false | undefined;
throwIfNoEntry?: true | undefined;
})
| undefined,
callback: (err: NodeJS.ErrnoException | null, stats: Stats) => void,
@@ -1152,33 +1155,82 @@ declare module "node:fs" {
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry?: true | undefined;
},
callback: (err: NodeJS.ErrnoException | null, stats: BigIntStats) => void,
): void;
function stat(
path: PathLike,
options: StatOptions | undefined,
options: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry: false;
},
callback: (err: NodeJS.ErrnoException | null, stats: Stats | undefined) => void,
): void;
function stat(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry: false;
},
callback: (err: NodeJS.ErrnoException | null, stats: BigIntStats | undefined) => void,
): void;
function stat(
path: PathLike,
options: StatOptions & {
throwIfNoEntry?: true | undefined;
},
callback: (err: NodeJS.ErrnoException | null, stats: Stats | BigIntStats) => void,
): void;
function stat(
path: PathLike,
options: StatOptions | undefined,
callback: (err: NodeJS.ErrnoException | null, stats: Stats | BigIntStats | undefined) => void,
): void;
namespace stat {
// TODO: aliased promisify signatures
/**
* Asynchronous stat(2) - Get file status.
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
*/
function __promisify__(path: PathLike): Promise<Stats>;
function __promisify__(
path: PathLike,
options?: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry?: true | undefined;
},
): Promise<Stats>;
function __promisify__(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry?: true | undefined;
},
): Promise<BigIntStats>;
function __promisify__(path: PathLike, options?: StatOptions): Promise<Stats | BigIntStats>;
function __promisify__(
path: PathLike,
options: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry: false;
},
): Promise<Stats | undefined>;
function __promisify__(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry: false;
},
): Promise<BigIntStats | undefined>;
function __promisify__(
path: PathLike,
options: StatOptions & {
throwIfNoEntry?: true | undefined;
},
): Promise<Stats | BigIntStats>;
function __promisify__(path: PathLike, options?: StatOptions): Promise<Stats | BigIntStats | undefined>;
}
/** @deprecated This orphaned interface will be removed in a future version. */
interface StatSyncFn extends Function {
(path: PathLike, options?: undefined): Stats;
(
@@ -1220,7 +1272,42 @@ declare module "node:fs" {
* Synchronous stat(2) - Get file status.
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
*/
const statSync: StatSyncFn;
function statSync(path: PathLike): Stats;
function statSync(
path: PathLike,
options?: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry?: true | undefined;
},
): Stats;
function statSync(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry?: true | undefined;
},
): BigIntStats;
function statSync(
path: PathLike,
options: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry: false;
},
): Stats | undefined;
function statSync(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry: false;
},
): BigIntStats | undefined;
function statSync(
path: PathLike,
options: StatOptions & {
throwIfNoEntry?: true | undefined;
},
): Stats | BigIntStats;
function statSync(path: PathLike, options?: StatOptions): Stats | BigIntStats | undefined;
/**
* Invokes the callback with the `fs.Stats` for the file descriptor.
*
@@ -1410,7 +1497,42 @@ declare module "node:fs" {
* Synchronous lstat(2) - Get file status. Does not dereference symbolic links.
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
*/
const lstatSync: StatSyncFn;
function lstatSync(path: PathLike): Stats;
function lstatSync(
path: PathLike,
options?: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry?: true | undefined;
},
): Stats;
function lstatSync(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry?: true | undefined;
},
): BigIntStats;
function lstatSync(
path: PathLike,
options: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry: false;
},
): Stats | undefined;
function lstatSync(
path: PathLike,
options: StatOptions & {
bigint: true;
throwIfNoEntry: false;
},
): BigIntStats | undefined;
function lstatSync(
path: PathLike,
options: StatOptions & {
throwIfNoEntry?: true | undefined;
},
): Stats | BigIntStats;
function lstatSync(path: PathLike, options?: StatOptions): Stats | BigIntStats | undefined;
/**
* Creates a new link from the `existingPath` to the `newPath`. See the POSIX [`link(2)`](http://man7.org/linux/man-pages/man2/link.2.html) documentation for more detail. No arguments other than
* a possible
@@ -4461,10 +4583,10 @@ declare module "node:fs" {
}
interface StatOptions {
bigint?: boolean | undefined;
}
interface StatSyncOptions extends StatOptions {
throwIfNoEntry?: boolean | undefined;
}
/** @deprecated This orphaned interface will be removed in a future version. Use `StatOptions` instead. */
interface StatSyncOptions extends StatOptions {}
interface CopyOptionsBase {
/**
* Dereference symlinks
+150 -1
View File
@@ -37,6 +37,7 @@ declare module "node:fs/promises" {
WriteVResult,
} from "node:fs";
import { Stream } from "node:stream";
import { ByteReadableStream, Transform, Writer } from "node:stream/iter";
import { ReadableStream } from "node:stream/web";
interface FileChangeInfo<T extends string | Buffer> {
eventType: WatchEventType;
@@ -85,6 +86,57 @@ declare module "node:fs/promises" {
interface ReadableWebStreamOptions {
autoClose?: boolean | undefined;
}
interface PullOptions extends Abortable {
/**
* Close the file handle when the stream ends.
* @default false
*/
autoClose?: boolean | undefined;
/**
* Byte offset to begin reading from. When specified,
* reads use explicit positioning (`pread` semantics).
*/
start?: number | undefined;
/**
* Maximum number of bytes to read before ending the
* iterator. Reads stop when `limit` bytes have been delivered or EOF is
* reached, whichever comes first.
*/
limit?: number | undefined;
/**
* Size in bytes of the buffer allocated for each
* read operation.
* @default 131072
*/
chunkSize?: number | undefined;
}
interface WriterOptions {
/**
* Close the file handle when the writer ends or fails.
* @default false
*/
autoClose?: boolean | undefined;
/**
* Byte offset to start writing at. When specified,
* writes use explicit positioning.
*/
start?: number | undefined;
/**
* Maximum number of bytes the writer will accept.
* Async writes (`write()`, `writev()`) that would exceed the limit reject
* with `ERR_OUT_OF_RANGE`. Sync writes (`writeSync()`, `writevSync()`)
* return `false`.
*/
limit?: number | undefined;
/**
* Maximum chunk size in bytes for synchronous write
* operations. Writes larger than this threshold fall back to async I/O.
* Set this to match the reader's `chunkSize` for optimal `pipeTo()`
* performance.
* @default 131072
*/
chunkSize?: number | undefined;
}
// TODO: Add `EventEmitter` close
interface FileHandle {
/**
@@ -202,6 +254,41 @@ declare module "node:fs/promises" {
* @return Fulfills with `undefined` upon success.
*/
datasync(): Promise<void>;
/**
* Return the file contents as an async iterable using the
* [`node:stream/iter`](https://nodejs.org/docs/latest-v25.x/api/stream_iter.html) pull model. Reads are performed in `chunkSize`-byte
* chunks (default 128 KB). If transforms are provided, they are applied
* via [`stream/iter pull()`](https://nodejs.org/docs/latest-v25.x/api/stream_iter.html#pullsource-transforms-options).
*
* The file handle is locked while the iterable is being consumed and unlocked
* when iteration completes, an error occurs, or the consumer breaks.
*
* This function is only available when the `--experimental-stream-iter` flag is
* enabled.
*
* ```js
* import { open } from 'node:fs/promises';
* import { text } from 'node:stream/iter';
* import { compressGzip } from 'node:zlib/iter';
*
* const fh = await open('input.txt', 'r');
*
* // Read as text
* console.log(await text(fh.pull({ autoClose: true })));
*
* // Read 1 KB starting at byte 100
* const fh2 = await open('input.txt', 'r');
* console.log(await text(fh2.pull({ start: 100, limit: 1024, autoClose: true })));
*
* // Read with compression
* const fh3 = await open('input.txt', 'r');
* const compressed = fh3.pull(compressGzip(), { autoClose: true });
* ```
* @since v25.9.0
* @experimental
*/
pull(...transforms: Transform[]): ByteReadableStream;
pull(...args: [...transforms: Transform[], options: PullOptions]): ByteReadableStream;
/**
* Request that all data for the open file descriptor is flushed to the storage
* device. The specific implementation is operating system and device specific.
@@ -450,6 +537,45 @@ declare module "node:fs/promises" {
buffers: TBuffers,
position?: number,
): Promise<WriteVResult<TBuffers>>;
/**
* Return a [`node:stream/iter`](https://nodejs.org/docs/latest-v25.x/api/stream_iter.html) writer backed by this file handle.
*
* The writer supports both `Symbol.asyncDispose` and `Symbol.dispose`:
*
* * `await using w = fh.writer()` — if the writer is still open (no `end()`
* called), `asyncDispose` calls `fail()`. If `end()` is pending, it waits
* for it to complete.
* * `using w = fh.writer()` — calls `fail()` unconditionally.
*
* The `writeSync()` and `writevSync()` methods enable the try-sync fast path
* used by [`stream/iter pipeTo()`](https://nodejs.org/docs/latest-v25.x/api/stream_iter.html#pipetosource-transforms-writer). When the reader's chunk size matches the
* writer's `chunkSize`, all writes in a `pipeTo()` pipeline complete
* synchronously with zero promise overhead.
*
* This function is only available when the `--experimental-stream-iter` flag is
* enabled.
*
* ```js
* import { open } from 'node:fs/promises';
* import { from, pipeTo } from 'node:stream/iter';
* import { compressGzip } from 'node:zlib/iter';
*
* // Async pipeline
* const fh = await open('output.gz', 'w');
* await pipeTo(from('Hello!'), compressGzip(), fh.writer({ autoClose: true }));
*
* // Sync pipeline with limit
* const src = await open('input.txt', 'r');
* const dst = await open('output.txt', 'w');
* const w = dst.writer({ limit: 1024 * 1024 }); // Max 1 MB
* await pipeTo(src.pull({ autoClose: true }), w);
* await w.end();
* await dst.close();
* ```
* @since v25.9.0
* @experimental
*/
writer(options?: WriterOptions): Writer;
/**
* Read from a file and write to an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s
* @since v13.13.0, v12.17.0
@@ -802,19 +928,42 @@ declare module "node:fs/promises" {
* @since v10.0.0
* @return Fulfills with the {fs.Stats} object for the given `path`.
*/
function stat(path: PathLike): Promise<Stats>;
function stat(
path: PathLike,
opts?: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry?: true | undefined;
},
): Promise<Stats>;
function stat(
path: PathLike,
opts: StatOptions & {
bigint: true;
throwIfNoEntry?: true | undefined;
},
): Promise<BigIntStats>;
function stat(path: PathLike, opts?: StatOptions): Promise<Stats | BigIntStats>;
function stat(
path: PathLike,
opts: StatOptions & {
bigint?: false | undefined;
throwIfNoEntry: false;
},
): Promise<Stats | undefined>;
function stat(
path: PathLike,
opts: StatOptions & {
bigint: true;
throwIfNoEntry: false;
},
): Promise<BigIntStats | undefined>;
function stat(
path: PathLike,
opts: StatOptions & {
throwIfNoEntry?: true | undefined;
},
): Promise<Stats | BigIntStats>;
function stat(path: PathLike, opts?: StatOptions): Promise<Stats | BigIntStats | undefined>;
/**
* @since v19.6.0, v18.15.0
* @return Fulfills with the {fs.StatFs} object for the given `path`.
+12
View File
@@ -1242,10 +1242,14 @@ declare module "node:http2" {
> extends SessionOptions {
streamResetBurst?: number | undefined;
streamResetRate?: number | undefined;
/** @deprecated Use `http1Options.IncomingMessage` instead. */
Http1IncomingMessage?: Http1Request | undefined;
/** @deprecated Use `http1Options.ServerResponse` instead. */
Http1ServerResponse?: Http1Response | undefined;
http1Options?: Http1Options<Http1Request, Http1Response> | undefined;
Http2ServerRequest?: Http2Request | undefined;
Http2ServerResponse?: Http2Response | undefined;
strictSingleValueFields?: boolean | undefined;
}
interface SecureClientSessionOptions extends ClientSessionOptions, tls.ConnectionOptions {}
interface SecureServerSessionOptions<
@@ -1269,6 +1273,14 @@ declare module "node:http2" {
allowHTTP1?: boolean | undefined;
origins?: string[] | undefined;
}
interface Http1Options<
Request extends typeof IncomingMessage,
Response extends typeof ServerResponse<InstanceType<Request>>,
> {
IncomingMessage?: Request | undefined;
ServerResponse?: Response | undefined;
keepAliveTimeout?: number | undefined;
}
interface Http2ServerCommon {
setTimeout(msec?: number, callback?: () => void): this;
/**
+2
View File
@@ -95,6 +95,7 @@
/// <reference path="sqlite.d.ts" />
/// <reference path="stream.d.ts" />
/// <reference path="stream/consumers.d.ts" />
/// <reference path="stream/iter.d.ts" />
/// <reference path="stream/promises.d.ts" />
/// <reference path="stream/web.d.ts" />
/// <reference path="string_decoder.d.ts" />
@@ -113,3 +114,4 @@
/// <reference path="wasi.d.ts" />
/// <reference path="worker_threads.d.ts" />
/// <reference path="zlib.d.ts" />
/// <reference path="zlib/iter.d.ts" />
+5
View File
@@ -2035,6 +2035,9 @@ declare module "node:inspector" {
autoAttach: boolean;
waitForDebuggerOnStart: boolean;
}
interface GetTargetsReturnType {
targetInfos: TargetInfo[];
}
interface TargetCreatedEventDataType {
targetInfo: TargetInfo;
}
@@ -2506,6 +2509,7 @@ declare module "node:inspector" {
*/
post(method: "NodeWorker.detach", params?: NodeWorker.DetachParameterType, callback?: (err: Error | null) => void): void;
post(method: "NodeWorker.detach", callback?: (err: Error | null) => void): void;
post(method: "Target.getTargets", callback?: (err: Error | null, params: Target.GetTargetsReturnType) => void): void;
post(method: "Target.setAutoAttach", params?: Target.SetAutoAttachParameterType, callback?: (err: Error | null) => void): void;
post(method: "Target.setAutoAttach", callback?: (err: Error | null) => void): void;
post(method: "DOMStorage.clear", params?: DOMStorage.ClearParameterType, callback?: (err: Error | null) => void): void;
@@ -3642,6 +3646,7 @@ declare module "node:inspector/promises" {
* Detached from the worker with given sessionId.
*/
post(method: "NodeWorker.detach", params?: NodeWorker.DetachParameterType): Promise<void>;
post(method: "Target.getTargets"): Promise<Target.GetTargetsReturnType>;
post(method: "Target.setAutoAttach", params?: Target.SetAutoAttachParameterType): Promise<void>;
post(method: "DOMStorage.clear", params?: DOMStorage.ClearParameterType): Promise<void>;
/**
+1
View File
@@ -218,6 +218,7 @@ declare module "node:module" {
* This feature requires `--allow-worker` if used with the
* [Permission Model](https://nodejs.org/docs/latest-v25.x/api/permissions.html#permission-model).
* @since v20.6.0, v18.19.0
* @deprecated Use `module.registerHooks()` instead.
* @param specifier Customization hooks to be registered; this should be
* the same string that would be passed to `import()`, except that if it is
* relative, it is resolved relative to `parentURL`.
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@types/node",
"version": "25.6.2",
"version": "25.9.1",
"description": "TypeScript definitions for node",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node",
"license": "MIT",
@@ -147,9 +147,9 @@
},
"scripts": {},
"dependencies": {
"undici-types": "~7.19.0"
"undici-types": ">=7.24.0 <7.24.7"
},
"peerDependencies": {},
"typesPublisherContentHash": "89af6b1f98f4dcf78863724bff751d6099486220dae09983e7b6430b0be61ac2",
"typesPublisherContentHash": "2546f5f588e15fc9aa202a3005dab2859d006fd48a8448107741e5ce184e9098",
"typeScriptVersion": "5.3"
}
+54 -25
View File
@@ -822,6 +822,28 @@ declare module "node:process" {
* @since v0.7.0
*/
abort(): never;
/**
* The `process.addUncaughtExceptionCaptureCallback()` function adds a callback
* that will be invoked when an uncaught exception occurs, receiving the exception
* value as its first argument.
*
* Unlike `process.setUncaughtExceptionCaptureCallback()`, this function allows
* multiple callbacks to be registered and does not conflict with the
* [`domain`](https://nodejs.org/docs/latest-v25.x/api/domain.html) module. Callbacks are called in reverse order of registration
* (most recent first). If a callback returns `true`, subsequent callbacks
* and the default uncaught exception handling are skipped.
*
* ```js
* import process from 'node:process';
*
* process.addUncaughtExceptionCaptureCallback((err) => {
* console.error('Caught exception:', err.message);
* return true; // Indicates exception was handled
* });
* ```
* @since v25.9.0
*/
addUncaughtExceptionCaptureCallback(fn: (err: unknown) => boolean): void;
/**
* The `process.chdir()` method changes the current working directory of the
* Node.js process or throws an exception if doing so fails (for instance, if
@@ -1418,9 +1440,11 @@ declare module "node:process" {
* method with a non-`null` argument while another capture function is set will
* throw an error.
*
* Using this function is mutually exclusive with using the deprecated `domain` built-in module.
* To register multiple callbacks that can coexist, use
* `process.addUncaughtExceptionCaptureCallback()` instead.
* @since v9.3.0
*/
// TODO: callback parameter should be `unknown`
setUncaughtExceptionCaptureCallback(cb: ((err: Error) => void) | null): void;
/**
* Indicates whether a callback has been set using {@link setUncaughtExceptionCaptureCallback}.
@@ -1474,30 +1498,35 @@ declare module "node:process" {
* Will generate an object similar to:
*
* ```console
* { node: '20.2.0',
* acorn: '8.8.2',
* ada: '2.4.0',
* ares: '1.19.0',
* base64: '0.5.0',
* brotli: '1.0.9',
* cjs_module_lexer: '1.2.2',
* cldr: '43.0',
* icu: '73.1',
* llhttp: '8.1.0',
* modules: '115',
* napi: '8',
* nghttp2: '1.52.0',
* nghttp3: '0.7.0',
* ngtcp2: '0.8.1',
* openssl: '3.0.8+quic',
* simdutf: '3.2.9',
* tz: '2023c',
* undici: '5.22.0',
* unicode: '15.0',
* uv: '1.44.2',
* uvwasi: '0.0.16',
* v8: '11.3.244.8-node.9',
* zlib: '1.2.13' }
* { node: '26.0.0-pre',
* acorn: '8.15.0',
* ada: '3.4.1',
* amaro: '1.1.5',
* ares: '1.34.6',
* brotli: '1.2.0',
* merve: '1.0.0',
* cldr: '48.0',
* icu: '78.2',
* llhttp: '9.3.0',
* modules: '144',
* napi: '10',
* nbytes: '0.1.1',
* ncrypto: '0.0.1',
* nghttp2: '1.68.0',
* nghttp3: '',
* ngtcp2: '',
* openssl: '3.5.4',
* simdjson: '4.2.4',
* simdutf: '7.3.3',
* sqlite: '3.51.2',
* tz: '2025c',
* undici: '7.18.2',
* unicode: '17.0',
* uv: '1.51.0',
* uvwasi: '0.0.23',
* v8: '14.3.127.18-node.10',
* zlib: '1.3.1-e00f703',
* zstd: '1.5.7' }
* ```
* @since v0.2.0
*/
+2 -2
View File
@@ -179,7 +179,7 @@ declare module "node:quic" {
* The TLS crypto keys to use for sessions.
* @since v23.8.0
*/
keys?: KeyObject | webcrypto.CryptoKey | ReadonlyArray<KeyObject | webcrypto.CryptoKey> | undefined;
keys?: KeyObject | readonly KeyObject[] | undefined;
/**
* Specifies the maximum UDP packet payload size.
* @since v23.8.0
@@ -653,7 +653,7 @@ declare module "node:quic" {
/**
* Sends an unreliable datagram to the remote peer, returning the datagram ID.
* If the datagram payload is specified as an `ArrayBufferView`, then ownership of
* that view will be transfered to the underlying stream.
* that view will be transferred to the underlying stream.
* @since v23.8.0
*/
sendDatagram(datagram: string | NodeJS.ArrayBufferView): bigint;
+15
View File
@@ -86,6 +86,21 @@ declare module "node:repl" {
* @default false
*/
breakEvalOnSigint?: boolean | undefined;
/**
* This function customizes error handling in the REPL.
* It receives the thrown exception as its first argument and must return one
* of the following values synchronously:
* * `'print'` to print the error to the output stream (default behavior).
* * `'ignore'` to skip all remaining error handling.
* * `'unhandled'` to treat the exception as fully unhandled. In this case,
* the error will be passed to process-wide exception handlers, such as
* the `'uncaughtException'` event.
* The `'unhandled'` value may or may not be desirable in situations
* where the `REPLServer` instance has been closed, depending on the particular
* use case.
* @since v25.9.0
*/
handleError?: ((err: unknown) => "print" | "ignore" | "unhandled") | undefined;
}
type REPLEval = (
this: REPLServer,
+54 -7
View File
@@ -87,6 +87,29 @@ declare module "node:sqlite" {
* @default true
*/
defensive?: boolean | undefined;
/**
* Configuration for various SQLite limits. These limits
* can be used to prevent excessive resource consumption when handling
* potentially malicious input. See [Run-Time Limits](https://www.sqlite.org/c3ref/c_limit_attached.html) and [Limit Constants](https://www.sqlite.org/c3ref/limit.html)
* in the SQLite documentation for details. Default values are determined by
* SQLite's compile-time defaults and may vary depending on how SQLite was
* built. The following properties are supported:
* @since v25.8.0
*/
limits?: NodeJS.PartialOptions<DatabaseLimits> | undefined;
}
interface DatabaseLimits {
length: number;
sqlLength: number;
column: number;
exprDepth: number;
compoundSelect: number;
vdbeOp: number;
functionArg: number;
attach: number;
likePatternLength: number;
variableNumber: number;
triggerDepth: number;
}
interface CreateSessionOptions {
/**
@@ -311,18 +334,17 @@ declare module "node:sqlite" {
* @since v22.13.0
* @param name The name of the SQLite function to create.
* @param options Optional configuration settings for the function.
* @param func The JavaScript function to call when the SQLite
* function is invoked. The return value of this function should be a valid
* SQLite data type: see
* [Type conversion between JavaScript and SQLite](https://nodejs.org/docs/latest-v25.x/api/sqlite.html#type-conversion-between-javascript-and-sqlite).
* The result defaults to `NULL` if the return value is `undefined`.
* @param fn The JavaScript function to call when the SQLite function is
* invoked. The return value of this function should be a valid SQLite data type:
* see [Type conversion between JavaScript and SQLite](https://nodejs.org/docs/latest-v25.x/api/sqlite.html#type-conversion-between-javascript-and-sqlite). The result defaults to
* `NULL` if the return value is `undefined`.
*/
function(
name: string,
options: FunctionOptions,
func: (...args: SQLOutputValue[]) => SQLInputValue,
fn: (...args: SQLOutputValue[]) => SQLInputValue,
): void;
function(name: string, func: (...args: SQLOutputValue[]) => SQLInputValue): void;
function(name: string, fn: (...args: SQLOutputValue[]) => SQLInputValue): void;
/**
* Sets an authorizer callback that SQLite will invoke whenever it attempts to
* access data or modify the database schema through prepared statements.
@@ -392,6 +414,31 @@ declare module "node:sqlite" {
* @since v24.0.0
*/
readonly isTransaction: boolean;
/**
* An object for getting and setting SQLite database limits at runtime.
* Each property corresponds to an SQLite limit and can be read or written.
*
* ```js
* const db = new DatabaseSync(':memory:');
*
* // Read current limit
* console.log(db.limits.length);
*
* // Set a new limit
* db.limits.sqlLength = 100000;
*
* // Reset a limit to its compile-time maximum
* db.limits.sqlLength = Infinity;
* ```
*
* Available properties: `length`, `sqlLength`, `column`, `exprDepth`,
* `compoundSelect`, `vdbeOp`, `functionArg`, `attach`, `likePatternLength`,
* `variableNumber`, `triggerDepth`.
*
* Setting a property to `Infinity` resets the limit to its compile-time maximum value.
* @since v25.8.0
*/
readonly limits: DatabaseLimits;
/**
* Opens the database specified in the `path` argument of the `DatabaseSync`constructor. This method should only be used when the database is not opened via
* the constructor. An exception is thrown if the database is already open.
+1 -1
View File
@@ -1067,7 +1067,7 @@ declare module "node:stream" {
writableCorked?: number | undefined;
}
interface DuplexToWebOptions {
type?: web.ReadableStreamType | undefined;
readableType?: web.ReadableStreamType | undefined;
}
interface DuplexEventMap extends ReadableEventMap, WritableEventMap {}
/**
+301
View File
@@ -0,0 +1,301 @@
declare module "node:stream/iter" {
// Symbols and custom typedefs
const broadcastProtocol: unique symbol;
const drainableProtocol: unique symbol;
const shareProtocol: unique symbol;
const shareSyncProtocol: unique symbol;
const toAsyncStreamable: unique symbol;
const toStreamable: unique symbol;
type Source =
| string
| ArrayBufferLike
| ArrayBufferView
| Iterable<SyncSource>
| AsyncIterable<Source>
| Streamable
| AsyncStreamable;
type SyncSource = string | ArrayBufferLike | ArrayBufferView | Iterable<SyncSource> | Streamable;
type Transform = StatelessTransformFn | StatefulTransform;
type SyncTransform = SyncStatelessTransformFn | SyncStatefulTransform;
type TransformResult =
| string
| ArrayBufferLike
| ArrayBufferView
| Iterable<SyncTransformResult>
| AsyncIterable<TransformResult>;
type SyncTransformResult = string | ArrayBufferLike | ArrayBufferView | Iterable<SyncTransformResult>;
interface AsyncStreamable {
[toAsyncStreamable](): Source;
}
interface Broadcastable {
[broadcastProtocol](options: BroadcastOptions): Broadcast;
}
interface Drainable {
[drainableProtocol](): Promise<boolean> | null;
}
interface Shareable {
[shareProtocol](options: ShareOptions): Share;
}
interface Streamable {
[toStreamable](): SyncSource;
}
interface SyncShareable {
[shareSyncProtocol](options: ShareSyncOptions): SyncShare;
}
// IDL dictionaries, enums, typedefs
type BackpressurePolicy = "strict" | "block" | "drop-oldest" | "drop-newest";
type ByteReadableStream = AsyncIterable<Uint8Array[]>;
type SyncByteReadableStream = Iterable<Uint8Array[]>;
interface WriteOptions {
signal?: AbortSignal;
}
interface PushStreamOptions {
highWaterMark?: number;
backpressure?: BackpressurePolicy;
signal?: AbortSignal;
}
interface PullOptions {
signal?: AbortSignal;
}
interface PipeToOptions {
signal?: AbortSignal;
preventClose?: boolean;
preventFail?: boolean;
}
interface PipeToSyncOptions {
preventClose?: boolean;
preventFail?: boolean;
}
interface ConsumeOptions {
signal?: AbortSignal;
limit?: number;
}
interface ConsumeSyncOptions {
limit?: number;
}
interface TextConsumeOptions extends ConsumeOptions {
encoding?: string;
}
interface TextConsumeSyncOptions extends ConsumeSyncOptions {
encoding?: string;
}
interface MergeOptions {
signal?: AbortSignal;
}
interface BroadcastOptions {
highWaterMark?: number;
backpressure?: BackpressurePolicy;
signal?: AbortSignal;
}
interface ShareOptions {
highWaterMark?: number;
backpressure?: BackpressurePolicy;
signal?: AbortSignal;
}
interface ShareSyncOptions {
highWaterMark?: number;
backpressure?: BackpressurePolicy;
}
interface DuplexDirectionOptions {
highWaterMark?: number;
backpressure?: BackpressurePolicy;
}
interface DuplexOptions {
highWaterMark?: number;
backpressure?: BackpressurePolicy;
a?: DuplexDirectionOptions;
b?: DuplexDirectionOptions;
signal?: AbortSignal;
}
interface TransformCallbackOptions {
signal: AbortSignal;
}
interface StatelessTransformFn {
(
chunks: Uint8Array[] | null,
options: TransformCallbackOptions,
): Promise<TransformResult | null> | TransformResult | null;
}
interface SyncStatelessTransformFn {
(chunks: Uint8Array[] | null): SyncTransformResult | null;
}
interface StatefulTransform {
transform(
source: AsyncIterable<Uint8Array[] | null>,
options: TransformCallbackOptions,
): AsyncIterable<TransformResult>;
}
interface SyncStatefulTransform {
transform(source: Iterable<Uint8Array[] | null>): Iterable<SyncTransformResult>;
}
// IDL interfaces
interface PushWriter extends Writer, Drainable {}
interface PushStreamResult {
writer: PushWriter;
readable: ByteReadableStream;
}
interface BroadcastWriter extends Writer, Drainable {}
interface BroadcastResult {
writer: BroadcastWriter;
broadcast: Broadcast;
}
interface Writer extends Disposable, AsyncDisposable {
readonly desiredSize: number | null;
write(chunk: Uint8Array | string, options?: WriteOptions): Promise<void>;
writev(chunks: Array<Uint8Array | string>, options?: WriteOptions): Promise<void>;
writeSync(chunk: Uint8Array | string): boolean;
writevSync(chunks: Array<Uint8Array | string>): boolean;
end(options?: WriteOptions): Promise<number>;
endSync(): number;
fail(reason?: any): void;
}
interface PartialWriter extends Partial<Writer> {
write(chunk: Uint8Array | string, options?: WriteOptions): Promise<void>;
}
interface SyncWriter extends Disposable {
readonly desiredSize: number | null;
writeSync(chunk: Uint8Array | string): number;
writevSync(chunks: Array<Uint8Array | string>): number;
endSync(): number;
fail(reason?: any): void;
}
interface PartialSyncWriter extends Partial<SyncWriter> {
writeSync(chunk: Uint8Array | string): number;
}
interface Broadcast extends Disposable {
readonly consumerCount: number;
readonly bufferSize: number;
push(...args: any[]): ByteReadableStream;
cancel(reason?: any): void;
}
interface Share extends Disposable {
readonly consumerCount: number;
readonly bufferSize: number;
pull(...args: any[]): ByteReadableStream;
cancel(reason?: any): void;
}
interface SyncShare extends Disposable {
readonly consumerCount: number;
readonly bufferSize: number;
pull(...args: any): SyncByteReadableStream;
cancel(reason?: any): void;
}
interface DuplexChannel extends AsyncDisposable {
readonly writer: Writer;
readonly readable: ByteReadableStream;
close(): Promise<void>;
}
// Push stream creation
function push(...transforms: Transform[]): PushStreamResult;
function push(...args: [...transforms: Transform[], options: PushStreamOptions]): PushStreamResult;
// Stream factories
function from(input: Source): ByteReadableStream;
function fromSync(input: SyncSource): SyncByteReadableStream;
// Pull pipelines
function pull(source: Source, ...transforms: Transform[]): ByteReadableStream;
function pull(
source: Source,
...args: [...transforms: Transform[], options: PullOptions]
): ByteReadableStream;
function pullSync(source: SyncSource, ...transforms: SyncTransform[]): SyncByteReadableStream;
// Pipe operations
function pipeTo(source: Source, writer: PartialWriter, options?: PipeToOptions): Promise<number>;
function pipeTo(source: Source, ...args: [...transforms: Transform[], writer: PartialWriter]): Promise<number>;
function pipeTo(
source: Source,
...args: [...transforms: Transform[], writer: PartialWriter, options: PipeToOptions]
): Promise<number>;
function pipeToSync(source: SyncSource, writer: PartialSyncWriter, options?: PipeToSyncOptions): number;
function pipeToSync(
source: SyncSource,
...args: [...transforms: SyncTransform[], writer: PartialSyncWriter]
): number;
function pipeToSync(
source: SyncSource,
...args: [...transforms: SyncTransform[], writer: PartialSyncWriter, options: PipeToSyncOptions]
): number;
// Consumers
function bytes(source: Source, options?: ConsumeOptions): Promise<Uint8Array>;
function bytesSync(source: SyncSource, options?: ConsumeSyncOptions): Uint8Array;
function text(source: Source, options?: TextConsumeOptions): Promise<string>;
function textSync(source: SyncSource, options?: TextConsumeSyncOptions): string;
function arrayBuffer(source: Source, options?: ConsumeOptions): Promise<ArrayBuffer>;
function arrayBufferSync(source: SyncSource, options?: ConsumeSyncOptions): ArrayBuffer;
function array(source: Source, options?: ConsumeOptions): Promise<Uint8Array[]>;
function arraySync(source: SyncSource, options?: ConsumeSyncOptions): Uint8Array[];
// Utilities
function tap(callback: StatelessTransformFn): StatelessTransformFn;
function tapSync(callback: SyncStatelessTransformFn): SyncStatelessTransformFn;
function merge(...sources: Source[]): ByteReadableStream;
function merge(...args: [...sources: Source[], options: MergeOptions]): ByteReadableStream;
function ondrain(drainable: any): Promise<boolean> | null;
// Multi-consumer
function broadcast(options?: BroadcastOptions): BroadcastResult;
function share(source: Source, options?: ShareOptions): Share;
function shareSync(source: SyncSource, options?: ShareSyncOptions): SyncShare;
// Duplex
function duplex(options?: DuplexOptions): [DuplexChannel, DuplexChannel];
// Node.js-specific extensions
namespace Broadcast {
/**
* Create a `Broadcast` from an existing source. The source is consumed
* automatically and pushed to all subscribers.
* @since v25.9.0
* @param options Same as `broadcast()`.
*/
function from(
input: ByteReadableStream | SyncByteReadableStream | Broadcastable,
options?: BroadcastOptions,
): BroadcastResult;
}
namespace Share {
/**
* Create a `Share` from an existing source.
* @since v25.9.0
* @param options Same as `share()`.
*/
function from(input: ByteReadableStream | SyncByteReadableStream | Shareable, options?: ShareOptions): Share;
}
namespace SyncShare {
/**
* @since v25.9.0
*/
function from(input: SyncByteReadableStream | SyncShareable, options?: ShareSyncOptions): SyncShare;
}
namespace Stream {
export {
array,
arrayBuffer,
arrayBufferSync,
arraySync,
broadcast,
broadcastProtocol,
bytes,
bytesSync,
drainableProtocol,
duplex,
from,
fromSync,
merge,
ondrain,
pipeTo,
pipeToSync,
pull,
pullSync,
push,
share,
shareProtocol,
shareSync,
shareSyncProtocol,
tap,
tapSync,
text,
textSync,
toAsyncStreamable,
toStreamable,
};
}
}
declare module "stream/iter" {
export * from "node:stream/iter";
}
+93 -16
View File
@@ -1,5 +1,5 @@
declare module "node:test" {
import { AssertMethodNames } from "node:assert";
import { AssertMethodNames, AssertPredicate } from "node:assert";
import { Readable, ReadableEventMap } from "node:stream";
import { TestEvent } from "node:test/reporters";
import { URL } from "node:url";
@@ -111,7 +111,12 @@ declare module "node:test" {
function only(name?: string, fn?: SuiteFn): Promise<void>;
function only(options?: TestOptions, fn?: SuiteFn): Promise<void>;
function only(fn?: SuiteFn): Promise<void>;
// added in v25.5.0, undocumented
/**
* This flips the pass/fail reporting for a specific test or suite: a flagged test
* case must throw in order to pass, and a flagged test case that does not throw
* fails.
* @since v25.5.0
*/
function expectFailure(name?: string, options?: TestOptions, fn?: SuiteFn): Promise<void>;
function expectFailure(name?: string, fn?: SuiteFn): Promise<void>;
function expectFailure(options?: TestOptions, fn?: SuiteFn): Promise<void>;
@@ -334,6 +339,7 @@ declare module "node:test" {
* This options is not compatible with `isolation='none'`. These variables will override
* those from the main process, and are not merged with `process.env`.
* @since v25.6.0
* @default process.env
*/
env?: NodeJS.ProcessEnv | undefined;
}
@@ -345,6 +351,7 @@ declare module "node:test" {
"test:diagnostic": [data: EventData.TestDiagnostic];
"test:enqueue": [data: EventData.TestEnqueue];
"test:fail": [data: EventData.TestFail];
"test:interrupted": [data: EventData.TestInterrupted];
"test:pass": [data: EventData.TestPass];
"test:plan": [data: EventData.TestPlan];
"test:start": [data: EventData.TestStart];
@@ -736,6 +743,13 @@ declare module "node:test" {
*/
skip?: string | boolean;
}
interface TestInterrupted {
/**
* An array of objects containing information about the
* interrupted tests.
*/
tests: TestStart[];
}
interface TestPass extends LocationInfo {
/**
* Additional execution metadata.
@@ -983,6 +997,34 @@ declare module "node:test" {
* @since v21.7.0, v20.12.0
*/
readonly attempt: number;
/**
* The unique identifier of the worker running the current test file. This value is
* derived from the `NODE_TEST_WORKER_ID` environment variable. When running tests
* with `--test-isolation=process` (the default), each test file runs in a separate
* child process and is assigned a worker ID from 1 to N, where N is the number of
* concurrent workers. When running with `--test-isolation=none`, all tests run in
* the same process and the worker ID is always 1. This value is `undefined` when
* not running in a test context.
*
* This property is useful for splitting resources (like database connections or
* server ports) across concurrent test files:
*
* ```js
* import { test } from 'node:test';
* import { process } from 'node:process';
*
* test('database operations', async (t) => {
* // Worker ID is available via context
* console.log(`Running in worker ${t.workerId}`);
*
* // Or via environment variable (available at import time)
* const workerId = process.env.NODE_TEST_WORKER_ID;
* // Use workerId to allocate separate resources per worker
* });
* ```
* @since v25.8.0
*/
readonly workerId: number | undefined;
/**
* This function is used to set the number of assertions and subtests that are expected to run
* within the test. If the number of assertions and subtests that run does not match the
@@ -1263,6 +1305,17 @@ declare module "node:test" {
* @default false
*/
concurrency?: number | boolean | undefined;
/**
* If truthy, the test is expected to fail. If a non-empty string is provided, that string is displayed
* in the test results as the reason why the test is expected to fail. If a
* `RegExp`, `Function`, `Object`, or `Error` is provided directly (without wrapping in `{ match: … }`), the test passes
* only if the thrown error matches, following the behavior of
* `assert.throws`. To provide both a reason and validation, pass an object
* with `label` (string) and `match` (RegExp, Function, Object, or Error).
* @since v25.5.0
* @default false
*/
expectFailure?: boolean | string | AssertPredicate | undefined;
/**
* If truthy, and the test context is configured to run `only` tests, then this test will be
* run. Otherwise, the test is skipped.
@@ -1301,8 +1354,6 @@ declare module "node:test" {
* @since v22.2.0
*/
plan?: number | undefined;
// added in v25.5.0, undocumented
expectFailure?: boolean | undefined;
}
/**
* This function creates a hook that runs before executing a suite.
@@ -1428,19 +1479,40 @@ declare module "node:test" {
*/
cache?: boolean | undefined;
/**
* The value to use as the mocked module's default export.
*
* If this value is not provided, ESM mocks do not include a default export.
* If the mock is a CommonJS or builtin module, this setting is used as the value of `module.exports`.
* If this value is not provided, CJS and builtin mocks use an empty object as the value of `module.exports`.
* Optional mocked exports. The `default` property, if
* provided, is used as the mocked module's default export. All other own
* enumerable properties are used as named exports.
* **This option cannot be used with `defaultExport` or `namedExports`.**
* * If the mock is a CommonJS or builtin module, `exports.default` is used as
* the value of `module.exports`.
* * If `exports.default` is not provided for a CommonJS or builtin mock,
* `module.exports` defaults to an empty object.
* * If named exports are provided with a non-object default export, the mock
* throws an exception when used as a CommonJS or builtin module.
*/
exports?: object | undefined;
/**
* An optional value used as the mocked module's default
* export. If this value is not provided, ESM mocks do not include a default
* export. If the mock is a CommonJS or builtin module, this setting is used as
* the value of `module.exports`. If this value is not provided, CJS and builtin
* mocks use an empty object as the value of `module.exports`.
* **This option cannot be used with `options.exports`.**
* This option is deprecated and will be removed in a later version.
* Prefer `options.exports.default`.
* @deprecated
*/
defaultExport?: any;
/**
* An object whose keys and values are used to create the named exports of the mock module.
*
* If the mock is a CommonJS or builtin module, these values are copied onto `module.exports`.
* Therefore, if a mock is created with both named exports and a non-object default export,
* the mock will throw an exception when used as a CJS or builtin module.
* An optional object whose keys and values are used to
* create the named exports of the mock module. If the mock is a CommonJS or
* builtin module, these values are copied onto `module.exports`. Therefore, if a
* mock is created with both named exports and a non-object default export, the
* mock will throw an exception when used as a CJS or builtin module.
* **This option cannot be used with `options.exports`.**
* This option is deprecated and will be removed in a later version.
* Prefer `options.exports`.
* @deprecated
*/
namedExports?: object | undefined;
}
@@ -1615,14 +1687,19 @@ declare module "node:test" {
* [`--experimental-test-module-mocks`](https://nodejs.org/docs/latest-v25.x/api/cli.html#--experimental-test-module-mocks)
* command-line flag.
*
* **Note**: [module customization hooks](https://nodejs.org/docs/latest-v25.x/api/module.html#customization-hooks) registered via the **synchronous** API effect resolution of
* the `specifier` provided to `mock.module`. Customization hooks registered via the **asynchronous**
* API are currently ignored (because the test runner's loader is synchronous, and node does not
* support multi-chain / cross-chain loading).
*
* The following example demonstrates how a mock is created for a module.
*
* ```js
* test('mocks a builtin module in both module systems', async (t) => {
* // Create a mock of 'node:readline' with a named export named 'fn', which
* // Create a mock of 'node:readline' with a named export named 'foo', which
* // does not exist in the original 'node:readline' module.
* const mock = t.mock.module('node:readline', {
* namedExports: { fn() { return 42; } },
* exports: { foo: () => 42 },
* });
*
* let esmImpl = await import('node:readline');
+1
View File
@@ -8,6 +8,7 @@ declare module "node:test/reporters" {
| { type: "test:diagnostic"; data: EventData.TestDiagnostic }
| { type: "test:enqueue"; data: EventData.TestEnqueue }
| { type: "test:fail"; data: EventData.TestFail }
| { type: "test:interrupted"; data: EventData.TestInterrupted }
| { type: "test:pass"; data: EventData.TestPass }
| { type: "test:plan"; data: EventData.TestPlan }
| { type: "test:start"; data: EventData.TestStart }
+2
View File
@@ -97,6 +97,7 @@
/// <reference path="../sqlite.d.ts" />
/// <reference path="../stream.d.ts" />
/// <reference path="../stream/consumers.d.ts" />
/// <reference path="../stream/iter.d.ts" />
/// <reference path="../stream/promises.d.ts" />
/// <reference path="../stream/web.d.ts" />
/// <reference path="../string_decoder.d.ts" />
@@ -115,3 +116,4 @@
/// <reference path="../wasi.d.ts" />
/// <reference path="../worker_threads.d.ts" />
/// <reference path="../zlib.d.ts" />
/// <reference path="../zlib/iter.d.ts" />
+2
View File
@@ -97,6 +97,7 @@
/// <reference path="../sqlite.d.ts" />
/// <reference path="../stream.d.ts" />
/// <reference path="../stream/consumers.d.ts" />
/// <reference path="../stream/iter.d.ts" />
/// <reference path="../stream/promises.d.ts" />
/// <reference path="../stream/web.d.ts" />
/// <reference path="../string_decoder.d.ts" />
@@ -115,3 +116,4 @@
/// <reference path="../wasi.d.ts" />
/// <reference path="../worker_threads.d.ts" />
/// <reference path="../zlib.d.ts" />
/// <reference path="../zlib/iter.d.ts" />
+27 -3
View File
@@ -229,9 +229,31 @@ declare module "node:url" {
* * `result` is returned.
* @since v0.1.25
* @legacy Use the WHATWG URL API instead.
* @param urlObject A URL object (as returned by `url.parse()` or constructed otherwise). If a string, it is converted to an object by passing it to `url.parse()`.
* @param urlObject A URL object (as returned by `url.parse()` or constructed otherwise).
*/
function format(urlObject: UrlObject | string): string;
function format(urlObject: UrlObject): string;
/**
* `url.format(urlString)` is shorthand for `url.format(url.parse(urlString))`.
*
* Because it invokes the deprecated `url.parse()` internally, passing a string argument
* to `url.format()` is itself deprecated.
*
* Canonicalizing a URL string can be performed using the WHATWG URL API, by
* constructing a new URL object and calling `url.toString()`.
*
* ```js
* import { URL } from 'node:url';
*
* const unformatted = 'http://[fe80:0:0:0:0:0:0:1]:/a/b?a=b#abc';
* const formatted = new URL(unformatted).toString();
*
* console.log(formatted); // Prints: http://[fe80::1]/a/b?a=b#abc
* ```
* @since v0.1.25
* @deprecated Use the WHATWG URL API instead.
* @param urlString A string that will be passed to `url.parse()` and then formatted.
*/
function format(urlString: string): string;
/**
* The `url.resolve()` method resolves a target URL relative to a base URL in a
* manner similar to that of a web browser resolving an anchor tag.
@@ -243,6 +265,8 @@ declare module "node:url" {
* url.resolve('http://example.com/one', '/two'); // 'http://example.com/two'
* ```
*
* Because it invokes the deprecated `url.parse()` internally, `url.resolve()` is itself deprecated.
*
* To achieve the same result using the WHATWG URL API:
*
* ```js
@@ -261,7 +285,7 @@ declare module "node:url" {
* resolve('http://example.com/one', '/two'); // 'http://example.com/two'
* ```
* @since v0.1.25
* @legacy Use the WHATWG URL API instead.
* @deprecated Use the WHATWG URL API instead.
* @param from The base URL to use if `to` is a relative URL.
* @param to The target URL to resolve.
*/
+55 -89
View File
@@ -200,16 +200,16 @@ declare module "node:vm" {
* The globals are contained in the `context` object.
*
* ```js
* import vm from 'node:vm';
* import { createContext, Script } from 'node:vm';
*
* const context = {
* animal: 'cat',
* count: 2,
* };
*
* const script = new vm.Script('count += 1; name = "kitty";');
* const script = new Script('count += 1; name = "kitty";');
*
* vm.createContext(context);
* createContext(context);
* for (let i = 0; i < 10; ++i) {
* script.runInContext(context);
* }
@@ -232,20 +232,21 @@ declare module "node:vm" {
*
* 1. Creates a new context.
* 2. If `contextObject` is an object, contextifies it with the new context.
* If `contextObject` is undefined, creates a new object and contextifies it.
* If `contextObject` is undefined, creates a new object and contextifies it.
* If `contextObject` is `vm.constants.DONT_CONTEXTIFY`, don't contextify anything.
* 3. Runs the compiled code contained by the `vm.Script` object within the created context. The code
* does not have access to the scope in which this method is called.
* 4. Returns the result.
* 3. Compiles the code as a `vm.Script`
* 4. Runs the compiled code within the created context. The code does not have access to the scope in
* which this method is called.
* 5. Returns the result.
*
* The following example compiles code that sets a global variable, then executes
* the code multiple times in different contexts. The globals are set on and
* contained within each individual `context`.
*
* ```js
* const vm = require('node:vm');
* import { constants, Script } from 'node:vm';
*
* const script = new vm.Script('globalVar = "set"');
* const script = new Script('globalVar = "set"');
*
* const contexts = [{}, {}, {}];
* contexts.forEach((context) => {
@@ -256,10 +257,10 @@ declare module "node:vm" {
* // Prints: [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]
*
* // This would throw if the context is created from a contextified object.
* // vm.constants.DONT_CONTEXTIFY allows creating contexts with ordinary
* // constants.DONT_CONTEXTIFY allows creating contexts with ordinary
* // global objects that can be frozen.
* const freezeScript = new vm.Script('Object.freeze(globalThis); globalThis;');
* const frozenContext = freezeScript.runInNewContext(vm.constants.DONT_CONTEXTIFY);
* const freezeScript = new Script('Object.freeze(globalThis); globalThis;');
* const frozenContext = freezeScript.runInNewContext(constants.DONT_CONTEXTIFY);
* ```
* @since v0.3.1
* @param contextObject Either `vm.constants.DONT_CONTEXTIFY` or an object that will be contextified.
@@ -278,11 +279,11 @@ declare module "node:vm" {
* executes that code multiple times:
*
* ```js
* import vm from 'node:vm';
* import { Script } from 'node:vm';
*
* global.globalVar = 0;
*
* const script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });
* const script = new Script('globalVar += 1', { filename: 'myfile.vm' });
*
* for (let i = 0; i < 1000; ++i) {
* script.runInThisContext();
@@ -371,14 +372,14 @@ declare module "node:vm" {
* variables will remain unchanged.
*
* ```js
* const vm = require('node:vm');
* import { createContext, runInContext } from 'node:vm';
*
* global.globalVar = 3;
*
* const context = { globalVar: 1 };
* vm.createContext(context);
* createContext(context);
*
* vm.runInContext('globalVar *= 2;', context);
* runInContext('globalVar *= 2;', context);
*
* console.log(context);
* // Prints: { globalVar: 2 }
@@ -429,13 +430,13 @@ declare module "node:vm" {
* The following example compiles and executes different scripts using a single `contextified` object:
*
* ```js
* import vm from 'node:vm';
* import { createContext, runInContext } from 'node:vm';
*
* const contextObject = { globalVar: 1 };
* vm.createContext(contextObject);
* createContext(contextObject);
*
* for (let i = 0; i < 10; ++i) {
* vm.runInContext('globalVar *= 2;', contextObject);
* runInContext('globalVar *= 2;', contextObject);
* }
* console.log(contextObject);
* // Prints: { globalVar: 1024 }
@@ -466,21 +467,24 @@ declare module "node:vm" {
* variable and sets a new one. These globals are contained in the `contextObject`.
*
* ```js
* const vm = require('node:vm');
* import { runInNewContext, constants } from 'node:vm';
*
* const contextObject = {
* animal: 'cat',
* count: 2,
* };
*
* vm.runInNewContext('count += 1; name = "kitty"', contextObject);
* runInNewContext('count += 1; name = "kitty"', contextObject);
* console.log(contextObject);
* // Prints: { animal: 'cat', count: 3, name: 'kitty' }
*
* // This would throw if the context is created from a contextified object.
* // vm.constants.DONT_CONTEXTIFY allows creating contexts with ordinary global objects that
* // can be frozen.
* const frozenContext = vm.runInNewContext('Object.freeze(globalThis); globalThis;', vm.constants.DONT_CONTEXTIFY);
* const frozenContext = runInNewContext(
* 'Object.freeze(globalThis); globalThis;',
* constants.DONT_CONTEXTIFY,
* );
* ```
* @since v0.3.1
* @param code The JavaScript code to compile and run.
@@ -504,10 +508,10 @@ declare module "node:vm" {
* the JavaScript [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) function to run the same code:
*
* ```js
* import vm from 'node:vm';
* import { runInThisContext } from 'node:vm';
* let localVar = 'initial value';
*
* const vmResult = vm.runInThisContext('localVar = "vm";');
* const vmResult = runInThisContext('localVar = "vm";');
* console.log(`vmResult: '${vmResult}', localVar: '${localVar}'`);
* // Prints: vmResult: 'vm', localVar: 'initial value'
*
@@ -519,38 +523,6 @@ declare module "node:vm" {
* Because `vm.runInThisContext()` does not have access to the local scope, `localVar` is unchanged. In contrast,
* [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) _does_ have access to the
* local scope, so the value `localVar` is changed. In this way `vm.runInThisContext()` is much like an [indirect `eval()` call](https://es5.github.io/#x10.4.2), e.g.`(0,eval)('code')`.
*
* ## Example: Running an HTTP server within a VM
*
* When using either `script.runInThisContext()` or {@link runInThisContext}, the code is executed within the current V8 global
* context. The code passed to this VM context will have its own isolated scope.
*
* In order to run a simple web server using the `node:http` module the code passed
* to the context must either import `node:http` on its own, or have a
* reference to the `node:http` module passed to it. For instance:
*
* ```js
* 'use strict';
* import vm from 'node:vm';
*
* const code = `
* ((require) => {
* const http = require('node:http');
*
* http.createServer((request, response) => {
* response.writeHead(200, { 'Content-Type': 'text/plain' });
* response.end('Hello World\\n');
* }).listen(8124);
*
* console.log('Server running at http://127.0.0.1:8124/');
* })`;
*
* vm.runInThisContext(code)(require);
* ```
*
* The `require()` in the above case shares the state with the context it is
* passed from. This may introduce risks when untrusted code is executed, e.g.
* altering objects in the context in unwanted ways.
* @since v0.3.1
* @param code The JavaScript code to compile and run.
* @return the result of the very last statement executed in the script.
@@ -582,44 +554,32 @@ declare module "node:vm" {
* the memory occupied by each heap space in the current V8 instance.
*
* ```js
* import vm from 'node:vm';
* import { createContext, measureMemory } from 'node:vm';
* // Measure the memory used by the main context.
* vm.measureMemory({ mode: 'summary' })
* measureMemory({ mode: 'summary' })
* // This is the same as vm.measureMemory()
* .then((result) => {
* // The current format is:
* // {
* // total: {
* // jsMemoryEstimate: 2418479, jsMemoryRange: [ 2418479, 2745799 ]
* // }
* // total: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
* // WebAssembly: { code: 0, metadata: 33962 },
* // }
* console.log(result);
* });
*
* const context = vm.createContext({ a: 1 });
* vm.measureMemory({ mode: 'detailed', execution: 'eager' })
* .then((result) => {
* // Reference the context here so that it won't be GC'ed
* // until the measurement is complete.
* console.log(context.a);
* // {
* // total: {
* // jsMemoryEstimate: 2574732,
* // jsMemoryRange: [ 2574732, 2904372 ]
* // },
* // current: {
* // jsMemoryEstimate: 2438996,
* // jsMemoryRange: [ 2438996, 2768636 ]
* // },
* // other: [
* // {
* // jsMemoryEstimate: 135736,
* // jsMemoryRange: [ 135736, 465376 ]
* // }
* // ]
* // }
* console.log(result);
* });
* const context = createContext({ a: 1 });
* measureMemory({ mode: 'detailed', execution: 'eager' }).then((result) => {
* // Reference the context here so that it won't be GC'ed
* // until the measurement is complete.
* console.log('Context:', context.a);
* // {
* // total: { jsMemoryEstimate: 1767100, jsMemoryRange: [1767100, 5440560] },
* // WebAssembly: { code: 0, metadata: 33962 },
* // current: { jsMemoryEstimate: 1601828, jsMemoryRange: [1601828, 5275288] },
* // other: [{ jsMemoryEstimate: 165272, jsMemoryRange: [Array] }],
* // }
* console.log(result);
* });
* ```
* @since v13.10.0
* @experimental
@@ -1092,15 +1052,21 @@ declare module "node:vm" {
* module graphs.
*
* ```js
* import vm from 'node:vm';
* import { SyntheticModule } from 'node:vm';
*
* const source = '{ "a": 1 }';
* const module = new vm.SyntheticModule(['default'], function() {
* const syntheticModule = new SyntheticModule(['default'], function() {
* const obj = JSON.parse(source);
* this.setExport('default', obj);
* });
*
* // Use `module` in linking...
* // Use `syntheticModule` in linking
* (async () => {
* await syntheticModule.link(() => {});
* await syntheticModule.evaluate();
*
* console.log('Default export:', syntheticModule.namespace.default);
* })();
* ```
* @since v13.0.0, v12.16.0
* @experimental
+131
View File
@@ -0,0 +1,131 @@
declare module "node:zlib/iter" {
import { StatefulTransform, SyncStatefulTransform } from "node:stream/iter";
interface BrotliOptions {
chunkSize?: number | undefined;
params?: { [key: number]: number | boolean } | undefined;
dictionary?: NodeJS.ArrayBufferView | undefined;
}
interface ZlibOptions {
chunkSize?: number | undefined;
windowBits?: number | undefined;
dictionary?: NodeJS.ArrayBufferView | undefined;
}
interface ZlibCompressionOptions extends ZlibOptions {
level?: number | undefined;
memLevel?: number | undefined;
strategy?: number | undefined;
}
interface ZstdOptions {
chunkSize?: number | undefined;
params?: { [key: number]: number | boolean } | undefined;
dictionary?: NodeJS.ArrayBufferView | undefined;
}
interface ZstdCompressionOptions extends ZstdOptions {
pledgedSrcSize?: number | undefined;
}
/**
* Create a Brotli compression transform. Output is compatible with
* `zlib.brotliDecompress()` and `decompressBrotli()`/`decompressBrotliSync()`.
* @since v25.9.0
* @returns A stateful transform.
*/
function compressBrotli(options?: BrotliOptions): StatefulTransform;
/**
* Create a Brotli compression transform. Output is compatible with
* `zlib.brotliDecompress()` and `decompressBrotli()`/`decompressBrotliSync()`.
* @since v25.9.0
* @returns A stateful transform.
*/
function compressBrotliSync(options?: BrotliOptions): SyncStatefulTransform;
/**
* Create a deflate compression transform. Output is compatible with
* `zlib.inflate()` and `decompressDeflate()`/`decompressDeflateSync()`.
* @since v25.9.0
* @returns A stateful transform.
*/
function compressDeflate(options?: ZlibCompressionOptions): StatefulTransform;
/**
* Create a deflate compression transform. Output is compatible with
* `zlib.inflate()` and `decompressDeflate()`/`decompressDeflateSync()`.
* @since v25.9.0
* @returns A stateful transform.
*/
function compressDeflateSync(options?: ZlibCompressionOptions): SyncStatefulTransform;
/**
* Create a gzip compression transform. Output is compatible with `zlib.gunzip()`
* and `decompressGzip()`/`decompressGzipSync()`.
* @returns A stateful transform.
*/
function compressGzip(options?: ZlibCompressionOptions): StatefulTransform;
/**
* Create a gzip compression transform. Output is compatible with `zlib.gunzip()`
* and `decompressGzip()`/`decompressGzipSync()`.
* @returns A stateful transform.
*/
function compressGzipSync(options?: ZlibCompressionOptions): SyncStatefulTransform;
/**
* Create a Zstandard compression transform. Output is compatible with
* `zlib.zstdDecompress()` and `decompressZstd()`/`decompressZstdSync()`.
* @since v25.9.0
* @returns A stateful transform.
*/
function compressZstd(options?: ZstdCompressionOptions): StatefulTransform;
/**
* Create a Zstandard compression transform. Output is compatible with
* `zlib.zstdDecompress()` and `decompressZstd()`/`decompressZstdSync()`.
* @since v25.9.0
* @returns A stateful transform.
*/
function compressZstdSync(options?: ZstdCompressionOptions): SyncStatefulTransform;
/**
* Create a Brotli decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressBrotli(options?: BrotliOptions): StatefulTransform;
/**
* Create a Brotli decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressBrotliSync(options?: BrotliOptions): SyncStatefulTransform;
/**
* Create a deflate decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressDeflate(options?: ZlibOptions): StatefulTransform;
/**
* Create a deflate decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressDeflateSync(options?: ZlibOptions): SyncStatefulTransform;
/**
* Create a gzip decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressGzip(options?: ZlibOptions): StatefulTransform;
/**
* Create a gzip decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressGzipSync(options?: ZlibOptions): SyncStatefulTransform;
/**
* Create a Zstandard decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressZstd(options?: ZstdOptions): StatefulTransform;
/**
* Create a Zstandard decompression transform.
* @since v25.9.0
* @returns A stateful transform.
*/
function decompressZstdSync(options?: ZstdOptions): SyncStatefulTransform;
}
declare module "zlib/iter" {
export * from "node:zlib/iter";
}
+216
View File
@@ -0,0 +1,216 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.1] - 2020-08-29
### Fixed
- Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150.
## [2.0.0] - 2020-08-14
### Changed
- Full rewrite. Now port from python 3.9.0 & more precise following.
See [doc](./doc) for difference and migration info.
- node.js 10+ required
- Removed most of local docs in favour of original ones.
## [1.0.10] - 2018-02-15
### Fixed
- Use .concat instead of + for arrays, #122.
## [1.0.9] - 2016-09-29
### Changed
- Rerelease after 1.0.8 - deps cleanup.
## [1.0.8] - 2016-09-29
### Changed
- Maintenance (deps bump, fix node 6.5+ tests, coverage report).
## [1.0.7] - 2016-03-17
### Changed
- Teach `addArgument` to accept string arg names. #97, @tomxtobin.
## [1.0.6] - 2016-02-06
### Changed
- Maintenance: moved to eslint & updated CS.
## [1.0.5] - 2016-02-05
### Changed
- Removed lodash dependency to significantly reduce install size.
Thanks to @mourner.
## [1.0.4] - 2016-01-17
### Changed
- Maintenance: lodash update to 4.0.0.
## [1.0.3] - 2015-10-27
### Fixed
- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple.
## [1.0.2] - 2015-03-22
### Changed
- Relaxed lodash version dependency.
## [1.0.1] - 2015-02-20
### Changed
- Changed dependencies to be compatible with ancient nodejs.
## [1.0.0] - 2015-02-19
### Changed
- Maintenance release.
- Replaced `underscore` with `lodash`.
- Bumped version to 1.0.0 to better reflect semver meaning.
- HISTORY.md -> CHANGELOG.md
## [0.1.16] - 2013-12-01
### Changed
- Maintenance release. Updated dependencies and docs.
## [0.1.15] - 2013-05-13
### Fixed
- Fixed #55, @trebor89
## [0.1.14] - 2013-05-12
### Fixed
- Fixed #62, @maxtaco
## [0.1.13] - 2013-04-08
### Changed
- Added `.npmignore` to reduce package size
## [0.1.12] - 2013-02-10
### Fixed
- Fixed conflictHandler (#46), @hpaulj
## [0.1.11] - 2013-02-07
### Added
- Added 70+ tests (ported from python), @hpaulj
- Added conflictHandler, @applepicke
- Added fromfilePrefixChar, @hpaulj
### Fixed
- Multiple bugfixes, @hpaulj
## [0.1.10] - 2012-12-30
### Added
- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion)
support, thanks to @hpaulj
### Fixed
- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj
## [0.1.9] - 2012-12-27
### Fixed
- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj
- Fixed default value behavior with `*` positionals, thanks to @hpaulj
- Improve `getDefault()` behavior, thanks to @hpaulj
- Improve negative argument parsing, thanks to @hpaulj
## [0.1.8] - 2012-12-01
### Fixed
- Fixed parser parents (issue #19), thanks to @hpaulj
- Fixed negative argument parse (issue #20), thanks to @hpaulj
## [0.1.7] - 2012-10-14
### Fixed
- Fixed 'choices' argument parse (issue #16)
- Fixed stderr output (issue #15)
## [0.1.6] - 2012-09-09
### Fixed
- Fixed check for conflict of options (thanks to @tomxtobin)
## [0.1.5] - 2012-09-03
### Fixed
- Fix parser #setDefaults method (thanks to @tomxtobin)
## [0.1.4] - 2012-07-30
### Fixed
- Fixed pseudo-argument support (thanks to @CGamesPlay)
- Fixed addHelp default (should be true), if not set (thanks to @benblank)
## [0.1.3] - 2012-06-27
### Fixed
- Fixed formatter api name: Formatter -> HelpFormatter
## [0.1.2] - 2012-05-29
### Fixed
- Removed excess whitespace in help
- Fixed error reporting, when parcer with subcommands
called with empty arguments
### Added
- Added basic tests
## [0.1.1] - 2012-05-23
### Fixed
- Fixed line wrapping in help formatter
- Added better error reporting on invalid arguments
## [0.1.0] - 2012-05-16
### Added
- First release.
[2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0
[1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10
[1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9
[1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8
[1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7
[1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6
[1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5
[1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4
[1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3
[1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0
[0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16
[0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15
[0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14
[0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13
[0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12
[0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11
[0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10
[0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9
[0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8
[0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7
[0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6
[0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5
[0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4
[0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3
[0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2
[0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1
[0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0
+254
View File
@@ -0,0 +1,254 @@
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the Internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the Internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+84
View File
@@ -0,0 +1,84 @@
argparse
========
[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse)
[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse)
CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)).
**Difference with original.**
- JS has no keyword arguments support.
- Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`.
- JS has no python's types `int`, `float`, ...
- Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`.
- `%r` format specifier uses `require('util').inspect()`.
More details in [doc](./doc).
Example
-------
`test.js` file:
```javascript
#!/usr/bin/env node
'use strict';
const { ArgumentParser } = require('argparse');
const { version } = require('./package.json');
const parser = new ArgumentParser({
description: 'Argparse example'
});
parser.add_argument('-v', '--version', { action: 'version', version });
parser.add_argument('-f', '--foo', { help: 'foo bar' });
parser.add_argument('-b', '--bar', { help: 'bar foo' });
parser.add_argument('--baz', { help: 'baz bar' });
console.dir(parser.parse_args());
```
Display help:
```
$ ./test.js -h
usage: test.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ]
Argparse example
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-f FOO, --foo FOO foo bar
-b BAR, --bar BAR bar foo
--baz BAZ baz bar
```
Parse arguments:
```
$ ./test.js -f=3 --bar=4 --baz 5
{ foo: '3', bar: '4', baz: '5' }
```
API docs
--------
Since this is a port with minimal divergence, there's no separate documentation.
Use original one instead, with notes about difference.
1. [Original doc](https://docs.python.org/3.9/library/argparse.html).
2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html).
3. [Difference with python](./doc).
argparse for enterprise
-----------------------
Available as part of the Tidelift Subscription
The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
+3707
View File
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
// Limited implementation of python % string operator, supports only %s and %r for now
// (other formats are not used here, but may appear in custom templates)
'use strict'
const { inspect } = require('util')
module.exports = function sub(pattern, ...values) {
let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g
let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) {
if (is_literal) return '%'
let padded_count = 0
if (is_padded) {
if (values.length === 0) throw new TypeError('not enough arguments for format string')
padded_count = values.shift()
if (!Number.isInteger(padded_count)) throw new TypeError('* wants int')
}
let str
if (name !== undefined) {
let dict = values[0]
if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping')
if (!(name in dict)) throw new TypeError(`no such key: '${name}'`)
str = dict[name]
} else {
if (values.length === 0) throw new TypeError('not enough arguments for format string')
str = values.shift()
}
switch (format) {
case 's':
str = String(str)
break
case 'r':
str = inspect(str)
break
case 'd':
case 'i':
if (typeof str !== 'number') {
throw new TypeError(`%${format} format: a number is required, not ${typeof str}`)
}
str = String(str.toFixed(0))
break
default:
throw new TypeError(`unsupported format character '${format}'`)
}
if (padded_count > 0) {
return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count)
} else {
return str
}
})
if (values.length) {
if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) {
// mapping
} else {
throw new TypeError('not all arguments converted during string formatting')
}
}
return result
}
+440
View File
@@ -0,0 +1,440 @@
// Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions):
// https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py
'use strict'
/*
* Text wrapping and filling.
*/
// Copyright (C) 1999-2001 Gregory P. Ward.
// Copyright (C) 2002, 2003 Python Software Foundation.
// Copyright (C) 2020 argparse.js authors
// Originally written by Greg Ward <gward@python.net>
// Hardcode the recognized whitespace characters to the US-ASCII
// whitespace characters. The main reason for doing this is that
// some Unicode spaces (like \u00a0) are non-breaking whitespaces.
//
// This less funky little regex just split on recognized spaces. E.g.
// "Hello there -- you goof-ball, use the -b option!"
// splits into
// Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/
const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/
class TextWrapper {
/*
* Object for wrapping/filling text. The public interface consists of
* the wrap() and fill() methods; the other methods are just there for
* subclasses to override in order to tweak the default behaviour.
* If you want to completely replace the main wrapping algorithm,
* you'll probably have to override _wrap_chunks().
*
* Several instance attributes control various aspects of wrapping:
* width (default: 70)
* the maximum width of wrapped lines (unless break_long_words
* is false)
* initial_indent (default: "")
* string that will be prepended to the first line of wrapped
* output. Counts towards the line's width.
* subsequent_indent (default: "")
* string that will be prepended to all lines save the first
* of wrapped output; also counts towards each line's width.
* expand_tabs (default: true)
* Expand tabs in input text to spaces before further processing.
* Each tab will become 0 .. 'tabsize' spaces, depending on its position
* in its line. If false, each tab is treated as a single character.
* tabsize (default: 8)
* Expand tabs in input text to 0 .. 'tabsize' spaces, unless
* 'expand_tabs' is false.
* replace_whitespace (default: true)
* Replace all whitespace characters in the input text by spaces
* after tab expansion. Note that if expand_tabs is false and
* replace_whitespace is true, every tab will be converted to a
* single space!
* fix_sentence_endings (default: false)
* Ensure that sentence-ending punctuation is always followed
* by two spaces. Off by default because the algorithm is
* (unavoidably) imperfect.
* break_long_words (default: true)
* Break words longer than 'width'. If false, those words will not
* be broken, and some lines might be longer than 'width'.
* break_on_hyphens (default: true)
* Allow breaking hyphenated words. If true, wrapping will occur
* preferably on whitespaces and right after hyphens part of
* compound words.
* drop_whitespace (default: true)
* Drop leading and trailing whitespace from lines.
* max_lines (default: None)
* Truncate wrapped lines.
* placeholder (default: ' [...]')
* Append to the last line of truncated text.
*/
constructor(options = {}) {
let {
width = 70,
initial_indent = '',
subsequent_indent = '',
expand_tabs = true,
replace_whitespace = true,
fix_sentence_endings = false,
break_long_words = true,
drop_whitespace = true,
break_on_hyphens = true,
tabsize = 8,
max_lines = undefined,
placeholder=' [...]'
} = options
this.width = width
this.initial_indent = initial_indent
this.subsequent_indent = subsequent_indent
this.expand_tabs = expand_tabs
this.replace_whitespace = replace_whitespace
this.fix_sentence_endings = fix_sentence_endings
this.break_long_words = break_long_words
this.drop_whitespace = drop_whitespace
this.break_on_hyphens = break_on_hyphens
this.tabsize = tabsize
this.max_lines = max_lines
this.placeholder = placeholder
}
// -- Private methods -----------------------------------------------
// (possibly useful for subclasses to override)
_munge_whitespace(text) {
/*
* _munge_whitespace(text : string) -> string
*
* Munge whitespace in text: expand tabs and convert all other
* whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz"
* becomes " foo bar baz".
*/
if (this.expand_tabs) {
text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js
}
if (this.replace_whitespace) {
text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ')
}
return text
}
_split(text) {
/*
* _split(text : string) -> [string]
*
* Split the text to wrap into indivisible chunks. Chunks are
* not quite the same as words; see _wrap_chunks() for full
* details. As an example, the text
* Look, goof-ball -- use the -b option!
* breaks into the following chunks:
* 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ',
* 'use', ' ', 'the', ' ', '-b', ' ', 'option!'
* if break_on_hyphens is True, or in:
* 'Look,', ' ', 'goof-ball', ' ', '--', ' ',
* 'use', ' ', 'the', ' ', '-b', ' ', option!'
* otherwise.
*/
let chunks = text.split(wordsep_simple_re)
chunks = chunks.filter(Boolean)
return chunks
}
_handle_long_word(reversed_chunks, cur_line, cur_len, width) {
/*
* _handle_long_word(chunks : [string],
* cur_line : [string],
* cur_len : int, width : int)
*
* Handle a chunk of text (most likely a word, not whitespace) that
* is too long to fit in any line.
*/
// Figure out when indent is larger than the specified width, and make
// sure at least one character is stripped off on every pass
let space_left
if (width < 1) {
space_left = 1
} else {
space_left = width - cur_len
}
// If we're allowed to break long words, then do so: put as much
// of the next chunk onto the current line as will fit.
if (this.break_long_words) {
cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left))
reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left)
// Otherwise, we have to preserve the long word intact. Only add
// it to the current line if there's nothing already there --
// that minimizes how much we violate the width constraint.
} else if (!cur_line) {
cur_line.push(...reversed_chunks.pop())
}
// If we're not allowed to break long words, and there's already
// text on the current line, do nothing. Next time through the
// main loop of _wrap_chunks(), we'll wind up here again, but
// cur_len will be zero, so the next line will be entirely
// devoted to the long word that we can't handle right now.
}
_wrap_chunks(chunks) {
/*
* _wrap_chunks(chunks : [string]) -> [string]
*
* Wrap a sequence of text chunks and return a list of lines of
* length 'self.width' or less. (If 'break_long_words' is false,
* some lines may be longer than this.) Chunks correspond roughly
* to words and the whitespace between them: each chunk is
* indivisible (modulo 'break_long_words'), but a line break can
* come between any two chunks. Chunks should not have internal
* whitespace; ie. a chunk is either all whitespace or a "word".
* Whitespace chunks will be removed from the beginning and end of
* lines, but apart from that whitespace is preserved.
*/
let lines = []
let indent
if (this.width <= 0) {
throw Error(`invalid width ${this.width} (must be > 0)`)
}
if (this.max_lines !== undefined) {
if (this.max_lines > 1) {
indent = this.subsequent_indent
} else {
indent = this.initial_indent
}
if (indent.length + this.placeholder.trimStart().length > this.width) {
throw Error('placeholder too large for max width')
}
}
// Arrange in reverse order so items can be efficiently popped
// from a stack of chucks.
chunks = chunks.reverse()
while (chunks.length > 0) {
// Start the list of chunks that will make up the current line.
// cur_len is just the length of all the chunks in cur_line.
let cur_line = []
let cur_len = 0
// Figure out which static string will prefix this line.
let indent
if (lines) {
indent = this.subsequent_indent
} else {
indent = this.initial_indent
}
// Maximum width for this line.
let width = this.width - indent.length
// First chunk on line is whitespace -- drop it, unless this
// is the very beginning of the text (ie. no lines started yet).
if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) {
chunks.pop()
}
while (chunks.length > 0) {
let l = chunks[chunks.length - 1].length
// Can at least squeeze this chunk onto the current line.
if (cur_len + l <= width) {
cur_line.push(chunks.pop())
cur_len += l
// Nope, this line is full.
} else {
break
}
}
// The current line is full, and the next chunk is too big to
// fit on *any* line (not just this one).
if (chunks.length && chunks[chunks.length - 1].length > width) {
this._handle_long_word(chunks, cur_line, cur_len, width)
cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0)
}
// If the last chunk on this line is all whitespace, drop it.
if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') {
cur_len -= cur_line[cur_line.length - 1].length
cur_line.pop()
}
if (cur_line) {
if (this.max_lines === undefined ||
lines.length + 1 < this.max_lines ||
(chunks.length === 0 ||
this.drop_whitespace &&
chunks.length === 1 &&
!chunks[0].trim()) && cur_len <= width) {
// Convert current line back to a string and store it in
// list of all lines (return value).
lines.push(indent + cur_line.join(''))
} else {
let had_break = false
while (cur_line) {
if (cur_line[cur_line.length - 1].trim() &&
cur_len + this.placeholder.length <= width) {
cur_line.push(this.placeholder)
lines.push(indent + cur_line.join(''))
had_break = true
break
}
cur_len -= cur_line[-1].length
cur_line.pop()
}
if (!had_break) {
if (lines) {
let prev_line = lines[lines.length - 1].trimEnd()
if (prev_line.length + this.placeholder.length <=
this.width) {
lines[lines.length - 1] = prev_line + this.placeholder
break
}
}
lines.push(indent + this.placeholder.lstrip())
}
break
}
}
}
return lines
}
_split_chunks(text) {
text = this._munge_whitespace(text)
return this._split(text)
}
// -- Public interface ----------------------------------------------
wrap(text) {
/*
* wrap(text : string) -> [string]
*
* Reformat the single paragraph in 'text' so it fits in lines of
* no more than 'self.width' columns, and return a list of wrapped
* lines. Tabs in 'text' are expanded with string.expandtabs(),
* and all other whitespace characters (including newline) are
* converted to space.
*/
let chunks = this._split_chunks(text)
// not implemented in js
//if (this.fix_sentence_endings) {
// this._fix_sentence_endings(chunks)
//}
return this._wrap_chunks(chunks)
}
fill(text) {
/*
* fill(text : string) -> string
*
* Reformat the single paragraph in 'text' to fit in lines of no
* more than 'self.width' columns, and return a new string
* containing the entire wrapped paragraph.
*/
return this.wrap(text).join('\n')
}
}
// -- Convenience interface ---------------------------------------------
function wrap(text, options = {}) {
/*
* Wrap a single paragraph of text, returning a list of wrapped lines.
*
* Reformat the single paragraph in 'text' so it fits in lines of no
* more than 'width' columns, and return a list of wrapped lines. By
* default, tabs in 'text' are expanded with string.expandtabs(), and
* all other whitespace characters (including newline) are converted to
* space. See TextWrapper class for available keyword args to customize
* wrapping behaviour.
*/
let { width = 70, ...kwargs } = options
let w = new TextWrapper(Object.assign({ width }, kwargs))
return w.wrap(text)
}
function fill(text, options = {}) {
/*
* Fill a single paragraph of text, returning a new string.
*
* Reformat the single paragraph in 'text' to fit in lines of no more
* than 'width' columns, and return a new string containing the entire
* wrapped paragraph. As with wrap(), tabs are expanded and other
* whitespace characters converted to space. See TextWrapper class for
* available keyword args to customize wrapping behaviour.
*/
let { width = 70, ...kwargs } = options
let w = new TextWrapper(Object.assign({ width }, kwargs))
return w.fill(text)
}
// -- Loosely related functionality -------------------------------------
let _whitespace_only_re = /^[ \t]+$/mg
let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg
function dedent(text) {
/*
* Remove any common leading whitespace from every line in `text`.
*
* This can be used to make triple-quoted strings line up with the left
* edge of the display, while still presenting them in the source code
* in indented form.
*
* Note that tabs and spaces are both treated as whitespace, but they
* are not equal: the lines " hello" and "\\thello" are
* considered to have no common leading whitespace.
*
* Entirely blank lines are normalized to a newline character.
*/
// Look for the longest leading string of spaces and tabs common to
// all lines.
let margin = undefined
text = text.replace(_whitespace_only_re, '')
let indents = text.match(_leading_whitespace_re) || []
for (let indent of indents) {
indent = indent.slice(0, -1)
if (margin === undefined) {
margin = indent
// Current line more deeply indented than previous winner:
// no change (previous winner is still on top).
} else if (indent.startsWith(margin)) {
// pass
// Current line consistent with and no deeper than previous winner:
// it's the new winner.
} else if (margin.startsWith(indent)) {
margin = indent
// Find the largest common whitespace between current line and previous
// winner.
} else {
for (let i = 0; i < margin.length && i < indent.length; i++) {
if (margin[i] !== indent[i]) {
margin = margin.slice(0, i)
break
}
}
}
}
if (margin) {
text = text.replace(new RegExp('^' + margin, 'mg'), '')
}
return text
}
module.exports = { wrap, fill, dedent }
+31
View File
@@ -0,0 +1,31 @@
{
"name": "argparse",
"description": "CLI arguments parser. Native port of python's argparse.",
"version": "2.0.1",
"keywords": [
"cli",
"parser",
"argparse",
"option",
"args"
],
"main": "argparse.js",
"files": [
"argparse.js",
"lib/"
],
"license": "Python-2.0",
"repository": "nodeca/argparse",
"scripts": {
"lint": "eslint .",
"test": "npm run lint && nyc mocha",
"coverage": "npm run test && nyc report --reporter html"
},
"devDependencies": {
"@babel/eslint-parser": "^7.11.0",
"@babel/plugin-syntax-class-properties": "^7.10.4",
"eslint": "^7.5.0",
"mocha": "^8.0.1",
"nyc": "^15.1.0"
}
}
+7 -5
View File
@@ -1,6 +1,6 @@
{
"name": "bare-events",
"version": "2.8.2",
"version": "2.8.3",
"description": "Event emitters for JavaScript",
"exports": {
"./package": "./package.json",
@@ -28,10 +28,11 @@
"lib"
],
"scripts": {
"test": "npm run lint && npm run test:bare && npm run test:node",
"test:bare": "bare test.js",
"test:node": "node test.js",
"lint": "prettier . --check"
"format": "prettier --write . && lunte --fix",
"lint": "prettier --check . && lunte",
"test": "npm run test:bare && npm run test:node",
"test:bare": "brittle-bare --coverage test.js",
"test:node": "brittle-node --coverage test.js"
},
"repository": {
"type": "git",
@@ -46,6 +47,7 @@
"devDependencies": {
"bare-abort-controller": "^1.0.0",
"brittle": "^3.3.2",
"lunte": "^1.8.0",
"prettier": "^3.4.2",
"prettier-config-holepunch": "^2.0.0",
"uncaughts": "^1.1.1"
+32 -14
View File
@@ -10,6 +10,7 @@ const STOP = 0x20
const CAPTURE = 0x1
const PASSIVE = 0x2
const ONCE = 0x4
const REMOVED = 0x8
// https://dom.spec.whatwg.org/#event
class Event {
@@ -144,8 +145,7 @@ exports.EventTarget = class EventTarget {
const listeners = this._listeners.get(type)
if (listeners === undefined) this._listeners.set(type, listener)
else {
if (listeners !== undefined) {
for (const existing of listeners) {
if (callback === existing.callback && capture === existing.capture) {
return // Duplicate listener
@@ -153,13 +153,16 @@ exports.EventTarget = class EventTarget {
}
listener.link(listeners)
} else {
this._listeners.set(type, listener)
}
if (signal !== null) {
signal.addEventListener('abort', onabort)
if (signal !== null) {
const self = this
signal.addEventListener('abort', onabort)
function onabort() {
listener.unlink()
}
function onabort() {
self._unlink(type, listener)
}
}
}
@@ -176,10 +179,7 @@ exports.EventTarget = class EventTarget {
for (const existing of listeners) {
if (callback === existing.callback && capture === existing.capture) {
const next = existing.unlink()
if (listeners === existing) this._listeners.set(type, next)
this._unlink(type, existing)
return
}
}
@@ -195,10 +195,14 @@ exports.EventTarget = class EventTarget {
try {
if (listeners === undefined) return true
for (const listener of listeners) {
const snapshot = Array.from(listeners)
for (const listener of snapshot) {
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
if (listener.once) listener.unlink()
if (listener.removed) continue
if (listener.once) this._unlink(event.type, listener)
let callback = listener.callback
let context = this
@@ -229,6 +233,18 @@ exports.EventTarget = class EventTarget {
__proto__: { constructor: EventTarget }
}
}
_unlink(type, listener) {
if (listener.removed) return
const head = this._listeners.get(type)
const next = listener.unlink()
if (head === listener) {
if (next === listener) this._listeners.delete(type)
else this._listeners.set(type, next)
}
}
}
// https://dom.spec.whatwg.org/#concept-event-listener
@@ -268,7 +284,7 @@ class EventListener {
}
get removed() {
return this._previous === this && this._next === this
return (this._state & REMOVED) !== 0
}
link(listener) {
@@ -287,6 +303,8 @@ class EventListener {
unlink() {
if (this.removed) return this
this._state |= REMOVED
const next = this._next
const previous = this._previous
+96
View File
@@ -0,0 +1,96 @@
declare namespace callsites {
interface CallSite {
/**
Returns the value of `this`.
*/
getThis(): unknown | undefined;
/**
Returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property.
*/
getTypeName(): string | null;
/**
Returns the current function.
*/
getFunction(): Function | undefined;
/**
Returns the name of the current function, typically its `name` property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
*/
getFunctionName(): string | null;
/**
Returns the name of the property of `this` or one of its prototypes that holds the current function.
*/
getMethodName(): string | undefined;
/**
Returns the name of the script if this function was defined in a script.
*/
getFileName(): string | null;
/**
Returns the current line number if this function was defined in a script.
*/
getLineNumber(): number | null;
/**
Returns the current column number if this function was defined in a script.
*/
getColumnNumber(): number | null;
/**
Returns a string representing the location where `eval` was called if this function was created using a call to `eval`.
*/
getEvalOrigin(): string | undefined;
/**
Returns `true` if this is a top-level invocation, that is, if it's a global object.
*/
isToplevel(): boolean;
/**
Returns `true` if this call takes place in code defined by a call to `eval`.
*/
isEval(): boolean;
/**
Returns `true` if this call is in native V8 code.
*/
isNative(): boolean;
/**
Returns `true` if this is a constructor call.
*/
isConstructor(): boolean;
}
}
declare const callsites: {
/**
Get callsites from the V8 stack trace API.
@returns An array of `CallSite` objects.
@example
```
import callsites = require('callsites');
function unicorn() {
console.log(callsites()[0].getFileName());
//=> '/Users/sindresorhus/dev/callsites/test.js'
}
unicorn();
```
*/
(): callsites.CallSite[];
// TODO: Remove this for the next major release, refactor the whole definition to:
// declare function callsites(): callsites.CallSite[];
// export = callsites;
default: typeof callsites;
};
export = callsites;
+13
View File
@@ -0,0 +1,13 @@
'use strict';
const callsites = () => {
const _prepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const stack = new Error().stack.slice(1);
Error.prepareStackTrace = _prepareStackTrace;
return stack;
};
module.exports = callsites;
// TODO: Remove this for the next major release
module.exports.default = callsites;
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+39
View File
@@ -0,0 +1,39 @@
{
"name": "callsites",
"version": "3.1.0",
"description": "Get callsites from the V8 stack trace API",
"license": "MIT",
"repository": "sindresorhus/callsites",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=6"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"stacktrace",
"v8",
"callsite",
"callsites",
"stack",
"trace",
"function",
"file",
"line",
"debug"
],
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
}
}
+48
View File
@@ -0,0 +1,48 @@
# callsites [![Build Status](https://travis-ci.org/sindresorhus/callsites.svg?branch=master)](https://travis-ci.org/sindresorhus/callsites)
> Get callsites from the [V8 stack trace API](https://v8.dev/docs/stack-trace-api)
## Install
```
$ npm install callsites
```
## Usage
```js
const callsites = require('callsites');
function unicorn() {
console.log(callsites()[0].getFileName());
//=> '/Users/sindresorhus/dev/callsites/test.js'
}
unicorn();
```
## API
Returns an array of callsite objects with the following methods:
- `getThis`: returns the value of `this`.
- `getTypeName`: returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property.
- `getFunction`: returns the current function.
- `getFunctionName`: returns the name of the current function, typically its `name` property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
- `getMethodName`: returns the name of the property of `this` or one of its prototypes that holds the current function.
- `getFileName`: if this function was defined in a script returns the name of the script.
- `getLineNumber`: if this function was defined in a script returns the current line number.
- `getColumnNumber`: if this function was defined in a script returns the current column number
- `getEvalOrigin`: if this function was created using a call to `eval` returns a string representing the location where `eval` was called.
- `isToplevel`: is this a top-level invocation, that is, is this the global object?
- `isEval`: does this call take place in code defined by a call to `eval`?
- `isNative`: is this call in native V8 code?
- `isConstructor`: is this a constructor call?
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)
-202
View File
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-770
View File
@@ -1,770 +0,0 @@
# Chrome DevTools MCP
[![npm chrome-devtools-mcp package](https://img.shields.io/npm/v/chrome-devtools-mcp.svg)](https://npmjs.org/package/chrome-devtools-mcp)
`chrome-devtools-mcp` lets your coding agent (such as Gemini, Claude, Cursor or Copilot)
control and inspect a live Chrome browser. It acts as a Model-Context-Protocol
(MCP) server, giving your AI coding assistant access to the full power of
Chrome DevTools for reliable automation, in-depth debugging, and performance analysis.
## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design Principles](./docs/design-principles.md)
## Key features
- **Get performance insights**: Uses [Chrome
DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record
traces and extract actionable performance insights.
- **Advanced browser debugging**: Analyze network requests, take screenshots and
check browser console messages (with source-mapped stack traces).
- **Reliable automation**. Uses
[puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in
Chrome and automatically wait for action results.
## Disclaimers
`chrome-devtools-mcp` exposes content of the browser instance to the MCP clients
allowing them to inspect, debug, and modify any data in the browser or DevTools.
Avoid sharing sensitive or personal information that you don't want to share with
MCP clients.
`chrome-devtools-mcp` officially supports Google Chrome and [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) only.
Other Chromium-based browser may work, but this is not guaranteed, and you may encounter unexpected behavior. Use at your own discretion.
We are committed to providing fixes and support for the latest version of [Extended Stable Chrome](https://chromiumdash.appspot.com/schedule).
Performance tools may send trace URLs to the Google CrUX API to fetch real-user
experience data. This helps provide a holistic performance picture by
presenting field data alongside lab data. This data is collected by the [Chrome
User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable
this, run with the `--no-performance-crux` flag.
## **Usage statistics**
Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
Data collection is **enabled by default**. You can opt-out by passing the `--no-usage-statistics` flag when starting the server:
```json
"args": ["-y", "chrome-devtools-mcp@latest", "--no-usage-statistics"]
```
Google handles this data in accordance with the [Google Privacy Policy](https://policies.google.com/privacy).
Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa.
Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
## Requirements
- [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
- [Chrome](https://www.google.com/chrome/) current stable version or newer.
- [npm](https://www.npmjs.com/)
## Getting started
Add the following config to your MCP client:
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"]
}
}
}
```
> [!NOTE]
> Using `chrome-devtools-mcp@latest` ensures that your MCP client will always use the latest version of the Chrome DevTools MCP server.
If you are interested in doing only basic browser tasks, use the `--slim` mode:
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest", "--slim", "--headless"]
}
}
}
```
See [Slim tool reference](./docs/slim-tool-reference.md).
### MCP Client configuration
<details>
<summary>Amp</summary>
Follow https://ampcode.com/manual#mcp and use the config provided above. You can also install the Chrome DevTools MCP server using the CLI:
```bash
amp mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
```
</details>
<details>
<summary>Antigravity</summary>
To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs</a> to install a custom MCP server. Add the following config to the MCP servers config:
```bash
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--browser-url=http://127.0.0.1:9222",
"-y"
]
}
}
}
```
This will make the Chrome DevTools MCP server automatically connect to the browser that Antigravity is using. If you are not using port 9222, make sure to adjust accordingly.
Chrome DevTools MCP will not start the browser instance automatically using this approach because the Chrome DevTools MCP server connects to Antigravity's built-in browser. If the browser is not already running, you have to start it first by clicking the Chrome icon at the top right corner.
</details>
<details>
<summary>Claude Code</summary>
**Install via CLI (MCP only)**
Use the Claude Code CLI to add the Chrome DevTools MCP server (<a href="https://code.claude.com/docs/en/mcp">guide</a>):
```bash
claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
```
**Install as a Plugin (MCP + Skills)**
> [!NOTE]
> If you already had Chrome DevTools MCP installed previously for Claude Code, make sure to remove it first from your installation and configuration files.
To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code:
```sh
/plugin marketplace add ChromeDevTools/chrome-devtools-mcp
```
Then, install the plugin:
```sh
/plugin install chrome-devtools-mcp
```
Restart Claude Code to have the MCP server and skills load (check with `/skills`).
> [!TIP]
> If the plugin installation fails with a `Failed to clone repository` error (e.g., HTTPS connectivity issues behind a corporate firewall), see the [troubleshooting guide](./docs/troubleshooting.md#claude-code-plugin-installation-fails-with-failed-to-clone-repository) for workarounds, or use the CLI installation method above instead.
</details>
<details>
<summary>Cline</summary>
Follow https://docs.cline.bot/mcp/configuring-mcp-servers and use the config provided above.
</details>
<details>
<summary>Codex</summary>
Follow the <a href="https://developers.openai.com/codex/mcp/#configure-with-the-cli">configure MCP guide</a>
using the standard config from above. You can also install the Chrome DevTools MCP server using the Codex CLI:
```bash
codex mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
```
**On Windows 11**
Configure the Chrome install location and increase the startup timeout by updating `.codex/config.toml` and adding the following `env` and `startup_timeout_ms` parameters:
```
[mcp_servers.chrome-devtools]
command = "cmd"
args = [
"/c",
"npx",
"-y",
"chrome-devtools-mcp@latest",
]
env = { SystemRoot="C:\\Windows", PROGRAMFILES="C:\\Program Files" }
startup_timeout_ms = 20_000
```
</details>
<details>
<summary>Command Code</summary>
Use the Command Code CLI to add the Chrome DevTools MCP server (<a href="https://commandcode.ai/docs/mcp">MCP guide</a>):
```bash
cmd mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
```
</details>
<details>
<summary>Copilot CLI</summary>
Start Copilot CLI:
```
copilot
```
Start the dialog to add a new MCP server by running:
```
/mcp add
```
Configure the following fields and press `CTRL+S` to save the configuration:
- **Server name:** `chrome-devtools`
- **Server Type:** `[1] Local`
- **Command:** `npx -y chrome-devtools-mcp@latest`
</details>
<details>
<summary>Copilot / VS Code</summary>
**Click the button to install:**
[<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://vscode.dev/redirect/mcp/install?name=io.github.ChromeDevTools%2Fchrome-devtools-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22chrome-devtools-mcp%22%5D%2C%22env%22%3A%7B%7D%7D)
[<img src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5" alt="Install in VS Code Insiders">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522io.github.ChromeDevTools%252Fchrome-devtools-mcp%2522%252C%2522config%2522%253A%257B%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522chrome-devtools-mcp%2522%255D%252C%2522env%2522%253A%257B%257D%257D%257D)
**Or install manually:**
Follow the MCP install <a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server">guide</a>,
with the standard config from above. You can also install the Chrome DevTools MCP server using the VS Code CLI:
For macOS and Linux:
```bash
code --add-mcp '{"name":"io.github.ChromeDevTools/chrome-devtools-mcp","command":"npx","args":["-y","chrome-devtools-mcp"],"env":{}}'
```
For Windows (PowerShell):
```powershell
code --add-mcp '{"""name""":"""io.github.ChromeDevTools/chrome-devtools-mcp""","""command""":"""npx""","""args""":["""-y""","""chrome-devtools-mcp"""]}'
```
</details>
<details>
<summary>Cursor</summary>
**Click the button to install:**
[<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](https://cursor.com/en/install-mcp?name=chrome-devtools&config=eyJjb21tYW5kIjoibnB4IC15IGNocm9tZS1kZXZ0b29scy1tY3BAbGF0ZXN0In0%3D)
**Or install manually:**
Go to `Cursor Settings` -> `MCP` -> `New MCP Server`. Use the config provided above.
</details>
<details>
<summary>Factory CLI</summary>
Use the Factory CLI to add the Chrome DevTools MCP server (<a href="https://docs.factory.ai/cli/configuration/mcp">guide</a>):
```bash
droid mcp add chrome-devtools "npx -y chrome-devtools-mcp@latest"
```
</details>
<details>
<summary>Gemini CLI</summary>
Install the Chrome DevTools MCP server using the Gemini CLI.
**Project wide:**
```bash
# Either MCP only:
gemini mcp add chrome-devtools npx chrome-devtools-mcp@latest
# Or as a Gemini extension (MCP+Skills):
gemini extensions install --auto-update https://github.com/ChromeDevTools/chrome-devtools-mcp
```
**Globally:**
```bash
gemini mcp add -s user chrome-devtools npx chrome-devtools-mcp@latest
```
Alternatively, follow the <a href="https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md#how-to-set-up-your-mcp-server">MCP guide</a> and use the standard config from above.
</details>
<details>
<summary>Gemini Code Assist</summary>
Follow the <a href="https://cloud.google.com/gemini/docs/codeassist/use-agentic-chat-pair-programmer#configure-mcp-servers">configure MCP guide</a>
using the standard config from above.
</details>
<details>
<summary>JetBrains AI Assistant & Junie</summary>
Go to `Settings | Tools | AI Assistant | Model Context Protocol (MCP)` -> `Add`. Use the config provided above.
The same way chrome-devtools-mcp can be configured for JetBrains Junie in `Settings | Tools | Junie | MCP Settings` -> `Add`. Use the config provided above.
</details>
<details>
<summary>Kiro</summary>
In **Kiro Settings**, go to `Configure MCP` > `Open Workspace or User MCP Config` > Use the configuration snippet provided above.
Or, from the IDE **Activity Bar** > `Kiro` > `MCP Servers` > `Click Open MCP Config`. Use the configuration snippet provided above.
</details>
<details>
<summary>Katalon Studio</summary>
The Chrome DevTools MCP server can be used with <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-chrome-devtools-mcp-server-for-studioassist">Katalon StudioAssist</a> via an MCP proxy.
**Step 1:** Install the MCP proxy by following the <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-mcp-proxy-for-stdio-mcp-servers">MCP proxy setup guide</a>.
**Step 2:** Start the Chrome DevTools MCP server with the proxy:
```bash
mcp-proxy --transport streamablehttp --port 8080 -- npx -y chrome-devtools-mcp@latest
```
**Note:** You may need to pick another port if 8080 is already in use.
**Step 3:** In Katalon Studio, add the server to StudioAssist with the following settings:
- **Connection URL:** `http://127.0.0.1:8080/mcp`
- **Transport type:** `HTTP`
Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
</details>
<details>
<summary>OpenCode</summary>
Add the following configuration to your `opencode.json` file. If you don't have one, create it at `~/.config/opencode/opencode.json` (<a href="https://opencode.ai/docs/mcp-servers">guide</a>):
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"chrome-devtools": {
"type": "local",
"command": ["npx", "-y", "chrome-devtools-mcp@latest"]
}
}
}
```
</details>
<details>
<summary>Qoder</summary>
In **Qoder Settings**, go to `MCP Server` > `+ Add` > Use the configuration snippet provided above.
Alternatively, follow the <a href="https://docs.qoder.com/user-guide/chat/model-context-protocol">MCP guide</a> and use the standard config from above.
</details>
<details>
<summary>Qoder CLI</summary>
Install the Chrome DevTools MCP server using the Qoder CLI (<a href="https://docs.qoder.com/cli/using-cli#mcp-servers">guide</a>):
**Project wide:**
```bash
qodercli mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
```
**Globally:**
```bash
qodercli mcp add -s user chrome-devtools -- npx chrome-devtools-mcp@latest
```
</details>
<details>
<summary>Visual Studio</summary>
**Click the button to install:**
[<img src="https://img.shields.io/badge/Visual_Studio-Install-C16FDE?logo=visualstudio&logoColor=white" alt="Install in Visual Studio">](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D)
</details>
<details>
<summary>Warp</summary>
Go to `Settings | AI | Manage MCP Servers` -> `+ Add` to [add an MCP Server](https://docs.warp.dev/knowledge-and-collaboration/mcp#adding-an-mcp-server). Use the config provided above.
</details>
<details>
<summary>Windsurf</summary>
Follow the <a href="https://docs.windsurf.com/windsurf/cascade/mcp#mcp-config-json">configure MCP guide</a>
using the standard config from above.
</details>
### Your first prompt
Enter the following prompt in your MCP Client to check if everything is working:
```
Check the performance of https://developers.chrome.com
```
Your MCP client should open the browser and record a performance trace.
> [!NOTE]
> The MCP server will start the browser automatically once the MCP client uses a tool that requires a running browser instance. Connecting to the Chrome DevTools MCP server on its own will not automatically start the browser.
## Tools
If you run into any issues, checkout our [troubleshooting guide](./docs/troubleshooting.md).
<!-- BEGIN AUTO GENERATED TOOLS -->
- **Input automation** (9 tools)
- [`click`](docs/tool-reference.md#click)
- [`drag`](docs/tool-reference.md#drag)
- [`fill`](docs/tool-reference.md#fill)
- [`fill_form`](docs/tool-reference.md#fill_form)
- [`handle_dialog`](docs/tool-reference.md#handle_dialog)
- [`hover`](docs/tool-reference.md#hover)
- [`press_key`](docs/tool-reference.md#press_key)
- [`type_text`](docs/tool-reference.md#type_text)
- [`upload_file`](docs/tool-reference.md#upload_file)
- **Navigation automation** (6 tools)
- [`close_page`](docs/tool-reference.md#close_page)
- [`list_pages`](docs/tool-reference.md#list_pages)
- [`navigate_page`](docs/tool-reference.md#navigate_page)
- [`new_page`](docs/tool-reference.md#new_page)
- [`select_page`](docs/tool-reference.md#select_page)
- [`wait_for`](docs/tool-reference.md#wait_for)
- **Emulation** (2 tools)
- [`emulate`](docs/tool-reference.md#emulate)
- [`resize_page`](docs/tool-reference.md#resize_page)
- **Performance** (4 tools)
- [`performance_analyze_insight`](docs/tool-reference.md#performance_analyze_insight)
- [`performance_start_trace`](docs/tool-reference.md#performance_start_trace)
- [`performance_stop_trace`](docs/tool-reference.md#performance_stop_trace)
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)
- **Network** (2 tools)
- [`get_network_request`](docs/tool-reference.md#get_network_request)
- [`list_network_requests`](docs/tool-reference.md#list_network_requests)
- **Debugging** (6 tools)
- [`evaluate_script`](docs/tool-reference.md#evaluate_script)
- [`get_console_message`](docs/tool-reference.md#get_console_message)
- [`lighthouse_audit`](docs/tool-reference.md#lighthouse_audit)
- [`list_console_messages`](docs/tool-reference.md#list_console_messages)
- [`take_screenshot`](docs/tool-reference.md#take_screenshot)
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
<!-- END AUTO GENERATED TOOLS -->
## Configuration
The Chrome DevTools MCP server supports the following configuration option:
<!-- BEGIN AUTO GENERATED OPTIONS -->
- **`--autoConnect`/ `--auto-connect`**
If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
- **Type:** boolean
- **Default:** `false`
- **`--browserUrl`/ `--browser-url`, `-u`**
Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.
- **Type:** string
- **`--wsEndpoint`/ `--ws-endpoint`, `-w`**
WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.
- **Type:** string
- **`--wsHeaders`/ `--ws-headers`**
Custom headers for WebSocket connection in JSON format (e.g., '{"Authorization":"Bearer token"}'). Only works with --wsEndpoint.
- **Type:** string
- **`--headless`**
Whether to run in headless (no UI) mode.
- **Type:** boolean
- **Default:** `false`
- **`--executablePath`/ `--executable-path`, `-e`**
Path to custom Chrome executable.
- **Type:** string
- **`--isolated`**
If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.
- **Type:** boolean
- **`--userDataDir`/ `--user-data-dir`**
Path to the user data directory for Chrome. Default is $HOME/.cache/chrome-devtools-mcp/chrome-profile$CHANNEL_SUFFIX_IF_NON_STABLE
- **Type:** string
- **`--channel`**
Specify a different Chrome channel that should be used. The default is the stable channel version.
- **Type:** string
- **Choices:** `stable`, `canary`, `beta`, `dev`
- **`--logFile`/ `--log-file`**
Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
- **Type:** string
- **`--viewport`**
Initial viewport size for the Chrome instances started by the server. For example, `1280x720`. In headless mode, max size is 3840x2160px.
- **Type:** string
- **`--proxyServer`/ `--proxy-server`**
Proxy server configuration for Chrome passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details.
- **Type:** string
- **`--acceptInsecureCerts`/ `--accept-insecure-certs`**
If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
- **Type:** boolean
- **`--experimentalScreencast`/ `--experimental-screencast`**
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
- **Type:** boolean
- **`--chromeArg`/ `--chrome-arg`**
Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
- **Type:** array
- **`--ignoreDefaultChromeArg`/ `--ignore-default-chrome-arg`**
Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
- **Type:** array
- **`--categoryEmulation`/ `--category-emulation`**
Set to false to exclude tools related to emulation.
- **Type:** boolean
- **Default:** `true`
- **`--categoryPerformance`/ `--category-performance`**
Set to false to exclude tools related to performance.
- **Type:** boolean
- **Default:** `true`
- **`--categoryNetwork`/ `--category-network`**
Set to false to exclude tools related to network.
- **Type:** boolean
- **Default:** `true`
- **`--performanceCrux`/ `--performance-crux`**
Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
- **Type:** boolean
- **Default:** `true`
- **`--usageStatistics`/ `--usage-statistics`**
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
- **Type:** boolean
- **Default:** `true`
- **`--slim`**
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
- **Type:** boolean
<!-- END AUTO GENERATED OPTIONS -->
Pass them via the `args` property in the JSON configuration. For example:
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--channel=canary",
"--headless=true",
"--isolated=true"
]
}
}
}
```
### Connecting via WebSocket with custom headers
You can connect directly to a Chrome WebSocket endpoint and include custom headers (e.g., for authentication):
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--wsEndpoint=ws://127.0.0.1:9222/devtools/browser/<id>",
"--wsHeaders={\"Authorization\":\"Bearer YOUR_TOKEN\"}"
]
}
}
}
```
To get the WebSocket endpoint from a running Chrome instance, visit `http://127.0.0.1:9222/json/version` and look for the `webSocketDebuggerUrl` field.
You can also run `npx chrome-devtools-mcp@latest --help` to see all available configuration options.
## Concepts
### User data directory
`chrome-devtools-mcp` starts a Chrome's stable channel instance using the following user
data directory:
- Linux / macOS: `$HOME/.cache/chrome-devtools-mcp/chrome-profile-$CHANNEL`
- Windows: `%HOMEPATH%/.cache/chrome-devtools-mcp/chrome-profile-$CHANNEL`
The user data directory is not cleared between runs and shared across
all instances of `chrome-devtools-mcp`. Set the `isolated` option to `true`
to use a temporary user data dir instead which will be cleared automatically after
the browser is closed.
### Connecting to a running Chrome instance
By default, the Chrome DevTools MCP server will start a new Chrome instance with a dedicated profile. This might not be ideal in all situations:
- If you would like to maintain the same application state when alternating between manual site testing and agent-driven testing.
- When the MCP needs to sign into a website. Some accounts may prevent sign-in when the browser is controlled via WebDriver (the default launch mechanism for the Chrome DevTools MCP server).
- If you're running your LLM inside a sandboxed environment, but you would like to connect to a Chrome instance that runs outside the sandbox.
In these cases, start Chrome first and let the Chrome DevTools MCP server connect to it. There are two ways to do so:
- **Automatic connection (available in Chrome 144)**: best for sharing state between manual and agent-driven testing.
- **Manual connection via remote debugging port**: best when running inside a sandboxed environment.
#### Automatically connecting to a running Chrome instance
**Step 1:** Set up remote debugging in Chrome
In Chrome (\>= M144), do the following to set up remote debugging:
1. Navigate to `chrome://inspect/#remote-debugging` to enable remote debugging.
2. Follow the dialog UI to allow or disallow incoming debugging connections.
**Step 2:** Configure Chrome DevTools MCP server to automatically connect to a running Chrome Instance
To connect the `chrome-devtools-mcp` server to the running Chrome instance, use
`--autoConnect` command line argument for the MCP server.
The following code snippet is an example configuration for gemini-cli:
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["chrome-devtools-mcp@latest", "--autoConnect"]
}
}
}
```
**Step 3:** Test your setup
Make sure your browser is running. Open gemini-cli and run the following prompt:
```none
Check the performance of https://developers.chrome.com
```
> [!NOTE]
> The <code>autoConnect</code> option requires the user to start Chrome. If the user has multiple active profiles, the MCP server will connect to the default profile (as determined by Chrome). The MCP server has access to all open windows for the selected profile.
The Chrome DevTools MCP server will try to connect to your running Chrome
instance. It shows a dialog asking for user permission.
Clicking **Allow** results in the Chrome DevTools MCP server opening
[developers.chrome.com](http://developers.chrome.com) and taking a performance
trace.
#### Manual connection using port forwarding
You can connect to a running Chrome instance by using the `--browser-url` option. This is useful if you are running the MCP server in a sandboxed environment that does not allow starting a new Chrome instance.
Here is a step-by-step guide on how to connect to a running Chrome instance:
**Step 1: Configure the MCP client**
Add the `--browser-url` option to your MCP client configuration. The value of this option should be the URL of the running Chrome instance. `http://127.0.0.1:9222` is a common default.
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--browser-url=http://127.0.0.1:9222"
]
}
}
}
```
**Step 2: Start the Chrome browser**
> [!WARNING]
> Enabling the remote debugging port opens up a debugging port on the running browser instance. Any application on your machine can connect to this port and control the browser. Make sure that you are not browsing any sensitive websites while the debugging port is open.
Start the Chrome browser with the remote debugging port enabled. Make sure to close any running Chrome instances before starting a new one with the debugging port enabled. The port number you choose must be the same as the one you specified in the `--browser-url` option in your MCP client configuration.
For security reasons, [Chrome requires you to use a non-default user data directory](https://developer.chrome.com/blog/remote-debugging-port) when enabling the remote debugging port. You can specify a custom directory using the `--user-data-dir` flag. This ensures that your regular browsing profile and data are not exposed to the debugging session.
**macOS**
```bash
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profile-stable
```
**Linux**
```bash
/usr/bin/google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profile-stable
```
**Windows**
```bash
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="%TEMP%\chrome-profile-stable"
```
**Step 3: Test your setup**
After configuring the MCP client and starting the Chrome browser, you can test your setup by running a simple prompt in your MCP client:
```
Check the performance of https://developers.chrome.com
```
Your MCP client should connect to the running Chrome instance and receive a performance report.
If you hit VM-to-host port forwarding issues, see the “Remote debugging between virtual machine (VM) and host fails” section in [`docs/troubleshooting.md`](./docs/troubleshooting.md#remote-debugging-between-virtual-machine-vm-and-host-fails).
For more details on remote debugging, see the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/remote-debugging/).
### Debugging Chrome on Android
Please consult [these instructions](./docs/debugging-android.md).
## Known limitations
See [Troubleshooting](./docs/troubleshooting.md).
@@ -1,69 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { CDPSessionEvent } from './third_party/index.js';
/**
* This class makes a puppeteer connection look like DevTools CDPConnection.
*
* Since we connect "root" DevTools targets to specific pages, we scope everything to a puppeteer CDP session.
*
* We don't have to recursively listen for 'sessionattached' as the "root" CDP session sees all child session attached
* events, regardless how deeply nested they are.
*/
export class PuppeteerDevToolsConnection {
#connection;
#observers = new Set();
#sessionEventHandlers = new Map();
constructor(session) {
this.#connection = session.connection();
session.on(CDPSessionEvent.SessionAttached, this.#startForwardingCdpEvents.bind(this));
session.on(CDPSessionEvent.SessionDetached, this.#stopForwardingCdpEvents.bind(this));
this.#startForwardingCdpEvents(session);
}
send(method, params, sessionId) {
if (sessionId === undefined) {
throw new Error('Attempting to send on the root session. This must not happen');
}
const session = this.#connection.session(sessionId);
if (!session) {
throw new Error('Unknown session ' + sessionId);
}
// Rolled protocol version between puppeteer and DevTools doesn't necessarily match
/* eslint-disable @typescript-eslint/no-explicit-any */
return session
.send(method, params)
.then(result => ({ result }))
.catch(error => ({ error }));
/* eslint-enable @typescript-eslint/no-explicit-any */
}
observe(observer) {
this.#observers.add(observer);
}
unobserve(observer) {
this.#observers.delete(observer);
}
#startForwardingCdpEvents(session) {
const handler = this.#handleEvent.bind(this, session.id());
this.#sessionEventHandlers.set(session.id(), handler);
session.on('*', handler);
}
#stopForwardingCdpEvents(session) {
const handler = this.#sessionEventHandlers.get(session.id());
if (handler) {
session.off('*', handler);
}
}
#handleEvent(sessionId, type, event) {
if (typeof type === 'string' &&
type !== CDPSessionEvent.SessionAttached &&
type !== CDPSessionEvent.SessionDetached) {
this.#observers.forEach(observer => observer.onEvent({
method: type,
sessionId,
params: event,
}));
}
}
}
-294
View File
@@ -1,294 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
import { Mutex } from './Mutex.js';
import { DevTools } from './third_party/index.js';
/**
* A mock implementation of an issues manager that only implements the methods
* that are actually used by the IssuesAggregator
*/
export class FakeIssuesManager extends DevTools.Common.ObjectWrapper
.ObjectWrapper {
issues() {
return [];
}
}
// DevTools CDP errors can get noisy.
DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
create: true,
data: {
navigatorLanguage: 'en-US',
settingLanguage: 'en-US',
lookupClosestDevToolsLocale: l => l,
},
});
DevTools.I18n.i18n.registerLocaleDataForTest('en-US', {});
DevTools.Formatter.FormatterWorkerPool.FormatterWorkerPool.instance({
forceNew: true,
entrypointURL: import.meta
.resolve('./third_party/devtools-formatter-worker.js'),
});
export class UniverseManager {
#browser;
#createUniverseFor;
#universes = new WeakMap();
/** Guard access to #universes so we don't create unnecessary universes */
#mutex = new Mutex();
constructor(browser, factory = DEFAULT_FACTORY) {
this.#browser = browser;
this.#createUniverseFor = factory;
}
async init(pages) {
try {
await this.#mutex.acquire();
const promises = [];
for (const page of pages) {
promises.push(this.#createUniverseFor(page).then(targetUniverse => this.#universes.set(page, targetUniverse)));
}
this.#browser.on('targetcreated', this.#onTargetCreated);
this.#browser.on('targetdestroyed', this.#onTargetDestroyed);
await Promise.all(promises);
}
finally {
this.#mutex.release();
}
}
get(page) {
return this.#universes.get(page) ?? null;
}
dispose() {
this.#browser.off('targetcreated', this.#onTargetCreated);
this.#browser.off('targetdestroyed', this.#onTargetDestroyed);
}
#onTargetCreated = async (target) => {
const page = await target.page();
try {
await this.#mutex.acquire();
if (!page || this.#universes.has(page)) {
return;
}
this.#universes.set(page, await this.#createUniverseFor(page));
}
finally {
this.#mutex.release();
}
};
#onTargetDestroyed = async (target) => {
const page = await target.page();
try {
await this.#mutex.acquire();
if (!page || !this.#universes.has(page)) {
return;
}
this.#universes.delete(page);
}
finally {
this.#mutex.release();
}
};
}
const DEFAULT_FACTORY = async (page) => {
const settingStorage = new DevTools.Common.Settings.SettingsStorage({});
const universe = new DevTools.Foundation.Universe.Universe({
settingsCreationOptions: {
syncedStorage: settingStorage,
globalStorage: settingStorage,
localStorage: settingStorage,
settingRegistrations: DevTools.Common.SettingRegistration.getRegisteredSettings(),
},
overrideAutoStartModels: new Set([DevTools.DebuggerModel]),
});
const session = await page.createCDPSession();
const connection = new PuppeteerDevToolsConnection(session);
const targetManager = universe.context.get(DevTools.TargetManager);
targetManager.observeModels(DevTools.DebuggerModel, SKIP_ALL_PAUSES);
const target = targetManager.createTarget('main', '', 'frame', // eslint-disable-line @typescript-eslint/no-explicit-any
/* parentTarget */ null, session.id(), undefined, connection);
return { target, universe };
};
// We don't want to pause any DevTools universe session ever on the MCP side.
//
// Note that calling `setSkipAllPauses` only affects the session on which it was
// sent. This means DevTools can still pause, step and do whatever. We just won't
// see the `Debugger.paused`/`Debugger.resumed` events on the MCP side.
const SKIP_ALL_PAUSES = {
modelAdded(model) {
void model.agent.invoke_setSkipAllPauses({ skip: true });
},
modelRemoved() {
// Do nothing.
},
};
/**
* Constructed from Runtime.ExceptionDetails of an uncaught error.
*
* TODO: Also construct from a RemoteObject of subtype 'error'.
*
* Consists of the message, a fully resolved stack trace and a fully resolved 'cause' chain.
*/
export class SymbolizedError {
message;
stackTrace;
cause;
constructor(message, stackTrace, cause) {
this.message = message;
this.stackTrace = stackTrace;
this.cause = cause;
}
static async fromDetails(opts) {
const message = SymbolizedError.#getMessage(opts.details);
if (!opts.includeStackAndCause || !opts.devTools) {
return new SymbolizedError(message, opts.resolvedStackTraceForTesting, opts.resolvedCauseForTesting);
}
let stackTrace;
if (opts.resolvedStackTraceForTesting) {
stackTrace = opts.resolvedStackTraceForTesting;
}
else if (opts.details.stackTrace) {
try {
stackTrace = await createStackTrace(opts.devTools, opts.details.stackTrace, opts.targetId);
}
catch {
// ignore
}
}
// TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
// If its an Error, recursively create a SymbolizedError.
let cause;
if (opts.resolvedCauseForTesting) {
cause = opts.resolvedCauseForTesting;
}
else if (opts.details.exception) {
try {
const causeRemoteObj = await SymbolizedError.#lookupCause(opts.devTools, opts.details.exception, opts.targetId);
if (causeRemoteObj) {
cause = await SymbolizedError.fromError({
devTools: opts.devTools,
error: causeRemoteObj,
targetId: opts.targetId,
});
}
}
catch {
// Ignore
}
}
return new SymbolizedError(message, stackTrace, cause);
}
static async fromError(opts) {
const details = await SymbolizedError.#getExceptionDetails(opts.devTools, opts.error, opts.targetId);
if (details) {
return SymbolizedError.fromDetails({
details,
devTools: opts.devTools,
targetId: opts.targetId,
includeStackAndCause: true,
});
}
return new SymbolizedError(SymbolizedError.#getMessageFromException(opts.error));
}
static #getMessage(details) {
// For Runtime.exceptionThrown with a present exception object, `details.text` will be "Uncaught" and
// we have to manually parse out the error text from the exception description.
// In the case of Runtime.getExceptionDetails, `details.text` has the Error.message.
if (details.text === 'Uncaught' && details.exception) {
return ('Uncaught ' +
SymbolizedError.#getMessageFromException(details.exception));
}
return details.text;
}
static #getMessageFromException(error) {
const messageWithRest = error.description?.split('\n at ', 2) ?? [];
return messageWithRest[0] ?? '';
}
static async #getExceptionDetails(devTools, error, targetId) {
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
return null;
}
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
const target = targetId
? targetManager.targetById(targetId) || devTools.target
: devTools.target;
const model = target.model(DevTools.RuntimeModel);
return ((await model.getExceptionDetails(error.objectId)) ?? null);
}
static async #lookupCause(devTools, error, targetId) {
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
return null;
}
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
const target = targetId
? targetManager.targetById(targetId) || devTools.target
: devTools.target;
const properties = await target.runtimeAgent().invoke_getProperties({
objectId: error.objectId,
});
if (properties.getError()) {
return null;
}
return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
}
static createForTesting(message, stackTrace, cause) {
return new SymbolizedError(message, stackTrace, cause);
}
}
export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
const message = consoleMessage;
const rawStackTrace = message._rawStackTrace();
if (rawStackTrace) {
return createStackTrace(devTools, rawStackTrace, message._targetId());
}
return undefined;
}
export async function createStackTrace(devTools, rawStackTrace, targetId) {
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
const target = targetId
? targetManager.targetById(targetId) || devTools.target
: devTools.target;
const model = target.model(DevTools.DebuggerModel);
// DevTools doesn't wait for source maps to attach before building a stack trace, rather it'll send
// an update event once a source map was attached and the stack trace retranslated. This doesn't
// work in the MCP case, so we'll collect all script IDs upfront and wait for any pending source map
// loads before creating the stack trace. We might also have to wait for Debugger.ScriptParsed events if
// the stack trace is created particularly early.
const scriptIds = new Set();
for (const frame of rawStackTrace.callFrames) {
scriptIds.add(frame.scriptId);
}
for (let asyncStack = rawStackTrace.parent; asyncStack; asyncStack = asyncStack.parent) {
for (const frame of asyncStack.callFrames) {
scriptIds.add(frame.scriptId);
}
}
const signal = AbortSignal.timeout(1_000);
await Promise.all([...scriptIds].map(id => waitForScript(model, id, signal)
.then(script => model.sourceMapManager().sourceMapForClientPromise(script))
.catch()));
const binding = devTools.universe.context.get(DevTools.DebuggerWorkspaceBinding);
// DevTools uses branded types for ScriptId and others. Casting the puppeteer protocol type to the DevTools protocol type is safe.
return binding.createStackTraceFromProtocolRuntime(rawStackTrace, target);
}
// Waits indefinitely for the script so pair it with Promise.race.
async function waitForScript(model, scriptId, signal) {
while (true) {
if (signal.aborted) {
throw signal.reason;
}
const script = model.scriptForId(scriptId);
if (script) {
return script;
}
await new Promise((resolve, reject) => {
signal.addEventListener('abort', () => reject(signal.reason), {
once: true,
});
void model
.once('ParsedScriptSource')
.then(resolve);
});
}
}
-707
View File
@@ -1,707 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import { UniverseManager } from './DevtoolsUtils.js';
import { McpPage } from './McpPage.js';
import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
import { Locator } from './third_party/index.js';
import { PredefinedNetworkConditions } from './third_party/index.js';
import { listPages } from './tools/pages.js';
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
import { ExtensionRegistry, } from './utils/ExtensionRegistry.js';
import { saveTemporaryFile } from './utils/files.js';
import { WaitForHelper } from './WaitForHelper.js';
const DEFAULT_TIMEOUT = 5_000;
const NAVIGATION_TIMEOUT = 10_000;
function getNetworkMultiplierFromString(condition) {
const puppeteerCondition = condition;
switch (puppeteerCondition) {
case 'Fast 4G':
return 1;
case 'Slow 4G':
return 2.5;
case 'Fast 3G':
return 5;
case 'Slow 3G':
return 10;
}
return 1;
}
export class McpContext {
browser;
logger;
// Maps LLM-provided isolatedContext name → Puppeteer BrowserContext.
#isolatedContexts = new Map();
// Auto-generated name counter for when no name is provided.
#nextIsolatedContextId = 1;
#pages = [];
#extensionServiceWorkers = [];
#mcpPages = new Map();
#selectedPage;
#networkCollector;
#consoleCollector;
#devtoolsUniverseManager;
#extensionRegistry = new ExtensionRegistry();
#isRunningTrace = false;
#screenRecorderData = null;
#inPageTools;
#nextPageId = 1;
#extensionPages = new WeakMap();
#extensionServiceWorkerMap = new WeakMap();
#nextExtensionServiceWorkerId = 1;
#nextSnapshotId = 1;
#traceResults = [];
#locatorClass;
#options;
constructor(browser, logger, options, locatorClass) {
this.browser = browser;
this.logger = logger;
this.#locatorClass = locatorClass;
this.#options = options;
this.#networkCollector = new NetworkCollector(this.browser);
this.#consoleCollector = new ConsoleCollector(this.browser, collect => {
return {
console: event => {
collect(event);
},
uncaughtError: event => {
collect(event);
},
issue: event => {
collect(event);
},
};
});
this.#devtoolsUniverseManager = new UniverseManager(this.browser);
}
async #init() {
const pages = await this.createPagesSnapshot();
await this.createExtensionServiceWorkersSnapshot();
await this.#networkCollector.init(pages);
await this.#consoleCollector.init(pages);
await this.#devtoolsUniverseManager.init(pages);
}
dispose() {
this.#networkCollector.dispose();
this.#consoleCollector.dispose();
this.#devtoolsUniverseManager.dispose();
for (const mcpPage of this.#mcpPages.values()) {
mcpPage.dispose();
}
this.#mcpPages.clear();
// Isolated contexts are intentionally not closed here.
// Either the entire browser will be closed or we disconnect
// without destroying browser state.
this.#isolatedContexts.clear();
}
static async from(browser, logger, opts,
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
locatorClass = Locator) {
const context = new McpContext(browser, logger, opts, locatorClass);
await context.#init();
return context;
}
resolveCdpRequestId(page, cdpRequestId) {
if (!cdpRequestId) {
this.logger('no network request');
return;
}
const request = this.#networkCollector.find(page.pptrPage, request => {
// @ts-expect-error id is internal.
return request.id === cdpRequestId;
});
if (!request) {
this.logger('no network request for ' + cdpRequestId);
return;
}
return this.#networkCollector.getIdForResource(request);
}
resolveCdpElementId(page, cdpBackendNodeId) {
if (!cdpBackendNodeId) {
this.logger('no cdpBackendNodeId');
return;
}
const snapshot = page.textSnapshot;
if (!snapshot) {
this.logger('no text snapshot');
return;
}
// TODO: index by backendNodeId instead.
const queue = [snapshot.root];
while (queue.length) {
const current = queue.pop();
if (current.backendNodeId === cdpBackendNodeId) {
return current.id;
}
for (const child of current.children) {
queue.push(child);
}
}
return;
}
getNetworkRequests(page, includePreservedRequests) {
return this.#networkCollector.getData(page.pptrPage, includePreservedRequests);
}
getConsoleData(page, includePreservedMessages) {
return this.#consoleCollector.getData(page.pptrPage, includePreservedMessages);
}
getDevToolsUniverse(page) {
return this.#devtoolsUniverseManager.get(page.pptrPage);
}
getConsoleMessageStableId(message) {
return this.#consoleCollector.getIdForResource(message);
}
getConsoleMessageById(page, id) {
return this.#consoleCollector.getById(page.pptrPage, id);
}
async newPage(background, isolatedContextName) {
let page;
if (isolatedContextName !== undefined) {
let ctx = this.#isolatedContexts.get(isolatedContextName);
if (!ctx) {
ctx = await this.browser.createBrowserContext();
this.#isolatedContexts.set(isolatedContextName, ctx);
}
page = await ctx.newPage();
}
else {
page = await this.browser.newPage({ background });
}
await this.createPagesSnapshot();
this.selectPage(this.#getMcpPage(page));
this.#networkCollector.addPage(page);
this.#consoleCollector.addPage(page);
return this.#getMcpPage(page);
}
async closePage(pageId) {
if (this.#pages.length === 1) {
throw new Error(CLOSE_PAGE_ERROR);
}
const page = this.getPageById(pageId);
if (page) {
page.dispose();
this.#mcpPages.delete(page.pptrPage);
}
await page.pptrPage.close({ runBeforeUnload: false });
}
getNetworkRequestById(page, reqid) {
return this.#networkCollector.getById(page.pptrPage, reqid);
}
async restoreEmulation(page) {
const currentSetting = page.emulationSettings;
await this.emulate(currentSetting, page.pptrPage);
}
async emulate(options, targetPage) {
const page = targetPage ?? this.getSelectedPptrPage();
const mcpPage = this.#getMcpPage(page);
const newSettings = { ...mcpPage.emulationSettings };
if (!options.networkConditions) {
await page.emulateNetworkConditions(null);
delete newSettings.networkConditions;
}
else if (options.networkConditions === 'Offline') {
await page.emulateNetworkConditions({
offline: true,
download: 0,
upload: 0,
latency: 0,
});
newSettings.networkConditions = 'Offline';
}
else if (options.networkConditions in PredefinedNetworkConditions) {
const networkCondition = PredefinedNetworkConditions[options.networkConditions];
await page.emulateNetworkConditions(networkCondition);
newSettings.networkConditions = options.networkConditions;
}
if (!options.cpuThrottlingRate) {
await page.emulateCPUThrottling(1);
delete newSettings.cpuThrottlingRate;
}
else {
await page.emulateCPUThrottling(options.cpuThrottlingRate);
newSettings.cpuThrottlingRate = options.cpuThrottlingRate;
}
if (!options.geolocation) {
await page.setGeolocation({ latitude: 0, longitude: 0 });
delete newSettings.geolocation;
}
else {
await page.setGeolocation(options.geolocation);
newSettings.geolocation = options.geolocation;
}
if (!options.userAgent) {
await page.setUserAgent({ userAgent: undefined });
delete newSettings.userAgent;
}
else {
await page.setUserAgent({ userAgent: options.userAgent });
newSettings.userAgent = options.userAgent;
}
if (!options.colorScheme || options.colorScheme === 'auto') {
await page.emulateMediaFeatures([
{ name: 'prefers-color-scheme', value: '' },
]);
delete newSettings.colorScheme;
}
else {
await page.emulateMediaFeatures([
{ name: 'prefers-color-scheme', value: options.colorScheme },
]);
newSettings.colorScheme = options.colorScheme;
}
if (!options.viewport) {
await page.setViewport(null);
delete newSettings.viewport;
}
else {
const defaults = {
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false,
};
const viewport = { ...defaults, ...options.viewport };
await page.setViewport(viewport);
newSettings.viewport = viewport;
}
mcpPage.emulationSettings = Object.keys(newSettings).length
? newSettings
: {};
this.#updateSelectedPageTimeouts();
}
setIsRunningPerformanceTrace(x) {
this.#isRunningTrace = x;
}
isRunningPerformanceTrace() {
return this.#isRunningTrace;
}
getScreenRecorder() {
return this.#screenRecorderData;
}
setScreenRecorder(data) {
this.#screenRecorderData = data;
}
isCruxEnabled() {
return this.#options.performanceCrux;
}
getSelectedPptrPage() {
const page = this.#selectedPage;
if (!page) {
throw new Error('No page selected');
}
if (page.pptrPage.isClosed()) {
throw new Error(`The selected page has been closed. Call ${listPages().name} to see open pages.`);
}
return page.pptrPage;
}
getSelectedMcpPage() {
const page = this.getSelectedPptrPage();
return this.#getMcpPage(page);
}
getPageById(pageId) {
const page = this.#mcpPages.values().find(mcpPage => mcpPage.id === pageId);
if (!page) {
throw new Error('No page found');
}
return page;
}
getPageId(page) {
return this.#mcpPages.get(page)?.id;
}
#getMcpPage(page) {
const mcpPage = this.#mcpPages.get(page);
if (!mcpPage) {
throw new Error('No McpPage found for the given page.');
}
return mcpPage;
}
#getSelectedMcpPage() {
return this.#getMcpPage(this.getSelectedPptrPage());
}
isPageSelected(page) {
return this.#selectedPage?.pptrPage === page;
}
selectPage(newPage) {
this.#selectedPage = newPage;
this.#updateSelectedPageTimeouts();
}
setInPageTools(toolGroup) {
this.#inPageTools = toolGroup;
}
getInPageTools() {
return this.#inPageTools;
}
#updateSelectedPageTimeouts() {
const page = this.#getSelectedMcpPage();
// For waiters 5sec timeout should be sufficient.
// Increased in case we throttle the CPU
const cpuMultiplier = page.cpuThrottlingRate;
page.pptrPage.setDefaultTimeout(DEFAULT_TIMEOUT * cpuMultiplier);
// 10sec should be enough for the load event to be emitted during
// navigations.
// Increased in case we throttle the network requests
const networkMultiplier = getNetworkMultiplierFromString(page.networkConditions);
page.pptrPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier);
}
// Linear scan over per-page snapshots. The page count is small (typically
// 2-10) so a reverse index isn't worthwhile given the uid-reuse lifecycle
// complexity it would introduce.
getAXNodeByUid(uid) {
for (const mcpPage of this.#mcpPages.values()) {
const node = mcpPage.textSnapshot?.idToNode.get(uid);
if (node) {
return node;
}
}
return undefined;
}
/**
* Creates a snapshot of the extension service workers.
*/
async createExtensionServiceWorkersSnapshot() {
const allTargets = await this.browser.targets();
const serviceWorkers = allTargets.filter(target => {
return (target.type() === 'service_worker' &&
target.url().includes('chrome-extension://'));
});
for (const serviceWorker of serviceWorkers) {
if (!this.#extensionServiceWorkerMap.has(serviceWorker)) {
this.#extensionServiceWorkerMap.set(serviceWorker, 'sw-' + this.#nextExtensionServiceWorkerId++);
}
}
this.#extensionServiceWorkers = serviceWorkers.map(serviceWorker => {
return {
target: serviceWorker,
id: this.#extensionServiceWorkerMap.get(serviceWorker),
url: serviceWorker.url(),
};
});
return this.#extensionServiceWorkers;
}
async createPagesSnapshot() {
const { pages: allPages, isolatedContextNames } = await this.#getAllPages();
for (const page of allPages) {
let mcpPage = this.#mcpPages.get(page);
if (!mcpPage) {
mcpPage = new McpPage(page, this.#nextPageId++);
this.#mcpPages.set(page, mcpPage);
// We emulate a focused page for all pages to support multi-agent workflows.
void page.emulateFocusedPage(true).catch(error => {
this.logger('Error turning on focused page emulation', error);
});
}
mcpPage.isolatedContextName = isolatedContextNames.get(page);
}
// Prune orphaned #mcpPages entries (pages that no longer exist).
const currentPages = new Set(allPages);
for (const [page, mcpPage] of this.#mcpPages) {
if (!currentPages.has(page)) {
mcpPage.dispose();
this.#mcpPages.delete(page);
}
}
this.#pages = allPages.filter(page => {
return (this.#options.experimentalDevToolsDebugging ||
!page.url().startsWith('devtools://'));
});
if ((!this.#selectedPage ||
this.#pages.indexOf(this.#selectedPage.pptrPage) === -1) &&
this.#pages[0]) {
this.selectPage(this.#getMcpPage(this.#pages[0]));
}
await this.detectOpenDevToolsWindows();
return this.#pages;
}
async #getAllPages() {
const defaultCtx = this.browser.defaultBrowserContext();
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
const allTargets = this.browser.targets();
const extensionTargets = allTargets.filter(target => {
return (target.url().startsWith('chrome-extension://') &&
target.type() === 'page');
});
for (const target of extensionTargets) {
// Right now target.page() returns null for popup and side panel pages.
let page = await target.page();
if (!page) {
// We need to cache pages instances for targets because target.asPage()
// returns a new page instance every time.
page = this.#extensionPages.get(target) ?? null;
if (!page) {
try {
page = await target.asPage();
this.#extensionPages.set(target, page);
}
catch (e) {
this.logger('Failed to get page for extension target', e);
}
}
}
if (page && !allPages.includes(page)) {
allPages.push(page);
}
}
// Build a reverse lookup from BrowserContext instance → name.
const contextToName = new Map();
for (const [name, ctx] of this.#isolatedContexts) {
contextToName.set(ctx, name);
}
// Auto-discover BrowserContexts not in our mapping (e.g., externally
// created incognito contexts) and assign generated names.
const knownContexts = new Set(this.#isolatedContexts.values());
for (const ctx of this.browser.browserContexts()) {
if (ctx !== defaultCtx && !ctx.closed && !knownContexts.has(ctx)) {
const name = `isolated-context-${this.#nextIsolatedContextId++}`;
this.#isolatedContexts.set(name, ctx);
contextToName.set(ctx, name);
}
}
// Map each page to its isolated context name (if any).
const isolatedContextNames = new Map();
for (const page of allPages) {
const ctx = page.browserContext();
const name = contextToName.get(ctx);
if (name) {
isolatedContextNames.set(page, name);
}
}
return { pages: allPages, isolatedContextNames };
}
async detectOpenDevToolsWindows() {
this.logger('Detecting open DevTools windows');
const { pages } = await this.#getAllPages();
await Promise.all(pages.map(async (page) => {
const mcpPage = this.#mcpPages.get(page);
if (!mcpPage) {
return;
}
// Prior to Chrome 144.0.7559.59, the command fails,
// Some Electron apps still use older version
// Fall back to not exposing DevTools at all.
try {
if (await page.hasDevTools()) {
mcpPage.devToolsPage = await page.openDevTools();
}
else {
mcpPage.devToolsPage = undefined;
}
}
catch {
mcpPage.devToolsPage = undefined;
}
}));
}
getExtensionServiceWorkers() {
return this.#extensionServiceWorkers;
}
getExtensionServiceWorkerId(extensionServiceWorker) {
return this.#extensionServiceWorkerMap.get(extensionServiceWorker.target);
}
getPages() {
return this.#pages;
}
getIsolatedContextName(page) {
return this.#mcpPages.get(page)?.isolatedContextName;
}
getDevToolsPage(page) {
return this.#mcpPages.get(page)?.devToolsPage;
}
async getDevToolsData(page) {
try {
this.logger('Getting DevTools UI data');
const devtoolsPage = this.getDevToolsPage(page.pptrPage);
if (!devtoolsPage) {
this.logger('No DevTools page detected');
return {};
}
const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
// @ts-expect-error no types
const UI = await import('/bundled/ui/legacy/legacy.js');
// @ts-expect-error no types
const SDK = await import('/bundled/core/sdk/sdk.js');
const request = UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest);
const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
return {
cdpRequestId: request?.requestId(),
cdpBackendNodeId: node?.backendNodeId(),
};
});
return { cdpBackendNodeId, cdpRequestId };
}
catch (err) {
this.logger('error getting devtools data', err);
}
return {};
}
/**
* Creates a text snapshot of a page.
*/
async createTextSnapshot(page, verbose = false, devtoolsData = undefined) {
const rootNode = await page.pptrPage.accessibility.snapshot({
includeIframes: true,
interestingOnly: !verbose,
});
if (!rootNode) {
return;
}
const { uniqueBackendNodeIdToMcpId } = page;
const snapshotId = this.#nextSnapshotId++;
// Iterate through the whole accessibility node tree and assign node ids that
// will be used for the tree serialization and mapping ids back to nodes.
let idCounter = 0;
const idToNode = new Map();
const seenUniqueIds = new Set();
const assignIds = (node) => {
let id = '';
// @ts-expect-error untyped loaderId & backendNodeId.
const uniqueBackendId = `${node.loaderId}_${node.backendNodeId}`;
if (uniqueBackendNodeIdToMcpId.has(uniqueBackendId)) {
// Re-use MCP exposed ID if the uniqueId is the same.
id = uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
}
else {
// Only generate a new ID if we have not seen the node before.
id = `${snapshotId}_${idCounter++}`;
uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
}
seenUniqueIds.add(uniqueBackendId);
const nodeWithId = {
...node,
id,
children: node.children
? node.children.map(child => assignIds(child))
: [],
};
// The AXNode for an option doesn't contain its `value`.
// Therefore, set text content of the option as value.
if (node.role === 'option') {
const optionText = node.name;
if (optionText) {
nodeWithId.value = optionText.toString();
}
}
idToNode.set(nodeWithId.id, nodeWithId);
return nodeWithId;
};
const rootNodeWithId = assignIds(rootNode);
const snapshot = {
root: rootNodeWithId,
snapshotId: String(snapshotId),
idToNode,
hasSelectedElement: false,
verbose,
};
page.textSnapshot = snapshot;
const data = devtoolsData ?? (await this.getDevToolsData(page));
if (data?.cdpBackendNodeId) {
snapshot.hasSelectedElement = true;
snapshot.selectedElementUid = this.resolveCdpElementId(page, data?.cdpBackendNodeId);
}
// Clean up unique IDs that we did not see anymore.
for (const key of uniqueBackendNodeIdToMcpId.keys()) {
if (!seenUniqueIds.has(key)) {
uniqueBackendNodeIdToMcpId.delete(key);
}
}
}
async saveTemporaryFile(data, filename) {
return await saveTemporaryFile(data, filename);
}
async saveFile(data, filename) {
try {
const filePath = path.resolve(filename);
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, data);
return { filename: filePath };
}
catch (err) {
this.logger(err);
throw new Error('Could not save a file', { cause: err });
}
}
storeTraceRecording(result) {
// Clear the trace results because we only consume the latest trace currently.
this.#traceResults = [];
this.#traceResults.push(result);
}
recordedTraces() {
return this.#traceResults;
}
getWaitForHelper(page, cpuMultiplier, networkMultiplier) {
return new WaitForHelper(page, cpuMultiplier, networkMultiplier);
}
waitForEventsAfterAction(action, options) {
const page = this.#getSelectedMcpPage();
const cpuMultiplier = page.cpuThrottlingRate;
const networkMultiplier = getNetworkMultiplierFromString(page.networkConditions);
const waitForHelper = this.getWaitForHelper(page.pptrPage, cpuMultiplier, networkMultiplier);
return waitForHelper.waitForEventsAfterAction(action, options);
}
getNetworkRequestStableId(request) {
return this.#networkCollector.getIdForResource(request);
}
waitForTextOnPage(text, timeout, targetPage) {
const page = targetPage ?? this.getSelectedPptrPage();
const frames = page.frames();
let locator = this.#locatorClass.race(frames.flatMap(frame => text.flatMap(value => [
frame.locator(`aria/${value}`),
frame.locator(`text/${value}`),
])));
if (timeout) {
locator = locator.setTimeout(timeout);
}
return locator.wait();
}
/**
* We need to ignore favicon request as they make our test flaky
*/
async setUpNetworkCollectorForTesting() {
this.#networkCollector = new NetworkCollector(this.browser, collect => {
return {
request: req => {
if (req.url().includes('favicon.ico')) {
return;
}
collect(req);
},
};
});
const { pages } = await this.#getAllPages();
await this.#networkCollector.init(pages);
}
async installExtension(extensionPath) {
const id = await this.browser.installExtension(extensionPath);
await this.#extensionRegistry.registerExtension(id, extensionPath);
return id;
}
async uninstallExtension(id) {
await this.browser.uninstallExtension(id);
this.#extensionRegistry.remove(id);
}
async triggerExtensionAction(id) {
const page = this.getSelectedPptrPage();
// @ts-expect-error internal puppeteer api is needed since we don't have a way to get
// a tab id at the moment
const theTarget = page._tabId;
const session = await this.browser.target().createCDPSession();
try {
await session.send('Extensions.triggerAction', {
id,
targetId: theTarget,
});
}
finally {
await session.detach();
}
}
listExtensions() {
return this.#extensionRegistry.list();
}
getExtension(id) {
return this.#extensionRegistry.getById(id);
}
}
-95
View File
@@ -1,95 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { takeSnapshot } from './tools/snapshot.js';
/**
* Per-page state wrapper. Consolidates dialog, snapshot, emulation,
* and metadata that were previously scattered across Maps in McpContext.
*
* Internal class consumed only by McpContext. Fields are public for direct
* read/write access. The dialog field is private because it requires an
* event listener lifecycle managed by the constructor/dispose pair.
*/
export class McpPage {
pptrPage;
id;
// Snapshot
textSnapshot = null;
uniqueBackendNodeIdToMcpId = new Map();
// Emulation
emulationSettings = {};
// Metadata
isolatedContextName;
devToolsPage;
// Dialog
#dialog;
#dialogHandler;
constructor(page, id) {
this.pptrPage = page;
this.id = id;
this.#dialogHandler = (dialog) => {
this.#dialog = dialog;
};
page.on('dialog', this.#dialogHandler);
}
get dialog() {
return this.#dialog;
}
getDialog() {
return this.dialog;
}
clearDialog() {
this.#dialog = undefined;
}
get networkConditions() {
return this.emulationSettings.networkConditions ?? null;
}
get cpuThrottlingRate() {
return this.emulationSettings.cpuThrottlingRate ?? 1;
}
get geolocation() {
return this.emulationSettings.geolocation ?? null;
}
get viewport() {
return this.emulationSettings.viewport ?? null;
}
get userAgent() {
return this.emulationSettings.userAgent ?? null;
}
get colorScheme() {
return this.emulationSettings.colorScheme ?? null;
}
dispose() {
this.pptrPage.off('dialog', this.#dialogHandler);
}
async getElementByUid(uid) {
if (!this.textSnapshot) {
throw new Error(`No snapshot found for page ${this.id ?? '?'}. Use ${takeSnapshot.name} to capture one.`);
}
const node = this.textSnapshot.idToNode.get(uid);
if (!node) {
throw new Error(`Element uid "${uid}" not found on page ${this.id}.`);
}
return this.#resolveElementHandle(node, uid);
}
async #resolveElementHandle(node, uid) {
const message = `Element with uid ${uid} no longer exists on the page.`;
try {
const handle = await node.elementHandle();
if (!handle) {
throw new Error(message);
}
return handle;
}
catch (error) {
throw new Error(message, {
cause: error,
});
}
}
getAXNodeByUid(uid) {
return this.textSnapshot?.idToNode.get(uid);
}
}
-668
View File
@@ -1,668 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { ConsoleFormatter } from './formatters/ConsoleFormatter.js';
import { IssueFormatter } from './formatters/IssueFormatter.js';
import { NetworkFormatter } from './formatters/NetworkFormatter.js';
import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
import { UncaughtError } from './PageCollector.js';
import { DevTools } from './third_party/index.js';
import { handleDialog } from './tools/pages.js';
import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
import { paginate } from './utils/pagination.js';
async function getToolGroup(page) {
// Check if there is a `devtoolstooldiscovery` event listener
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
// @ts-expect-error internal API
const client = page.pptrPage._client();
const { listeners } = await client.send('DOMDebugger.getEventListeners', {
objectId: windowHandle.remoteObject().objectId,
});
if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
return;
}
const toolGroup = await page.pptrPage.evaluate(() => {
return new Promise(resolve => {
const event = new CustomEvent('devtoolstooldiscovery');
// @ts-expect-error Adding custom property
event.respondWith = (toolGroup) => {
if (!window.__dtmcp) {
window.__dtmcp = {};
}
window.__dtmcp.toolGroup = toolGroup;
// When receiving a toolGroup for the first time, expose a simple execution helper
if (!window.__dtmcp.executeTool) {
window.__dtmcp.executeTool = async (toolName, args) => {
if (!window.__dtmcp?.toolGroup) {
throw new Error('No tools found on the page');
}
const tool = window.__dtmcp.toolGroup.tools.find(t => t.name === toolName);
if (!tool) {
throw new Error(`Tool ${toolName} not found`);
}
return await tool.execute(args);
};
}
resolve(toolGroup);
};
window.dispatchEvent(event);
// If the page does not synchronously call `event.respondWith`, return instead of timing out
setTimeout(() => {
resolve(undefined);
}, 0);
});
});
return toolGroup;
}
export class McpResponse {
#includePages = false;
#includeExtensionServiceWorkers = false;
#includeExtensionPages = false;
#snapshotParams;
#attachedNetworkRequestId;
#attachedNetworkRequestOptions;
#attachedConsoleMessageId;
#attachedTraceSummary;
#attachedTraceInsight;
#attachedLighthouseResult;
#textResponseLines = [];
#images = [];
#networkRequestsOptions;
#consoleDataOptions;
#listExtensions;
#listInPageTools;
#devToolsData;
#tabId;
#args;
#page;
constructor(args) {
this.#args = args;
}
setPage(page) {
this.#page = page;
}
attachDevToolsData(data) {
this.#devToolsData = data;
}
setTabId(tabId) {
this.#tabId = tabId;
}
setIncludePages(value) {
this.#includePages = value;
if (this.#args.categoryExtensions) {
this.#includeExtensionServiceWorkers = value;
this.#includeExtensionPages = value;
}
}
includeSnapshot(params) {
this.#snapshotParams = params ?? {
verbose: false,
};
}
setListExtensions() {
this.#listExtensions = true;
}
setListInPageTools() {
if (this.#args.categoryInPageTools) {
this.#listInPageTools = true;
}
}
setIncludeNetworkRequests(value, options) {
if (!value) {
this.#networkRequestsOptions = undefined;
return;
}
this.#networkRequestsOptions = {
include: value,
pagination: options?.pageSize || options?.pageIdx
? {
pageSize: options.pageSize,
pageIdx: options.pageIdx,
}
: undefined,
resourceTypes: options?.resourceTypes,
includePreservedRequests: options?.includePreservedRequests,
networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI,
};
}
setIncludeConsoleData(value, options) {
if (!value) {
this.#consoleDataOptions = undefined;
return;
}
this.#consoleDataOptions = {
include: value,
pagination: options?.pageSize || options?.pageIdx
? {
pageSize: options.pageSize,
pageIdx: options.pageIdx,
}
: undefined,
types: options?.types,
includePreservedMessages: options?.includePreservedMessages,
};
}
attachNetworkRequest(reqId, options) {
this.#attachedNetworkRequestId = reqId;
this.#attachedNetworkRequestOptions = options;
}
attachConsoleMessage(msgid) {
this.#attachedConsoleMessageId = msgid;
}
attachTraceSummary(result) {
this.#attachedTraceSummary = result;
}
attachTraceInsight(trace, insightSetId, insightName) {
this.#attachedTraceInsight = {
trace,
insightSetId,
insightName,
};
}
attachLighthouseResult(result) {
this.#attachedLighthouseResult = result;
}
get includePages() {
return this.#includePages;
}
get attachedTraceSummary() {
return this.#attachedTraceSummary;
}
get attachedTracedInsight() {
return this.#attachedTraceInsight;
}
get attachedLighthouseResult() {
return this.#attachedLighthouseResult;
}
get includeNetworkRequests() {
return this.#networkRequestsOptions?.include ?? false;
}
get includeConsoleData() {
return this.#consoleDataOptions?.include ?? false;
}
get attachedNetworkRequestId() {
return this.#attachedNetworkRequestId;
}
get networkRequestsPageIdx() {
return this.#networkRequestsOptions?.pagination?.pageIdx;
}
get consoleMessagesPageIdx() {
return this.#consoleDataOptions?.pagination?.pageIdx;
}
get consoleMessagesTypes() {
return this.#consoleDataOptions?.types;
}
appendResponseLine(value) {
this.#textResponseLines.push(value);
}
attachImage(value) {
this.#images.push(value);
}
get responseLines() {
return this.#textResponseLines;
}
get images() {
return this.#images;
}
get snapshotParams() {
return this.#snapshotParams;
}
async handle(toolName, context) {
if (this.#includePages) {
await context.createPagesSnapshot();
}
if (this.#includeExtensionServiceWorkers) {
await context.createExtensionServiceWorkersSnapshot();
}
let snapshot;
if (this.#snapshotParams) {
if (!this.#page) {
throw new Error('Response must have a page');
}
await context.createTextSnapshot(this.#page, this.#snapshotParams.verbose, this.#devToolsData);
const textSnapshot = this.#page.textSnapshot;
if (textSnapshot) {
const formatter = new SnapshotFormatter(textSnapshot);
if (this.#snapshotParams.filePath) {
await context.saveFile(new TextEncoder().encode(formatter.toString()), this.#snapshotParams.filePath);
snapshot = this.#snapshotParams.filePath;
}
else {
snapshot = formatter;
}
}
}
let detailedNetworkRequest;
if (this.#attachedNetworkRequestId) {
if (!this.#page) {
throw new Error(`Response must have an McpPage`);
}
const request = context.getNetworkRequestById(this.#page, this.#attachedNetworkRequestId);
const formatter = await NetworkFormatter.from(request, {
requestId: this.#attachedNetworkRequestId,
requestIdResolver: req => context.getNetworkRequestStableId(req),
fetchData: true,
requestFilePath: this.#attachedNetworkRequestOptions?.requestFilePath,
responseFilePath: this.#attachedNetworkRequestOptions?.responseFilePath,
saveFile: (data, filename) => context.saveFile(data, filename),
});
detailedNetworkRequest = formatter;
}
let detailedConsoleMessage;
if (this.#attachedConsoleMessageId) {
if (!this.#page) {
throw new Error(`Response must have an McpPage`);
}
const message = context.getConsoleMessageById(this.#page, this.#attachedConsoleMessageId);
const consoleMessageStableId = this.#attachedConsoleMessageId;
if ('args' in message || message instanceof UncaughtError) {
const consoleMessage = message;
const devTools = context.getDevToolsUniverse(this.#page);
detailedConsoleMessage = await ConsoleFormatter.from(consoleMessage, {
id: consoleMessageStableId,
fetchDetailedData: true,
devTools: devTools ?? undefined,
});
}
else if (message instanceof DevTools.AggregatedIssue) {
const formatter = new IssueFormatter(message, {
id: consoleMessageStableId,
requestIdResolver: context.resolveCdpRequestId.bind(context, this.#page),
elementIdResolver: context.resolveCdpElementId.bind(context, this.#page),
});
if (!formatter.isValid()) {
throw new Error("Can't provide details for the msgid " + consoleMessageStableId);
}
detailedConsoleMessage = formatter;
}
}
let extensions;
if (this.#listExtensions) {
extensions = context.listExtensions();
}
let inPageTools;
if (this.#listInPageTools) {
inPageTools = await getToolGroup(context.getSelectedMcpPage());
context.setInPageTools(inPageTools);
}
let consoleMessages;
if (this.#consoleDataOptions?.include) {
if (!this.#page) {
throw new Error(`Response must have an McpPage`);
}
const page = this.#page;
let messages = context.getConsoleData(this.#page, this.#consoleDataOptions.includePreservedMessages);
if (this.#consoleDataOptions.types?.length) {
const normalizedTypes = new Set(this.#consoleDataOptions.types);
messages = messages.filter(message => {
if ('type' in message) {
return normalizedTypes.has(message.type());
}
if (message instanceof DevTools.AggregatedIssue) {
return normalizedTypes.has('issue');
}
return normalizedTypes.has('error');
});
}
consoleMessages = (await Promise.all(messages.map(async (item) => {
const consoleMessageStableId = context.getConsoleMessageStableId(item);
if ('args' in item || item instanceof UncaughtError) {
const consoleMessage = item;
const devTools = context.getDevToolsUniverse(page);
return await ConsoleFormatter.from(consoleMessage, {
id: consoleMessageStableId,
fetchDetailedData: false,
devTools: devTools ?? undefined,
});
}
if (item instanceof DevTools.AggregatedIssue) {
const formatter = new IssueFormatter(item, {
id: consoleMessageStableId,
});
if (!formatter.isValid()) {
return null;
}
return formatter;
}
return null;
}))).filter(item => item !== null);
}
let networkRequests;
if (this.#networkRequestsOptions?.include) {
if (!this.#page) {
throw new Error(`Response must have an McpPage`);
}
let requests = context.getNetworkRequests(this.#page, this.#networkRequestsOptions?.includePreservedRequests);
// Apply resource type filtering if specified
if (this.#networkRequestsOptions.resourceTypes?.length) {
const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
requests = requests.filter(request => {
const type = request.resourceType();
return normalizedTypes.has(type);
});
}
if (requests.length) {
networkRequests = await Promise.all(requests.map(request => NetworkFormatter.from(request, {
requestId: context.getNetworkRequestStableId(request),
selectedInDevToolsUI: context.getNetworkRequestStableId(request) ===
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI,
fetchData: false,
saveFile: (data, filename) => context.saveFile(data, filename),
})));
}
}
return this.format(toolName, context, {
detailedConsoleMessage,
consoleMessages,
snapshot,
detailedNetworkRequest,
networkRequests,
traceInsight: this.#attachedTraceInsight,
traceSummary: this.#attachedTraceSummary,
extensions,
lighthouseResult: this.#attachedLighthouseResult,
inPageTools,
});
}
format(toolName, context, data) {
const structuredContent = {};
const response = [];
if (this.#textResponseLines.length) {
structuredContent.message = this.#textResponseLines.join('\n');
response.push(...this.#textResponseLines);
}
const networkConditions = this.#page?.networkConditions;
if (networkConditions) {
const timeout = this.#page.pptrPage.getDefaultNavigationTimeout();
response.push(`Emulating network conditions: ${networkConditions}`);
response.push(`Default navigation timeout set to ${timeout} ms`);
structuredContent.networkConditions = networkConditions;
structuredContent.navigationTimeout = timeout;
}
const viewport = this.#page?.viewport;
if (viewport) {
response.push(`Emulating viewport: ${JSON.stringify(viewport)}`);
structuredContent.viewport = viewport;
}
const userAgent = this.#page?.userAgent;
if (userAgent) {
response.push(`Emulating user agent: ${userAgent}`);
structuredContent.userAgent = userAgent;
}
const cpuThrottlingRate = this.#page?.cpuThrottlingRate ?? 1;
if (cpuThrottlingRate > 1) {
response.push(`Emulating CPU throttling: ${cpuThrottlingRate}x slowdown`);
structuredContent.cpuThrottlingRate = cpuThrottlingRate;
}
const colorScheme = this.#page?.colorScheme;
if (colorScheme) {
response.push(`Emulating color scheme: ${colorScheme}`);
structuredContent.colorScheme = colorScheme;
}
const dialog = this.#page?.getDialog();
if (dialog) {
const defaultValueIfNeeded = dialog.type() === 'prompt'
? ` (default value: "${dialog.defaultValue()}")`
: '';
response.push(`# Open dialog
${dialog.type()}: ${dialog.message()}${defaultValueIfNeeded}.
Call ${handleDialog.name} to handle it before continuing.`);
structuredContent.dialog = {
type: dialog.type(),
message: dialog.message(),
defaultValue: dialog.defaultValue(),
};
}
if (this.#includePages) {
const allPages = context.getPages();
const { regularPages, extensionPages } = allPages.reduce((acc, page) => {
if (page.url().startsWith('chrome-extension://')) {
acc.extensionPages.push(page);
}
else {
acc.regularPages.push(page);
}
return acc;
}, { regularPages: [], extensionPages: [] });
if (regularPages.length) {
const parts = [`## Pages`];
const structuredPages = [];
for (const page of regularPages) {
const isolatedContextName = context.getIsolatedContextName(page);
const contextLabel = isolatedContextName
? ` isolatedContext=${isolatedContextName}`
: '';
parts.push(`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
structuredPages.push(createStructuredPage(page, context));
}
response.push(...parts);
structuredContent.pages = structuredPages;
}
if (this.#includeExtensionPages) {
if (extensionPages.length) {
response.push(`## Extension Pages`);
const structuredExtensionPages = [];
for (const page of extensionPages) {
const isolatedContextName = context.getIsolatedContextName(page);
const contextLabel = isolatedContextName
? ` isolatedContext=${isolatedContextName}`
: '';
response.push(`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
structuredExtensionPages.push(createStructuredPage(page, context));
}
structuredContent.extensionPages = structuredExtensionPages;
}
}
}
if (this.#includeExtensionServiceWorkers) {
if (context.getExtensionServiceWorkers().length) {
response.push(`## Extension Service Workers`);
}
for (const extensionServiceWorker of context.getExtensionServiceWorkers()) {
response.push(`${extensionServiceWorker.id}: ${extensionServiceWorker.url}`);
}
structuredContent.extensionServiceWorkers = context
.getExtensionServiceWorkers()
.map(extensionServiceWorker => {
return {
id: extensionServiceWorker.id,
url: extensionServiceWorker.url,
};
});
}
if (this.#tabId) {
structuredContent.tabId = this.#tabId;
}
if (data.traceSummary) {
const summary = getTraceSummary(data.traceSummary);
response.push(summary);
structuredContent.traceSummary = summary;
structuredContent.traceInsights = [];
for (const insightSet of data.traceSummary.insights?.values() ?? []) {
for (const [insightName, model] of Object.entries(insightSet.model)) {
structuredContent.traceInsights.push({
insightName,
insightKey: model.insightKey,
});
}
}
}
if (data.traceInsight) {
const insightOutput = getInsightOutput(data.traceInsight.trace, data.traceInsight.insightSetId, data.traceInsight.insightName);
if ('error' in insightOutput) {
response.push(insightOutput.error);
}
else {
response.push(insightOutput.output);
}
}
if (data.lighthouseResult) {
structuredContent.lighthouseResult = data.lighthouseResult;
const { summary, reports } = data.lighthouseResult;
response.push('## Lighthouse Audit Results');
response.push(`Mode: ${summary.mode}`);
response.push(`Device: ${summary.device}`);
response.push(`URL: ${summary.url}`);
response.push('### Category Scores');
for (const score of summary.scores) {
response.push(`- ${score.title}: ${(score.score ?? 0) * 100} (${score.id})`);
}
response.push('### Audit Summary');
response.push(`Passed: ${summary.audits.passed}`);
response.push(`Failed: ${summary.audits.failed}`);
response.push(`Total Timing: ${summary.timing.total}ms`);
response.push('### Reports');
for (const report of reports) {
response.push(`- ${report}`);
}
}
if (data.snapshot) {
if (typeof data.snapshot === 'string') {
response.push(`Saved snapshot to ${data.snapshot}.`);
structuredContent.snapshotFilePath = data.snapshot;
}
else {
response.push('## Latest page snapshot');
response.push(data.snapshot.toString());
structuredContent.snapshot = data.snapshot.toJSON();
}
}
if (data.detailedNetworkRequest) {
response.push(data.detailedNetworkRequest.toStringDetailed());
structuredContent.networkRequest =
data.detailedNetworkRequest.toJSONDetailed();
}
if (data.detailedConsoleMessage) {
response.push(data.detailedConsoleMessage.toStringDetailed());
structuredContent.consoleMessage =
data.detailedConsoleMessage.toJSONDetailed();
}
if (data.extensions) {
structuredContent.extensions = data.extensions;
response.push('## Extensions');
if (data.extensions.length === 0) {
response.push('No extensions installed.');
}
else {
const extensionsMessage = data.extensions
.map(extension => {
return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.isEnabled ? 'Enabled' : 'Disabled'}`;
})
.join('\n');
response.push(extensionsMessage);
}
}
if (this.#listInPageTools) {
structuredContent.inPageTools = data.inPageTools ?? undefined;
response.push('## In-page tools');
if (!data.inPageTools || !data.inPageTools.tools) {
response.push('No in-page tools available.');
}
else {
const toolGroup = data.inPageTools;
response.push(`${toolGroup.name}: ${toolGroup.description}`);
response.push('Available tools:');
const toolDefinitionsMessage = toolGroup.tools
.map(tool => {
return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}`;
})
.join('\n');
response.push(toolDefinitionsMessage);
}
}
if (this.#networkRequestsOptions?.include && data.networkRequests) {
const requests = data.networkRequests;
response.push('## Network requests');
if (requests.length) {
const paginationData = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
structuredContent.pagination = paginationData.pagination;
response.push(...paginationData.info);
if (data.networkRequests) {
structuredContent.networkRequests = [];
for (const formatter of paginationData.items) {
response.push(formatter.toString());
structuredContent.networkRequests.push(formatter.toJSON());
}
}
}
else {
response.push('No requests found.');
}
}
if (this.#consoleDataOptions?.include) {
const messages = data.consoleMessages ?? [];
response.push('## Console messages');
if (messages.length) {
const paginationData = this.#dataWithPagination(messages, this.#consoleDataOptions.pagination);
structuredContent.pagination = paginationData.pagination;
response.push(...paginationData.info);
response.push(...paginationData.items.map(message => message.toString()));
structuredContent.consoleMessages = paginationData.items.map(message => message.toJSON());
}
else {
response.push('<no console messages found>');
}
}
const text = {
type: 'text',
text: response.join('\n'),
};
const images = this.#images.map(imageData => {
return {
type: 'image',
...imageData,
};
});
return {
content: [text, ...images],
structuredContent,
};
}
#dataWithPagination(data, pagination) {
const response = [];
const paginationResult = paginate(data, pagination);
if (paginationResult.invalidPage) {
response.push('Invalid page number provided. Showing first page.');
}
const { startIndex, endIndex, currentPage, totalPages } = paginationResult;
response.push(`Showing ${startIndex + 1}-${endIndex} of ${data.length} (Page ${currentPage + 1} of ${totalPages}).`);
if (pagination) {
if (paginationResult.hasNextPage) {
response.push(`Next page: ${currentPage + 1}`);
}
if (paginationResult.hasPreviousPage) {
response.push(`Previous page: ${currentPage - 1}`);
}
}
return {
info: response,
items: paginationResult.items,
pagination: {
currentPage: paginationResult.currentPage,
totalPages: paginationResult.totalPages,
hasNextPage: paginationResult.hasNextPage,
hasPreviousPage: paginationResult.hasPreviousPage,
startIndex: paginationResult.startIndex,
endIndex: paginationResult.endIndex,
invalidPage: paginationResult.invalidPage,
},
};
}
resetResponseLineForTesting() {
this.#textResponseLines = [];
}
}
function createStructuredPage(page, context) {
const isolatedContextName = context.getIsolatedContextName(page);
const entry = {
id: context.getPageId(page),
url: page.url(),
selected: context.isPageSelected(page),
};
if (isolatedContextName) {
entry.isolatedContext = isolatedContextName;
}
return entry;
}
-37
View File
@@ -1,37 +0,0 @@
/**
* @license
* Copyright 2025 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
export class Mutex {
static Guard = class Guard {
#mutex;
constructor(mutex) {
this.#mutex = mutex;
}
dispose() {
return this.#mutex.release();
}
};
#locked = false;
#acquirers = [];
// This is FIFO.
async acquire() {
if (!this.#locked) {
this.#locked = true;
return new Mutex.Guard(this);
}
const { resolve, promise } = Promise.withResolvers();
this.#acquirers.push(resolve);
await promise;
return new Mutex.Guard(this);
}
release() {
const resolve = this.#acquirers.shift();
if (!resolve) {
this.#locked = false;
return;
}
resolve();
}
}
-310
View File
@@ -1,310 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { FakeIssuesManager } from './DevtoolsUtils.js';
import { logger } from './logger.js';
import { DevTools } from './third_party/index.js';
export class UncaughtError {
details;
targetId;
constructor(details, targetId) {
this.details = details;
this.targetId = targetId;
}
}
function createIdGenerator() {
let i = 1;
return () => {
if (i === Number.MAX_SAFE_INTEGER) {
i = 0;
}
return i++;
};
}
export const stableIdSymbol = Symbol('stableIdSymbol');
export class PageCollector {
#browser;
#listenersInitializer;
#listeners = new WeakMap();
maxNavigationSaved = 3;
/**
* This maps a Page to a list of navigations with a sub-list
* of all collected resources.
* The newer navigations come first.
*/
storage = new WeakMap();
constructor(browser, listeners) {
this.#browser = browser;
this.#listenersInitializer = listeners;
}
async init(pages) {
for (const page of pages) {
this.addPage(page);
}
this.#browser.on('targetcreated', this.#onTargetCreated);
this.#browser.on('targetdestroyed', this.#onTargetDestroyed);
}
dispose() {
this.#browser.off('targetcreated', this.#onTargetCreated);
this.#browser.off('targetdestroyed', this.#onTargetDestroyed);
}
#onTargetCreated = async (target) => {
try {
const page = await target.page();
if (!page) {
return;
}
this.addPage(page);
}
catch (err) {
logger('Error getting a page for a target onTargetCreated', err);
}
};
#onTargetDestroyed = async (target) => {
try {
const page = await target.page();
if (!page) {
return;
}
this.cleanupPageDestroyed(page);
}
catch (err) {
logger('Error getting a page for a target onTargetDestroyed', err);
}
};
addPage(page) {
this.#initializePage(page);
}
#initializePage(page) {
if (this.storage.has(page)) {
return;
}
const idGenerator = createIdGenerator();
const storedLists = [[]];
this.storage.set(page, storedLists);
const listeners = this.#listenersInitializer(value => {
const withId = value;
withId[stableIdSymbol] = idGenerator();
const navigations = this.storage.get(page) ?? [[]];
navigations[0].push(withId);
});
listeners['framenavigated'] = (frame) => {
// Only split the storage on main frame navigation
if (frame !== page.mainFrame()) {
return;
}
this.splitAfterNavigation(page);
};
for (const [name, listener] of Object.entries(listeners)) {
page.on(name, listener);
}
this.#listeners.set(page, listeners);
}
splitAfterNavigation(page) {
const navigations = this.storage.get(page);
if (!navigations) {
return;
}
// Add the latest navigation first
navigations.unshift([]);
navigations.splice(this.maxNavigationSaved);
}
cleanupPageDestroyed(page) {
const listeners = this.#listeners.get(page);
if (listeners) {
for (const [name, listener] of Object.entries(listeners)) {
page.off(name, listener);
}
}
this.storage.delete(page);
}
getData(page, includePreservedData) {
const navigations = this.storage.get(page);
if (!navigations) {
return [];
}
if (!includePreservedData) {
return navigations[0];
}
const data = [];
for (let index = this.maxNavigationSaved; index >= 0; index--) {
if (navigations[index]) {
data.push(...navigations[index]);
}
}
return data;
}
getIdForResource(resource) {
return resource[stableIdSymbol] ?? -1;
}
getById(page, stableId) {
const navigations = this.storage.get(page);
if (!navigations) {
throw new Error('No requests found for selected page');
}
const item = this.find(page, item => item[stableIdSymbol] === stableId);
if (item) {
return item;
}
throw new Error('Request not found for selected page');
}
find(page, filter) {
const navigations = this.storage.get(page);
if (!navigations) {
return;
}
for (const navigation of navigations) {
const item = navigation.find(filter);
if (item) {
return item;
}
}
return;
}
}
export class ConsoleCollector extends PageCollector {
#subscribedPages = new WeakMap();
addPage(page) {
super.addPage(page);
if (!this.#subscribedPages.has(page)) {
const subscriber = new PageEventSubscriber(page);
this.#subscribedPages.set(page, subscriber);
void subscriber.subscribe();
}
}
cleanupPageDestroyed(page) {
super.cleanupPageDestroyed(page);
this.#subscribedPages.get(page)?.unsubscribe();
this.#subscribedPages.delete(page);
}
}
class PageEventSubscriber {
#issueManager = new FakeIssuesManager();
#issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
#seenKeys = new Set();
#seenIssues = new Set();
#page;
#session;
#targetId;
constructor(page) {
this.#page = page;
// @ts-expect-error use existing CDP client (internal Puppeteer API).
this.#session = this.#page._client();
// @ts-expect-error use internal Puppeteer API to get target ID
this.#targetId = this.#session.target()._targetId;
}
#resetIssueAggregator() {
this.#issueManager = new FakeIssuesManager();
if (this.#issueAggregator) {
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
}
this.#issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
this.#issueAggregator.addEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
}
async subscribe() {
this.#resetIssueAggregator();
this.#page.on('framenavigated', this.#onFrameNavigated);
this.#session.on('Audits.issueAdded', this.#onIssueAdded);
this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
try {
await this.#session.send('Audits.enable');
}
catch (error) {
logger('Error subscribing to issues', error);
}
}
unsubscribe() {
this.#seenKeys.clear();
this.#seenIssues.clear();
this.#page.off('framenavigated', this.#onFrameNavigated);
this.#session.off('Audits.issueAdded', this.#onIssueAdded);
this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
if (this.#issueAggregator) {
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
}
void this.#session.send('Audits.disable').catch(() => {
// might fail.
});
}
#onAggregatedIssue = (event) => {
if (this.#seenIssues.has(event.data)) {
return;
}
this.#seenIssues.add(event.data);
this.#page.emit('issue', event.data);
};
#onExceptionThrown = (event) => {
this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
};
// On navigation, we reset issue aggregation.
#onFrameNavigated = (frame) => {
// Only split the storage on main frame navigation
if (frame !== frame.page().mainFrame()) {
return;
}
this.#seenKeys.clear();
this.#seenIssues.clear();
this.#resetIssueAggregator();
};
#onIssueAdded = (data) => {
try {
const inspectorIssue = data.issue;
const issue = DevTools.createIssuesFromProtocolIssue(null,
// @ts-expect-error Protocol types diverge.
inspectorIssue)[0];
if (!issue) {
logger('No issue mapping for for the issue: ', inspectorIssue.code);
return;
}
const primaryKey = issue.primaryKey();
if (this.#seenKeys.has(primaryKey)) {
return;
}
this.#seenKeys.add(primaryKey);
this.#issueManager.dispatchEventToListeners("IssueAdded" /* DevTools.IssuesManagerEvents.ISSUE_ADDED */, {
issue,
// @ts-expect-error We don't care that issues model is null
issuesModel: null,
});
}
catch (error) {
logger('Error creating a new issue', error);
}
};
}
export class NetworkCollector extends PageCollector {
constructor(browser, listeners = collect => {
return {
request: req => {
collect(req);
},
};
}) {
super(browser, listeners);
}
splitAfterNavigation(page) {
const navigations = this.storage.get(page) ?? [];
if (!navigations) {
return;
}
const requests = navigations[0];
const lastRequestIdx = requests.findLastIndex(request => {
return request.frame() === page.mainFrame()
? request.isNavigationRequest()
: false;
});
// Keep all requests since the last navigation request including that
// navigation request itself.
// Keep the reference
if (lastRequestIdx !== -1) {
const fromCurrentNavigation = requests.splice(lastRequestIdx);
navigations.unshift(fromCurrentNavigation);
}
else {
navigations.unshift([]);
}
navigations.splice(this.maxNavigationSaved);
}
}
-18
View File
@@ -1,18 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { McpResponse } from './McpResponse.js';
export class SlimMcpResponse extends McpResponse {
async handle(_toolName, _context) {
const text = {
type: 'text',
text: this.responseLines.join('\n'),
};
return {
content: [text],
structuredContent: text,
};
}
}
-139
View File
@@ -1,139 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { logger } from './logger.js';
export class WaitForHelper {
#abortController = new AbortController();
#page;
#stableDomTimeout;
#stableDomFor;
#expectNavigationIn;
#navigationTimeout;
constructor(page, cpuTimeoutMultiplier, networkTimeoutMultiplier) {
this.#stableDomTimeout = 3000 * cpuTimeoutMultiplier;
this.#stableDomFor = 100 * cpuTimeoutMultiplier;
this.#expectNavigationIn = 100 * cpuTimeoutMultiplier;
this.#navigationTimeout = 3000 * networkTimeoutMultiplier;
this.#page = page;
}
/**
* A wrapper that executes a action and waits for
* a potential navigation, after which it waits
* for the DOM to be stable before returning.
*/
async waitForStableDom() {
const stableDomObserver = await this.#page.evaluateHandle(timeout => {
let timeoutId;
function callback() {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
domObserver.resolver.resolve();
domObserver.observer.disconnect();
}, timeout);
}
const domObserver = {
resolver: Promise.withResolvers(),
observer: new MutationObserver(callback),
};
// It's possible that the DOM is not gonna change so we
// need to start the timeout initially.
callback();
domObserver.observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
});
return domObserver;
}, this.#stableDomFor);
this.#abortController.signal.addEventListener('abort', async () => {
try {
await stableDomObserver.evaluate(observer => {
observer.observer.disconnect();
observer.resolver.resolve();
});
await stableDomObserver.dispose();
}
catch {
// Ignored cleanup errors
}
});
return Promise.race([
stableDomObserver.evaluate(async (observer) => {
return await observer.resolver.promise;
}),
this.timeout(this.#stableDomTimeout).then(() => {
throw new Error('Timeout');
}),
]);
}
async waitForNavigationStarted() {
// Currently Puppeteer does not have API
// For when a navigation is about to start
const navigationStartedPromise = new Promise(resolve => {
const listener = (event) => {
if ([
'historySameDocument',
'historyDifferentDocument',
'sameDocument',
].includes(event.navigationType)) {
resolve(false);
return;
}
resolve(true);
};
this.#page._client().on('Page.frameStartedNavigating', listener);
this.#abortController.signal.addEventListener('abort', () => {
resolve(false);
this.#page._client().off('Page.frameStartedNavigating', listener);
});
});
return await Promise.race([
navigationStartedPromise,
this.timeout(this.#expectNavigationIn).then(() => false),
]);
}
timeout(time) {
return new Promise(res => {
const id = setTimeout(res, time);
this.#abortController.signal.addEventListener('abort', () => {
res();
clearTimeout(id);
});
});
}
async waitForEventsAfterAction(action, options) {
const navigationFinished = this.waitForNavigationStarted()
.then(navigationStated => {
if (navigationStated) {
return this.#page.waitForNavigation({
timeout: options?.timeout ?? this.#navigationTimeout,
signal: this.#abortController.signal,
});
}
return;
})
.catch(error => logger(error));
try {
await action();
}
catch (error) {
// Clear up pending promises
this.#abortController.abort();
throw error;
}
try {
await navigationFinished;
// Wait for stable dom after navigation so we execute in
// the correct context
await this.waitForStableDom();
}
catch (error) {
logger(error);
}
finally {
this.#abortController.abort();
}
}
}
@@ -1,651 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export const commands = {
click: {
description: 'Clicks on the provided element',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot',
required: true,
},
dblClick: {
name: 'dblClick',
type: 'boolean',
description: 'Set to true for double clicks. Default is false.',
required: false,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
close_page: {
description: 'Closes the page by its index. The last open page cannot be closed.',
category: 'Navigation automation',
args: {
pageId: {
name: 'pageId',
type: 'number',
description: 'The ID of the page to close. Call list_pages to list pages.',
required: true,
},
},
},
drag: {
description: 'Drag an element onto another element',
category: 'Input automation',
args: {
from_uid: {
name: 'from_uid',
type: 'string',
description: 'The uid of the element to drag',
required: true,
},
to_uid: {
name: 'to_uid',
type: 'string',
description: 'The uid of the element to drop into',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
emulate: {
description: 'Emulates various features on the selected page.',
category: 'Emulation',
args: {
networkConditions: {
name: 'networkConditions',
type: 'string',
description: 'Throttle network. Omit to disable throttling.',
required: false,
enum: ['Offline', 'Slow 3G', 'Fast 3G', 'Slow 4G', 'Fast 4G'],
},
cpuThrottlingRate: {
name: 'cpuThrottlingRate',
type: 'number',
description: 'Represents the CPU slowdown factor. Omit or set the rate to 1 to disable throttling',
required: false,
},
geolocation: {
name: 'geolocation',
type: 'string',
description: 'Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override.',
required: false,
},
userAgent: {
name: 'userAgent',
type: 'string',
description: 'User agent to emulate. Set to empty string to clear the user agent override.',
required: false,
},
colorScheme: {
name: 'colorScheme',
type: 'string',
description: 'Emulate the dark or the light mode. Set to "auto" to reset to the default.',
required: false,
enum: ['dark', 'light', 'auto'],
},
viewport: {
name: 'viewport',
type: 'string',
description: "Emulate device viewports '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'. 'touch' and 'mobile' to emulate mobile devices. 'landscape' to emulate landscape mode.",
required: false,
},
},
},
evaluate_script: {
description: 'Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON,\nso returned values have to be JSON-serializable.',
category: 'Debugging',
args: {
function: {
name: 'function',
type: 'string',
description: 'A JavaScript function declaration to be executed by the tool in the currently selected page.\nExample without arguments: `() => {\n return document.title\n}` or `async () => {\n return await fetch("example.com")\n}`.\nExample with arguments: `(el) => {\n return el.innerText;\n}`\n',
required: true,
},
args: {
name: 'args',
type: 'array',
description: 'An optional list of arguments to pass to the function.',
required: false,
},
},
},
fill: {
description: 'Type text into a input, text area or select an option from a <select> element.',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot',
required: true,
},
value: {
name: 'value',
type: 'string',
description: 'The value to fill in',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
fill_form: {
description: 'Fill out multiple form elements at once',
category: 'Input automation',
args: {
elements: {
name: 'elements',
type: 'array',
description: 'Elements from snapshot to fill out.',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
get_console_message: {
description: 'Gets a console message by its ID. You can get all messages by calling list_console_messages.',
category: 'Debugging',
args: {
msgid: {
name: 'msgid',
type: 'number',
description: 'The msgid of a console message on the page from the listed console messages',
required: true,
},
},
},
get_network_request: {
description: 'Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel.',
category: 'Network',
args: {
reqid: {
name: 'reqid',
type: 'number',
description: 'The reqid of the network request. If omitted returns the currently selected request in the DevTools Network panel.',
required: false,
},
requestFilePath: {
name: 'requestFilePath',
type: 'string',
description: 'The absolute or relative path to save the request body to. If omitted, the body is returned inline.',
required: false,
},
responseFilePath: {
name: 'responseFilePath',
type: 'string',
description: 'The absolute or relative path to save the response body to. If omitted, the body is returned inline.',
required: false,
},
},
},
handle_dialog: {
description: 'If a browser dialog was opened, use this command to handle it',
category: 'Input automation',
args: {
action: {
name: 'action',
type: 'string',
description: 'Whether to dismiss or accept the dialog',
required: true,
enum: ['accept', 'dismiss'],
},
promptText: {
name: 'promptText',
type: 'string',
description: 'Optional prompt text to enter into the dialog.',
required: false,
},
},
},
hover: {
description: 'Hover over the provided element',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
lighthouse_audit: {
description: 'Get Lighthouse score and reports for accessibility, SEO and best practices. This excludes performance. For performance audits, run performance_start_trace',
category: 'Debugging',
args: {
mode: {
name: 'mode',
type: 'string',
description: '"navigation" reloads & audits. "snapshot" analyzes current state.',
required: false,
default: 'navigation',
enum: ['navigation', 'snapshot'],
},
device: {
name: 'device',
type: 'string',
description: 'Device to emulate.',
required: false,
default: 'desktop',
enum: ['desktop', 'mobile'],
},
outputDirPath: {
name: 'outputDirPath',
type: 'string',
description: 'Directory for reports. If omitted, uses temporary files.',
required: false,
},
},
},
list_console_messages: {
description: 'List all console messages for the currently selected page since the last navigation.',
category: 'Debugging',
args: {
pageSize: {
name: 'pageSize',
type: 'integer',
description: 'Maximum number of messages to return. When omitted, returns all requests.',
required: false,
},
pageIdx: {
name: 'pageIdx',
type: 'integer',
description: 'Page number to return (0-based). When omitted, returns the first page.',
required: false,
},
types: {
name: 'types',
type: 'array',
description: 'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.',
required: false,
},
includePreservedMessages: {
name: 'includePreservedMessages',
type: 'boolean',
description: 'Set to true to return the preserved messages over the last 3 navigations.',
required: false,
default: false,
},
},
},
list_network_requests: {
description: 'List all requests for the currently selected page since the last navigation.',
category: 'Network',
args: {
pageSize: {
name: 'pageSize',
type: 'integer',
description: 'Maximum number of requests to return. When omitted, returns all requests.',
required: false,
},
pageIdx: {
name: 'pageIdx',
type: 'integer',
description: 'Page number to return (0-based). When omitted, returns the first page.',
required: false,
},
resourceTypes: {
name: 'resourceTypes',
type: 'array',
description: 'Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.',
required: false,
},
includePreservedRequests: {
name: 'includePreservedRequests',
type: 'boolean',
description: 'Set to true to return the preserved requests over the last 3 navigations.',
required: false,
default: false,
},
},
},
list_pages: {
description: 'Get a list of pages open in the browser.',
category: 'Navigation automation',
args: {},
},
navigate_page: {
description: 'Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.',
category: 'Navigation automation',
args: {
type: {
name: 'type',
type: 'string',
description: 'Navigate the page by URL, back or forward in history, or reload.',
required: false,
enum: ['url', 'back', 'forward', 'reload'],
},
url: {
name: 'url',
type: 'string',
description: 'Target URL (only type=url)',
required: false,
},
ignoreCache: {
name: 'ignoreCache',
type: 'boolean',
description: 'Whether to ignore cache on reload.',
required: false,
},
handleBeforeUnload: {
name: 'handleBeforeUnload',
type: 'string',
description: 'Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.',
required: false,
enum: ['accept', 'decline'],
},
initScript: {
name: 'initScript',
type: 'string',
description: 'A JavaScript script to be executed on each new document before any other scripts for the next navigation.',
required: false,
},
timeout: {
name: 'timeout',
type: 'integer',
description: 'Maximum wait time in milliseconds. If set to 0, the default timeout will be used.',
required: false,
},
},
},
new_page: {
description: 'Open a new tab and load a URL. Use project URL if not specified otherwise.',
category: 'Navigation automation',
args: {
url: {
name: 'url',
type: 'string',
description: 'URL to load in a new page.',
required: true,
},
background: {
name: 'background',
type: 'boolean',
description: 'Whether to open the page in the background without bringing it to the front. Default is false (foreground).',
required: false,
},
isolatedContext: {
name: 'isolatedContext',
type: 'string',
description: 'If specified, the page is created in an isolated browser context with the given name. Pages in the same browser context share cookies and storage. Pages in different browser contexts are fully isolated.',
required: false,
},
timeout: {
name: 'timeout',
type: 'integer',
description: 'Maximum wait time in milliseconds. If set to 0, the default timeout will be used.',
required: false,
},
},
},
performance_analyze_insight: {
description: 'Provides more detailed information on a specific Performance Insight of an insight set that was highlighted in the results of a trace recording.',
category: 'Performance',
args: {
insightSetId: {
name: 'insightSetId',
type: 'string',
description: 'The id for the specific insight set. Only use the ids given in the "Available insight sets" list.',
required: true,
},
insightName: {
name: 'insightName',
type: 'string',
description: 'The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown"',
required: true,
},
},
},
performance_start_trace: {
description: 'Start a performance trace on the selected webpage. Use to find frontend performance issues, Core Web Vitals (LCP, INP, CLS), and improve page load speed.',
category: 'Performance',
args: {
reload: {
name: 'reload',
type: 'boolean',
description: 'Determines if, once tracing has started, the current selected page should be automatically reloaded. Navigate the page to the right URL using the navigate_page tool BEFORE starting the trace if reload or autoStop is set to true.',
required: false,
default: true,
},
autoStop: {
name: 'autoStop',
type: 'boolean',
description: 'Determines if the trace recording should be automatically stopped.',
required: false,
default: true,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).',
required: false,
},
},
},
performance_stop_trace: {
description: 'Stop the active performance trace recording on the selected webpage.',
category: 'Performance',
args: {
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).',
required: false,
},
},
},
press_key: {
description: 'Press a key or key combination. Use this when other input methods like fill() cannot be used (e.g., keyboard shortcuts, navigation keys, or special key combinations).',
category: 'Input automation',
args: {
key: {
name: 'key',
type: 'string',
description: 'A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
resize_page: {
description: "Resizes the selected page's window so that the page has specified dimension",
category: 'Emulation',
args: {
width: {
name: 'width',
type: 'number',
description: 'Page width',
required: true,
},
height: {
name: 'height',
type: 'number',
description: 'Page height',
required: true,
},
},
},
select_page: {
description: 'Select a page as a context for future tool calls.',
category: 'Navigation automation',
args: {
pageId: {
name: 'pageId',
type: 'number',
description: 'The ID of the page to select. Call list_pages to get available pages.',
required: true,
},
bringToFront: {
name: 'bringToFront',
type: 'boolean',
description: 'Whether to focus the page and bring it to the top.',
required: false,
},
},
},
take_memory_snapshot: {
description: 'Capture a memory heapsnapshot of the currently selected page to memory leak debugging',
category: 'Performance',
args: {
filePath: {
name: 'filePath',
type: 'string',
description: 'A path to a .heapsnapshot file to save the heapsnapshot to.',
required: true,
},
},
},
take_screenshot: {
description: 'Take a screenshot of the page or element.',
category: 'Debugging',
args: {
format: {
name: 'format',
type: 'string',
description: 'Type of format to save the screenshot as. Default is "png"',
required: false,
default: 'png',
enum: ['png', 'jpeg', 'webp'],
},
quality: {
name: 'quality',
type: 'number',
description: 'Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format.',
required: false,
},
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.',
required: false,
},
fullPage: {
name: 'fullPage',
type: 'boolean',
description: 'If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid.',
required: false,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response.',
required: false,
},
},
},
take_snapshot: {
description: 'Take a text snapshot of the currently selected page based on the a11y tree. The snapshot lists page elements along with a unique\nidentifier (uid). Always use the latest snapshot. Prefer taking a snapshot over taking a screenshot. The snapshot indicates the element selected\nin the DevTools Elements panel (if any).',
category: 'Debugging',
args: {
verbose: {
name: 'verbose',
type: 'boolean',
description: 'Whether to include all possible information available in the full a11y tree. Default is false.',
required: false,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute path, or a path relative to the current working directory, to save the snapshot to instead of attaching it to the response.',
required: false,
},
},
},
type_text: {
description: 'Type text using keyboard into a previously focused input',
category: 'Input automation',
args: {
text: {
name: 'text',
type: 'string',
description: 'The text to type',
required: true,
},
submitKey: {
name: 'submitKey',
type: 'string',
description: 'Optional key to press after typing. E.g., "Enter", "Tab", "Escape"',
required: false,
},
},
},
upload_file: {
description: 'Upload a file through a provided element.',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of the file input element or an element that will open file chooser on the page from the page content snapshot',
required: true,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The local path of the file to upload',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
wait_for: {
description: 'Wait for the specified text to appear on the selected page.',
category: 'Navigation automation',
args: {
text: {
name: 'text',
type: 'array',
description: 'Non-empty list of texts. Resolves when any value appears on the page.',
required: true,
},
timeout: {
name: 'timeout',
type: 'integer',
description: 'Maximum wait time in milliseconds. If set to 0, the default timeout will be used.',
required: false,
},
},
},
};
@@ -1,322 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { yargs, hideBin } from '../third_party/index.js';
export const cliOptions = {
autoConnect: {
type: 'boolean',
description: 'If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.',
conflicts: ['isolated', 'executablePath', 'categoryExtensions'],
default: false,
coerce: (value) => {
if (!value) {
return;
}
return value;
},
},
browserUrl: {
type: 'string',
description: 'Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.',
alias: 'u',
conflicts: ['wsEndpoint', 'categoryExtensions'],
coerce: (url) => {
if (!url) {
return;
}
try {
new URL(url);
}
catch {
throw new Error(`Provided browserUrl ${url} is not valid URL.`);
}
return url;
},
},
wsEndpoint: {
type: 'string',
description: 'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
alias: 'w',
conflicts: ['browserUrl', 'categoryExtensions'],
coerce: (url) => {
if (!url) {
return;
}
try {
const parsed = new URL(url);
if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {
throw new Error(`Provided wsEndpoint ${url} must use ws:// or wss:// protocol.`);
}
return url;
}
catch (error) {
if (error.message.includes('ws://')) {
throw error;
}
throw new Error(`Provided wsEndpoint ${url} is not valid URL.`);
}
},
},
wsHeaders: {
type: 'string',
description: 'Custom headers for WebSocket connection in JSON format (e.g., \'{"Authorization":"Bearer token"}\'). Only works with --wsEndpoint.',
implies: 'wsEndpoint',
coerce: (val) => {
if (!val) {
return;
}
try {
const parsed = JSON.parse(val);
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('Headers must be a JSON object');
}
return parsed;
}
catch (error) {
throw new Error(`Invalid JSON for wsHeaders: ${error.message}`);
}
},
},
headless: {
type: 'boolean',
description: 'Whether to run in headless (no UI) mode.',
default: false,
},
executablePath: {
type: 'string',
description: 'Path to custom Chrome executable.',
conflicts: ['browserUrl', 'wsEndpoint'],
alias: 'e',
},
isolated: {
type: 'boolean',
description: 'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.',
},
userDataDir: {
type: 'string',
description: 'Path to the user data directory for Chrome. Default is $HOME/.cache/chrome-devtools-mcp/chrome-profile$CHANNEL_SUFFIX_IF_NON_STABLE',
conflicts: ['browserUrl', 'wsEndpoint', 'isolated'],
},
channel: {
type: 'string',
description: 'Specify a different Chrome channel that should be used. The default is the stable channel version.',
choices: ['stable', 'canary', 'beta', 'dev'],
conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
},
logFile: {
type: 'string',
describe: 'Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.',
},
viewport: {
type: 'string',
describe: 'Initial viewport size for the Chrome instances started by the server. For example, `1280x720`. In headless mode, max size is 3840x2160px.',
coerce: (arg) => {
if (arg === undefined) {
return;
}
const [width, height] = arg.split('x').map(Number);
if (!width || !height || Number.isNaN(width) || Number.isNaN(height)) {
throw new Error('Invalid viewport. Expected format is `1280x720`.');
}
return {
width,
height,
};
},
},
proxyServer: {
type: 'string',
description: `Proxy server configuration for Chrome passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details.`,
},
acceptInsecureCerts: {
type: 'boolean',
description: `If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.`,
},
experimentalPageIdRouting: {
type: 'boolean',
describe: 'Whether to expose pageId on page-scoped tools and route requests by page ID.',
hidden: true,
},
experimentalDevtools: {
type: 'boolean',
describe: 'Whether to enable automation over DevTools targets',
hidden: true,
},
experimentalVision: {
type: 'boolean',
describe: 'Whether to enable vision tools',
hidden: true,
},
experimentalStructuredContent: {
type: 'boolean',
describe: 'Whether to output structured formatted content.',
hidden: true,
},
experimentalIncludeAllPages: {
type: 'boolean',
describe: 'Whether to include all kinds of pages such as webviews or background pages as pages.',
hidden: true,
},
experimentalInteropTools: {
type: 'boolean',
describe: 'Whether to enable interoperability tools',
hidden: true,
},
experimentalScreencast: {
type: 'boolean',
describe: 'Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.',
},
chromeArg: {
type: 'array',
describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
},
ignoreDefaultChromeArg: {
type: 'array',
describe: 'Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
},
categoryEmulation: {
type: 'boolean',
default: true,
describe: 'Set to false to exclude tools related to emulation.',
},
categoryPerformance: {
type: 'boolean',
default: true,
describe: 'Set to false to exclude tools related to performance.',
},
categoryNetwork: {
type: 'boolean',
default: true,
describe: 'Set to false to exclude tools related to network.',
},
categoryExtensions: {
type: 'boolean',
hidden: true,
conflicts: ['browserUrl', 'autoConnect', 'wsEndpoint'],
describe: 'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect is not supported.',
},
categoryInPageTools: {
type: 'boolean',
hidden: true,
describe: 'Set to true to enable tools exposed by the inspected page itself',
},
performanceCrux: {
type: 'boolean',
default: true,
describe: 'Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.',
},
usageStatistics: {
type: 'boolean',
default: true,
describe: 'Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.',
},
clearcutEndpoint: {
type: 'string',
hidden: true,
describe: 'Endpoint for Clearcut telemetry.',
},
clearcutForceFlushIntervalMs: {
type: 'number',
hidden: true,
describe: 'Force flush interval in milliseconds (for testing).',
},
clearcutIncludePidHeader: {
type: 'boolean',
hidden: true,
describe: 'Include watchdog PID in Clearcut request headers (for testing).',
},
slim: {
type: 'boolean',
describe: 'Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.',
},
viaCli: {
type: 'boolean',
describe: 'Set by Chrome DevTools CLI if the MCP server is started via the CLI client (this arg exists for usage stats)',
hidden: true,
},
};
export function parseArguments(version, argv = process.argv) {
const yargsInstance = yargs(hideBin(argv))
.scriptName('npx chrome-devtools-mcp@latest')
.options(cliOptions)
.check(args => {
// We can't set default in the options else
// Yargs will complain
if (!args.channel &&
!args.browserUrl &&
!args.wsEndpoint &&
!args.executablePath) {
args.channel = 'stable';
}
return true;
})
.example([
[
'$0 --browserUrl http://127.0.0.1:9222',
'Connect to an existing browser instance via HTTP',
],
[
'$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123',
'Connect to an existing browser instance via WebSocket',
],
[
`$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123 --wsHeaders '{"Authorization":"Bearer token"}'`,
'Connect via WebSocket with custom headers',
],
['$0 --channel beta', 'Use Chrome Beta installed on this system'],
['$0 --channel canary', 'Use Chrome Canary installed on this system'],
['$0 --channel dev', 'Use Chrome Dev installed on this system'],
['$0 --channel stable', 'Use stable Chrome installed on this system'],
['$0 --logFile /tmp/log.txt', 'Save logs to a file'],
['$0 --help', 'Print CLI options'],
[
'$0 --viewport 1280x720',
'Launch Chrome with the initial viewport size of 1280x720px',
],
[
`$0 --chrome-arg='--no-sandbox' --chrome-arg='--disable-setuid-sandbox'`,
'Launch Chrome without sandboxes. Use with caution.',
],
[
`$0 --ignore-default-chrome-arg='--disable-extensions'`,
'Disable the default arguments provided by Puppeteer. Use with caution.',
],
['$0 --no-category-emulation', 'Disable tools in the emulation category'],
[
'$0 --no-category-performance',
'Disable tools in the performance category',
],
['$0 --no-category-network', 'Disable tools in the network category'],
[
'$0 --user-data-dir=/tmp/user-data-dir',
'Use a custom user data directory',
],
[
'$0 --auto-connect',
'Connect to a stable Chrome instance (Chrome 144+) running instead of launching a new instance',
],
[
'$0 --auto-connect --channel=canary',
'Connect to a canary Chrome instance (Chrome 144+) running instead of launching a new instance',
],
[
'$0 --no-usage-statistics',
'Do not send usage statistics https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics.',
],
[
'$0 --no-performance-crux',
'Disable CrUX (field data) integration in performance tools.',
],
[
'$0 --slim',
'Only 3 tools: navigation, JavaScript execution and screenshot',
],
]);
return yargsInstance
.wrap(Math.min(120, yargsInstance.terminalWidth()))
.help()
.version(version)
.parseSync();
}
@@ -1,35 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../polyfill.js';
import process from 'node:process';
import { createMcpServer, logDisclaimers } from '../index.js';
import { logger, saveLogsToFile } from '../logger.js';
import { computeFlagUsage } from '../telemetry/flagUtils.js';
import { StdioServerTransport } from '../third_party/index.js';
import { VERSION } from '../version.js';
import { cliOptions, parseArguments } from './chrome-devtools-mcp-cli-options.js';
export const args = parseArguments(VERSION);
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
if (process.env['CI'] ||
process.env['CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS']) {
console.error("turning off usage statistics. process.env['CI'] || process.env['CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS'] is set.");
args.usageStatistics = false;
}
if (process.env['CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT'] !== 'true') {
process.on('unhandledRejection', (reason, promise) => {
logger('Unhandled promise rejection', promise, reason);
});
}
logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
const { server, clearcutLogger } = await createMcpServer(args, {
logFile,
});
const transport = new StdioServerTransport();
await server.connect(transport);
logger('Chrome DevTools MCP Server connected');
logDisclaimers(args);
void clearcutLogger?.logDailyActiveIfNeeded();
void clearcutLogger?.logServerStart(computeFlagUsage(args, cliOptions));
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env node
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
process.title = 'chrome-devtools-mcp';
import { version } from 'node:process';
const [major, minor] = version.substring(1).split('.').map(Number);
if (major === 20 && minor < 19) {
console.error(`ERROR: \`chrome-devtools-mcp\` does not support Node ${process.version}. Please upgrade to Node 20.19.0 LTS or a newer LTS.`);
process.exit(1);
}
if (major === 22 && minor < 12) {
console.error(`ERROR: \`chrome-devtools-mcp\` does not support Node ${process.version}. Please upgrade to Node 22.12.0 LTS or a newer LTS.`);
process.exit(1);
}
if (major < 20) {
console.error(`ERROR: \`chrome-devtools-mcp\` does not support Node ${process.version}. Please upgrade to Node 20.19.0 LTS or a newer LTS.`);
process.exit(1);
}
await import('./chrome-devtools-mcp-main.js');
-188
View File
@@ -1,188 +0,0 @@
#!/usr/bin/env node
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
process.title = 'chrome-devtools';
import process from 'node:process';
import { startDaemon, stopDaemon, sendCommand, handleResponse, } from '../daemon/client.js';
import { isDaemonRunning, serializeArgs } from '../daemon/utils.js';
import { logDisclaimers } from '../index.js';
import { hideBin, yargs } from '../third_party/index.js';
import { VERSION } from '../version.js';
import { commands } from './chrome-devtools-cli-options.js';
import { cliOptions, parseArguments } from './chrome-devtools-mcp-cli-options.js';
async function start(args) {
const combinedArgs = [...args, ...defaultArgs];
await startDaemon(combinedArgs);
logDisclaimers(parseArguments(VERSION, combinedArgs));
}
const defaultArgs = ['--viaCli', '--experimentalStructuredContent'];
const startCliOptions = {
...cliOptions,
};
// Not supported in CLI on purpose.
delete startCliOptions.autoConnect;
// Missing CLI serialization.
delete startCliOptions.viewport;
// CLI is generated based on the default tool definitions. To enable conditional
// tools, they need to be enabled during CLI generation.
delete startCliOptions.experimentalPageIdRouting;
delete startCliOptions.experimentalVision;
delete startCliOptions.experimentalInteropTools;
delete startCliOptions.experimentalScreencast;
delete startCliOptions.categoryEmulation;
delete startCliOptions.categoryPerformance;
delete startCliOptions.categoryNetwork;
delete startCliOptions.categoryExtensions;
// Always on in CLI.
delete startCliOptions.experimentalStructuredContent;
// Change the defaults.
if (!('default' in cliOptions.headless)) {
throw new Error('headless cli option unexpectedly does not have a default');
}
if ('default' in cliOptions.isolated) {
throw new Error('isolated cli option unexpectedly has a default');
}
startCliOptions.headless.default = true;
startCliOptions.isolated.description =
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to true unless userDataDir is provided.';
const y = yargs(hideBin(process.argv))
.scriptName('chrome-devtools')
.showHelpOnFail(true)
.usage('chrome-devtools <command> [...args] --flags')
.usage(`Run 'chrome-devtools <command> --help' for help on the specific command.`)
.demandCommand()
.version(VERSION)
.strict()
.help(true)
.wrap(120);
y.command('start', 'Start or restart chrome-devtools-mcp', y => y
.options(startCliOptions)
.example('$0 start --browserUrl http://localhost:9222', 'Start the server connecting to an existing browser')
.strict(), async (argv) => {
if (isDaemonRunning()) {
await stopDaemon();
}
// Defaults but we do not want to affect the yargs conflict resolution.
if (argv.isolated === undefined && argv.userDataDir === undefined) {
argv.isolated = true;
}
if (argv.headless === undefined) {
argv.headless = true;
}
const args = serializeArgs(cliOptions, argv);
await start(args);
process.exit(0);
}).strict(); // Re-enable strict validation for other commands; this is applied to the yargs instance itself
y.command('status', 'Checks if chrome-devtools-mcp is running', async () => {
if (isDaemonRunning()) {
console.log('chrome-devtools-mcp daemon is running.');
const response = await sendCommand({
method: 'status',
});
if (response.success) {
const data = JSON.parse(response.result);
console.log(`pid=${data.pid} socket=${data.socketPath} start-date=${data.startDate} version=${data.version}`);
console.log(`args=${JSON.stringify(data.args)}`);
}
else {
console.error('Error:', response.error);
process.exit(1);
}
}
else {
console.log('chrome-devtools-mcp daemon is not running.');
}
process.exit(0);
});
y.command('stop', 'Stop chrome-devtools-mcp if any', async () => {
if (!isDaemonRunning()) {
process.exit(0);
}
await stopDaemon();
process.exit(0);
});
for (const [commandName, commandDef] of Object.entries(commands)) {
const args = commandDef.args;
const requiredArgNames = Object.keys(args).filter(name => args[name].required);
const optionalArgNames = Object.keys(args).filter(name => !args[name].required);
let commandStr = commandName;
for (const arg of requiredArgNames) {
commandStr += ` <${arg}>`;
}
for (const arg of optionalArgNames) {
commandStr += ` [--${arg}]`;
}
y.command(commandStr, commandDef.description, y => {
y.option('output-format', {
choices: ['md', 'json'],
default: 'md',
});
for (const [argName, opt] of Object.entries(args)) {
const type = opt.type === 'integer' || opt.type === 'number'
? 'number'
: opt.type === 'boolean'
? 'boolean'
: opt.type === 'array'
? 'array'
: 'string';
if (opt.required) {
const options = {
describe: opt.description,
type: type,
};
if (opt.default !== undefined) {
options.default = opt.default;
}
if (opt.enum) {
options.choices = opt.enum;
}
y.positional(argName, options);
}
else {
const options = {
describe: opt.description,
type: type,
};
if (opt.default !== undefined) {
options.default = opt.default;
}
if (opt.enum) {
options.choices = opt.enum;
}
y.option(argName, options);
}
}
}, async (argv) => {
try {
if (!isDaemonRunning()) {
await start([]);
}
const commandArgs = {};
for (const argName of Object.keys(args)) {
if (argName in argv) {
commandArgs[argName] = argv[argName];
}
}
const response = await sendCommand({
method: 'invoke_tool',
tool: commandName,
args: commandArgs,
});
if (response.success) {
console.log(await handleResponse(JSON.parse(response.result), argv['output-format']));
}
else {
console.error('Error:', response.error);
process.exit(1);
}
}
catch (error) {
console.error('Failed to execute command:', error);
process.exit(1);
}
});
}
await y.parse();
-615
View File
@@ -1,615 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export const commands = {
click: {
description: 'Clicks on the provided element',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot',
required: true,
},
dblClick: {
name: 'dblClick',
type: 'boolean',
description: 'Set to true for double clicks. Default is false.',
required: false,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
close_page: {
description: 'Closes the page by its index. The last open page cannot be closed.',
category: 'Navigation automation',
args: {
pageId: {
name: 'pageId',
type: 'number',
description: 'The ID of the page to close. Call list_pages to list pages.',
required: true,
},
},
},
drag: {
description: 'Drag an element onto another element',
category: 'Input automation',
args: {
from_uid: {
name: 'from_uid',
type: 'string',
description: 'The uid of the element to drag',
required: true,
},
to_uid: {
name: 'to_uid',
type: 'string',
description: 'The uid of the element to drop into',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
emulate: {
description: 'Emulates various features on the selected page.',
category: 'Emulation',
args: {
networkConditions: {
name: 'networkConditions',
type: 'string',
description: 'Throttle network. Omit to disable throttling.',
required: false,
enum: ['Offline', 'Slow 3G', 'Fast 3G', 'Slow 4G', 'Fast 4G'],
},
cpuThrottlingRate: {
name: 'cpuThrottlingRate',
type: 'number',
description: 'Represents the CPU slowdown factor. Omit or set the rate to 1 to disable throttling',
required: false,
},
geolocation: {
name: 'geolocation',
type: 'string',
description: 'Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override.',
required: false,
},
userAgent: {
name: 'userAgent',
type: 'string',
description: 'User agent to emulate. Set to empty string to clear the user agent override.',
required: false,
},
colorScheme: {
name: 'colorScheme',
type: 'string',
description: 'Emulate the dark or the light mode. Set to "auto" to reset to the default.',
required: false,
enum: ['dark', 'light', 'auto'],
},
viewport: {
name: 'viewport',
type: 'string',
description: "Emulate device viewports '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'. 'touch' and 'mobile' to emulate mobile devices. 'landscape' to emulate landscape mode.",
required: false,
},
},
},
evaluate_script: {
description: 'Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON,\nso returned values have to be JSON-serializable.',
category: 'Debugging',
args: {
function: {
name: 'function',
type: 'string',
description: 'A JavaScript function declaration to be executed by the tool in the currently selected page.\nExample without arguments: `() => {\n return document.title\n}` or `async () => {\n return await fetch("example.com")\n}`.\nExample with arguments: `(el) => {\n return el.innerText;\n}`\n',
required: true,
},
args: {
name: 'args',
type: 'array',
description: 'An optional list of arguments to pass to the function.',
required: false,
},
},
},
fill: {
description: 'Type text into a input, text area or select an option from a <select> element.',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot',
required: true,
},
value: {
name: 'value',
type: 'string',
description: 'The value to fill in',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
get_console_message: {
description: 'Gets a console message by its ID. You can get all messages by calling list_console_messages.',
category: 'Debugging',
args: {
msgid: {
name: 'msgid',
type: 'number',
description: 'The msgid of a console message on the page from the listed console messages',
required: true,
},
},
},
get_network_request: {
description: 'Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel.',
category: 'Network',
args: {
reqid: {
name: 'reqid',
type: 'number',
description: 'The reqid of the network request. If omitted returns the currently selected request in the DevTools Network panel.',
required: false,
},
requestFilePath: {
name: 'requestFilePath',
type: 'string',
description: 'The absolute or relative path to save the request body to. If omitted, the body is returned inline.',
required: false,
},
responseFilePath: {
name: 'responseFilePath',
type: 'string',
description: 'The absolute or relative path to save the response body to. If omitted, the body is returned inline.',
required: false,
},
},
},
handle_dialog: {
description: 'If a browser dialog was opened, use this command to handle it',
category: 'Input automation',
args: {
action: {
name: 'action',
type: 'string',
description: 'Whether to dismiss or accept the dialog',
required: true,
enum: ['accept', 'dismiss'],
},
promptText: {
name: 'promptText',
type: 'string',
description: 'Optional prompt text to enter into the dialog.',
required: false,
},
},
},
hover: {
description: 'Hover over the provided element',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
lighthouse_audit: {
description: 'Get Lighthouse score and reports for accessibility, SEO and best practices. This excludes performance. For performance audits, run performance_start_trace',
category: 'Debugging',
args: {
mode: {
name: 'mode',
type: 'string',
description: '"navigation" reloads & audits. "snapshot" analyzes current state.',
required: false,
default: 'navigation',
enum: ['navigation', 'snapshot'],
},
device: {
name: 'device',
type: 'string',
description: 'Device to emulate.',
required: false,
default: 'desktop',
enum: ['desktop', 'mobile'],
},
outputDirPath: {
name: 'outputDirPath',
type: 'string',
description: 'Directory for reports. If omitted, uses temporary files.',
required: false,
},
},
},
list_console_messages: {
description: 'List all console messages for the currently selected page since the last navigation.',
category: 'Debugging',
args: {
pageSize: {
name: 'pageSize',
type: 'integer',
description: 'Maximum number of messages to return. When omitted, returns all requests.',
required: false,
},
pageIdx: {
name: 'pageIdx',
type: 'integer',
description: 'Page number to return (0-based). When omitted, returns the first page.',
required: false,
},
types: {
name: 'types',
type: 'array',
description: 'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.',
required: false,
},
includePreservedMessages: {
name: 'includePreservedMessages',
type: 'boolean',
description: 'Set to true to return the preserved messages over the last 3 navigations.',
required: false,
default: false,
},
},
},
list_network_requests: {
description: 'List all requests for the currently selected page since the last navigation.',
category: 'Network',
args: {
pageSize: {
name: 'pageSize',
type: 'integer',
description: 'Maximum number of requests to return. When omitted, returns all requests.',
required: false,
},
pageIdx: {
name: 'pageIdx',
type: 'integer',
description: 'Page number to return (0-based). When omitted, returns the first page.',
required: false,
},
resourceTypes: {
name: 'resourceTypes',
type: 'array',
description: 'Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.',
required: false,
},
includePreservedRequests: {
name: 'includePreservedRequests',
type: 'boolean',
description: 'Set to true to return the preserved requests over the last 3 navigations.',
required: false,
default: false,
},
},
},
list_pages: {
description: 'Get a list of pages open in the browser.',
category: 'Navigation automation',
args: {},
},
navigate_page: {
description: 'Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.',
category: 'Navigation automation',
args: {
type: {
name: 'type',
type: 'string',
description: 'Navigate the page by URL, back or forward in history, or reload.',
required: false,
enum: ['url', 'back', 'forward', 'reload'],
},
url: {
name: 'url',
type: 'string',
description: 'Target URL (only type=url)',
required: false,
},
ignoreCache: {
name: 'ignoreCache',
type: 'boolean',
description: 'Whether to ignore cache on reload.',
required: false,
},
handleBeforeUnload: {
name: 'handleBeforeUnload',
type: 'string',
description: 'Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.',
required: false,
enum: ['accept', 'decline'],
},
initScript: {
name: 'initScript',
type: 'string',
description: 'A JavaScript script to be executed on each new document before any other scripts for the next navigation.',
required: false,
},
timeout: {
name: 'timeout',
type: 'integer',
description: 'Maximum wait time in milliseconds. If set to 0, the default timeout will be used.',
required: false,
},
},
},
new_page: {
description: 'Open a new tab and load a URL. Use project URL if not specified otherwise.',
category: 'Navigation automation',
args: {
url: {
name: 'url',
type: 'string',
description: 'URL to load in a new page.',
required: true,
},
background: {
name: 'background',
type: 'boolean',
description: 'Whether to open the page in the background without bringing it to the front. Default is false (foreground).',
required: false,
},
isolatedContext: {
name: 'isolatedContext',
type: 'string',
description: 'If specified, the page is created in an isolated browser context with the given name. Pages in the same browser context share cookies and storage. Pages in different browser contexts are fully isolated.',
required: false,
},
timeout: {
name: 'timeout',
type: 'integer',
description: 'Maximum wait time in milliseconds. If set to 0, the default timeout will be used.',
required: false,
},
},
},
performance_analyze_insight: {
description: 'Provides more detailed information on a specific Performance Insight of an insight set that was highlighted in the results of a trace recording.',
category: 'Performance',
args: {
insightSetId: {
name: 'insightSetId',
type: 'string',
description: 'The id for the specific insight set. Only use the ids given in the "Available insight sets" list.',
required: true,
},
insightName: {
name: 'insightName',
type: 'string',
description: 'The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown"',
required: true,
},
},
},
performance_start_trace: {
description: 'Start a performance trace on the selected webpage. Use to find frontend performance issues, Core Web Vitals (LCP, INP, CLS), and improve page load speed.',
category: 'Performance',
args: {
reload: {
name: 'reload',
type: 'boolean',
description: 'Determines if, once tracing has started, the current selected page should be automatically reloaded. Navigate the page to the right URL using the navigate_page tool BEFORE starting the trace if reload or autoStop is set to true.',
required: false,
default: true,
},
autoStop: {
name: 'autoStop',
type: 'boolean',
description: 'Determines if the trace recording should be automatically stopped.',
required: false,
default: true,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).',
required: false,
},
},
},
performance_stop_trace: {
description: 'Stop the active performance trace recording on the selected webpage.',
category: 'Performance',
args: {
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).',
required: false,
},
},
},
press_key: {
description: 'Press a key or key combination. Use this when other input methods like fill() cannot be used (e.g., keyboard shortcuts, navigation keys, or special key combinations).',
category: 'Input automation',
args: {
key: {
name: 'key',
type: 'string',
description: 'A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
resize_page: {
description: "Resizes the selected page's window so that the page has specified dimension",
category: 'Emulation',
args: {
width: {
name: 'width',
type: 'number',
description: 'Page width',
required: true,
},
height: {
name: 'height',
type: 'number',
description: 'Page height',
required: true,
},
},
},
select_page: {
description: 'Select a page as a context for future tool calls.',
category: 'Navigation automation',
args: {
pageId: {
name: 'pageId',
type: 'number',
description: 'The ID of the page to select. Call list_pages to get available pages.',
required: true,
},
bringToFront: {
name: 'bringToFront',
type: 'boolean',
description: 'Whether to focus the page and bring it to the top.',
required: false,
},
},
},
take_memory_snapshot: {
description: 'Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.',
category: 'Performance',
args: {
filePath: {
name: 'filePath',
type: 'string',
description: 'A path to a .heapsnapshot file to save the heapsnapshot to.',
required: true,
},
},
},
take_screenshot: {
description: 'Take a screenshot of the page or element.',
category: 'Debugging',
args: {
format: {
name: 'format',
type: 'string',
description: 'Type of format to save the screenshot as. Default is "png"',
required: false,
default: 'png',
enum: ['png', 'jpeg', 'webp'],
},
quality: {
name: 'quality',
type: 'number',
description: 'Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format.',
required: false,
},
uid: {
name: 'uid',
type: 'string',
description: 'The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.',
required: false,
},
fullPage: {
name: 'fullPage',
type: 'boolean',
description: 'If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid.',
required: false,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response.',
required: false,
},
},
},
take_snapshot: {
description: 'Take a text snapshot of the currently selected page based on the a11y tree. The snapshot lists page elements along with a unique\nidentifier (uid). Always use the latest snapshot. Prefer taking a snapshot over taking a screenshot. The snapshot indicates the element selected\nin the DevTools Elements panel (if any).',
category: 'Debugging',
args: {
verbose: {
name: 'verbose',
type: 'boolean',
description: 'Whether to include all possible information available in the full a11y tree. Default is false.',
required: false,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The absolute path, or a path relative to the current working directory, to save the snapshot to instead of attaching it to the response.',
required: false,
},
},
},
type_text: {
description: 'Type text using keyboard into a previously focused input',
category: 'Input automation',
args: {
text: {
name: 'text',
type: 'string',
description: 'The text to type',
required: true,
},
submitKey: {
name: 'submitKey',
type: 'string',
description: 'Optional key to press after typing. E.g., "Enter", "Tab", "Escape"',
required: false,
},
},
},
upload_file: {
description: 'Upload a file through a provided element.',
category: 'Input automation',
args: {
uid: {
name: 'uid',
type: 'string',
description: 'The uid of the file input element or an element that will open file chooser on the page from the page content snapshot',
required: true,
},
filePath: {
name: 'filePath',
type: 'string',
description: 'The local path of the file to upload',
required: true,
},
includeSnapshot: {
name: 'includeSnapshot',
type: 'boolean',
description: 'Whether to include a snapshot in the response. Default is false.',
required: false,
},
},
},
};
-203
View File
@@ -1,203 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { logger } from './logger.js';
import { puppeteer } from './third_party/index.js';
let browser;
function makeTargetFilter(enableExtensions = false) {
const ignoredPrefixes = new Set(['chrome://', 'chrome-untrusted://']);
if (!enableExtensions) {
ignoredPrefixes.add('chrome-extension://');
}
return function targetFilter(target) {
if (target.url() === 'chrome://newtab/') {
return true;
}
// Could be the only page opened in the browser.
if (target.url().startsWith('chrome://inspect')) {
return true;
}
for (const prefix of ignoredPrefixes) {
if (target.url().startsWith(prefix)) {
return false;
}
}
return true;
};
}
export async function ensureBrowserConnected(options) {
const { channel, enableExtensions } = options;
if (browser?.connected) {
return browser;
}
const connectOptions = {
targetFilter: makeTargetFilter(enableExtensions),
defaultViewport: null,
handleDevToolsAsPage: true,
};
let autoConnect = false;
if (options.wsEndpoint) {
connectOptions.browserWSEndpoint = options.wsEndpoint;
if (options.wsHeaders) {
connectOptions.headers = options.wsHeaders;
}
}
else if (options.browserURL) {
connectOptions.browserURL = options.browserURL;
}
else if (channel || options.userDataDir) {
const userDataDir = options.userDataDir;
if (userDataDir) {
autoConnect = true;
// TODO: re-expose this logic via Puppeteer.
const portPath = path.join(userDataDir, 'DevToolsActivePort');
try {
const fileContent = await fs.promises.readFile(portPath, 'utf8');
const [rawPort, rawPath] = fileContent
.split('\n')
.map(line => {
return line.trim();
})
.filter(line => {
return !!line;
});
if (!rawPort || !rawPath) {
throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
}
const port = parseInt(rawPort, 10);
if (isNaN(port) || port <= 0 || port > 65535) {
throw new Error(`Invalid port '${rawPort}' found`);
}
const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
connectOptions.browserWSEndpoint = browserWSEndpoint;
}
catch (error) {
throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled by going to chrome://inspect/#remote-debugging.`, {
cause: error,
});
}
}
else {
if (!channel) {
throw new Error('Channel must be provided if userDataDir is missing');
}
connectOptions.channel = (channel === 'stable' ? 'chrome' : `chrome-${channel}`);
}
}
else {
throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
}
logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
try {
browser = await puppeteer.connect(connectOptions);
}
catch (err) {
throw new Error(`Could not connect to Chrome. ${autoConnect ? `Check if Chrome is running and remote debugging is enabled by going to chrome://inspect/#remote-debugging.` : `Check if Chrome is running.`}`, {
cause: err,
});
}
logger('Connected Puppeteer');
return browser;
}
export function detectDisplay() {
// Only detect display on Linux/UNIX.
if (os.platform() === 'win32' || os.platform() === 'darwin') {
return;
}
if (!process.env['DISPLAY']) {
try {
const result = execSync(`ps -u $(id -u) -o pid= | xargs -I{} cat /proc/{}/environ 2>/dev/null | tr '\\0' '\\n' | grep -m1 '^DISPLAY=' | cut -d= -f2`);
const display = result.toString('utf8').trim();
process.env['DISPLAY'] = display;
}
catch {
// no-op
}
}
}
export async function launch(options) {
const { channel, executablePath, headless, isolated } = options;
const profileDirName = channel && channel !== 'stable'
? `chrome-profile-${channel}`
: 'chrome-profile';
let userDataDir = options.userDataDir;
if (!isolated && !userDataDir) {
userDataDir = path.join(os.homedir(), '.cache', options.viaCli ? 'chrome-devtools-mcp-cli' : 'chrome-devtools-mcp', profileDirName);
await fs.promises.mkdir(userDataDir, {
recursive: true,
});
}
const args = [
...(options.chromeArgs ?? []),
'--hide-crash-restore-bubble',
];
const ignoreDefaultArgs = options.ignoreDefaultChromeArgs ?? false;
if (headless) {
args.push('--screen-info={3840x2160}');
}
let puppeteerChannel;
if (options.devtools) {
args.push('--auto-open-devtools-for-tabs');
}
if (!executablePath) {
puppeteerChannel =
channel && channel !== 'stable'
? `chrome-${channel}`
: 'chrome';
}
if (!headless) {
detectDisplay();
}
try {
const browser = await puppeteer.launch({
channel: puppeteerChannel,
targetFilter: makeTargetFilter(options.enableExtensions),
executablePath,
defaultViewport: null,
userDataDir,
pipe: true,
headless,
args,
ignoreDefaultArgs: ignoreDefaultArgs,
acceptInsecureCerts: options.acceptInsecureCerts,
handleDevToolsAsPage: true,
enableExtensions: options.enableExtensions,
});
if (options.logFile) {
// FIXME: we are probably subscribing too late to catch startup logs. We
// should expose the process earlier or expose the getRecentLogs() getter.
browser.process()?.stderr?.pipe(options.logFile);
browser.process()?.stdout?.pipe(options.logFile);
}
if (options.viewport) {
const [page] = await browser.pages();
await page?.resize({
contentWidth: options.viewport.width,
contentHeight: options.viewport.height,
});
}
return browser;
}
catch (error) {
if (userDataDir &&
error.message.includes('The browser is already running')) {
throw new Error(`The browser is already running for ${userDataDir}. Use --isolated to run multiple browser instances.`, {
cause: error,
});
}
throw error;
}
}
export async function ensureBrowserLaunched(options) {
if (browser?.connected) {
return browser;
}
browser = await launch(options);
return browser;
}
-152
View File
@@ -1,152 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import net from 'node:net';
import { logger } from '../logger.js';
import { PipeTransport } from '../third_party/index.js';
import { saveTemporaryFile } from '../utils/files.js';
import { DAEMON_SCRIPT_PATH, getSocketPath, getPidFilePath, isDaemonRunning, } from './utils.js';
const FILE_TIMEOUT = 10_000;
/**
* Waits for a file to be created and populated (removed = false) or removed (removed = true).
*/
function waitForFile(filePath, removed = false) {
return new Promise((resolve, reject) => {
const check = () => {
const exists = fs.existsSync(filePath);
if (removed) {
return !exists;
}
if (!exists) {
return false;
}
try {
return fs.statSync(filePath).size > 0;
}
catch {
return false;
}
};
if (check()) {
resolve();
return;
}
const timer = setTimeout(() => {
fs.unwatchFile(filePath);
reject(new Error(`Timeout: file ${filePath} ${removed ? 'not removed' : 'not found'} within ${FILE_TIMEOUT}ms`));
}, FILE_TIMEOUT);
fs.watchFile(filePath, { interval: 500 }, () => {
if (check()) {
clearTimeout(timer);
fs.unwatchFile(filePath);
resolve();
}
});
});
}
export async function startDaemon(mcpArgs = []) {
if (isDaemonRunning()) {
logger('Daemon is already running');
return;
}
const pidFilePath = getPidFilePath();
if (fs.existsSync(pidFilePath)) {
fs.unlinkSync(pidFilePath);
}
logger('Starting daemon...', ...mcpArgs);
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], {
detached: true,
stdio: 'ignore',
env: process.env,
cwd: process.cwd(),
windowsHide: true,
});
child.unref();
await waitForFile(pidFilePath);
}
const SEND_COMMAND_TIMEOUT = 60_000; // ms
/**
* `sendCommand` opens a socket connection sends a single command and disconnects.
*/
export async function sendCommand(command) {
const socketPath = getSocketPath();
const socket = net.createConnection({
path: socketPath,
});
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
socket.destroy();
reject(new Error('Timeout waiting for daemon response'));
}, SEND_COMMAND_TIMEOUT);
const transport = new PipeTransport(socket, socket);
transport.onmessage = async (message) => {
clearTimeout(timer);
logger('onmessage', message);
resolve(JSON.parse(message));
};
socket.on('error', error => {
clearTimeout(timer);
logger('Socket error:', error);
reject(error);
});
socket.on('close', () => {
clearTimeout(timer);
logger('Socket closed:');
reject(new Error('Socket closed'));
});
logger('Sending message', command);
transport.send(JSON.stringify(command));
});
}
export async function stopDaemon() {
if (!isDaemonRunning()) {
logger('Daemon is not running');
return;
}
const pidFilePath = getPidFilePath();
await sendCommand({ method: 'stop' });
await waitForFile(pidFilePath, /*removed=*/ true);
}
export async function handleResponse(response, format) {
if (response.isError) {
return JSON.stringify(response.content);
}
if (format === 'json') {
if (response.structuredContent) {
return JSON.stringify(response.structuredContent);
}
// Fall-through to text for backward compatibility.
}
const chunks = [];
for (const content of response.content) {
if (content.type === 'text') {
chunks.push(content.text);
}
else if (content.type === 'image') {
const imageData = content.data;
const mimeType = content.mimeType;
let extension = '.png';
switch (mimeType) {
case 'image/jpg':
case 'image/jpeg':
extension = '.jpeg';
break;
case 'webp':
extension = '.webp';
break;
}
const data = Buffer.from(imageData, 'base64');
const name = crypto.randomUUID();
const { filepath } = await saveTemporaryFile(data, `${name}${extension}`);
chunks.push(`Saved to ${filepath}.`);
}
else {
throw new Error('Not supported response content type');
}
}
return format === 'md' ? chunks.join(' ') : JSON.stringify(chunks);
}
-206
View File
@@ -1,206 +0,0 @@
#!/usr/bin/env node
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'node:fs';
import { createServer } from 'node:net';
import path from 'node:path';
import process from 'node:process';
import { logger } from '../logger.js';
import { Client, PipeTransport, StdioClientTransport, } from '../third_party/index.js';
import { VERSION } from '../version.js';
import { DAEMON_CLIENT_NAME, getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
const pid = getDaemonPid();
if (isDaemonRunning(pid)) {
logger('Another daemon process is running.');
process.exit(1);
}
const pidFilePath = getPidFilePath();
fs.mkdirSync(path.dirname(pidFilePath), {
recursive: true,
});
fs.writeFileSync(pidFilePath, process.pid.toString());
logger(`Writing ${process.pid.toString()} to ${pidFilePath}`);
const socketPath = getSocketPath();
const startDate = new Date();
const mcpServerArgs = process.argv.slice(2);
let mcpClient = null;
let mcpTransport = null;
let server = null;
async function setupMCPClient() {
console.log('Setting up MCP client connection...');
// Create stdio transport for chrome-devtools-mcp
// Workaround for https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/client/stdio.ts#L128
// which causes the console window to show on Windows.
// @ts-expect-error no types for type.
process.type = 'mcp-client';
mcpTransport = new StdioClientTransport({
command: process.execPath,
args: [INDEX_SCRIPT_PATH, ...mcpServerArgs],
env: process.env,
});
mcpClient = new Client({
name: DAEMON_CLIENT_NAME,
version: VERSION,
}, {
capabilities: {},
});
await mcpClient.connect(mcpTransport);
console.log('MCP client connected');
}
async function handleRequest(msg) {
try {
if (msg.method === 'invoke_tool') {
if (!mcpClient) {
throw new Error('MCP client not initialized');
}
const { tool, args } = msg;
const result = (await mcpClient.callTool({
name: tool,
arguments: args || {},
}));
return {
success: true,
result: JSON.stringify(result),
};
}
else if (msg.method === 'stop') {
// Ensure we are not interrupting in-progress starting.
await started;
// Trigger cleanup asynchronously.
setImmediate(() => {
void cleanup();
});
return {
success: true,
message: 'stopping',
};
}
else if (msg.method === 'status') {
return {
success: true,
result: JSON.stringify({
pid: process.pid,
socketPath,
startDate: startDate.toISOString(),
version: VERSION,
args: mcpServerArgs,
}),
};
}
{
return {
success: false,
error: `Unknown method: ${JSON.stringify(msg, null, 2)}`,
};
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
};
}
}
async function startSocketServer() {
// Remove existing socket file if it exists (only on non-Windows)
if (!IS_WINDOWS) {
try {
fs.unlinkSync(socketPath);
}
catch {
// ignore errors.
}
}
return await new Promise((resolve, reject) => {
server = createServer(socket => {
const transport = new PipeTransport(socket, socket);
transport.onmessage = async (message) => {
logger('onmessage', message);
const response = await handleRequest(JSON.parse(message));
transport.send(JSON.stringify(response));
socket.end();
};
socket.on('error', error => {
logger('Socket error:', error);
});
});
server.listen({
path: socketPath,
readableAll: false,
writableAll: false,
}, async () => {
console.log(`Daemon server listening on ${socketPath}`);
try {
// Setup MCP client
await setupMCPClient();
resolve();
}
catch (err) {
reject(err);
}
});
server.on('error', error => {
logger('Server error:', error);
reject(error);
});
});
}
async function cleanup() {
console.log('Cleaning up daemon...');
try {
await mcpClient?.close();
}
catch (error) {
logger('Error closing MCP client:', error);
}
try {
await mcpTransport?.close();
}
catch (error) {
logger('Error closing MCP transport:', error);
}
if (server) {
await new Promise(resolve => {
server.close(() => resolve());
});
}
if (!IS_WINDOWS) {
try {
fs.unlinkSync(socketPath);
}
catch {
// ignore errors
}
}
logger(`unlinking ${pidFilePath}`);
if (fs.existsSync(pidFilePath)) {
fs.unlinkSync(pidFilePath);
}
process.exit(0);
}
// Handle shutdown signals
process.on('SIGTERM', () => {
void cleanup();
});
process.on('SIGINT', () => {
void cleanup();
});
process.on('SIGHUP', () => {
void cleanup();
});
// Handle uncaught errors
process.on('uncaughtException', error => {
logger('Uncaught exception:', error);
});
process.on('unhandledRejection', error => {
logger('Unhandled rejection:', error);
});
// Start the server
const started = startSocketServer().catch(error => {
logger('Failed to start daemon server:', error);
process.exit(1);
});
-6
View File
@@ -1,6 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export {};
-109
View File
@@ -1,109 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';
import { logger } from '../logger.js';
export const DAEMON_SCRIPT_PATH = path.join(import.meta.dirname, 'daemon.js');
export const INDEX_SCRIPT_PATH = path.join(import.meta.dirname, '..', 'bin', 'chrome-devtools-mcp.js');
const APP_NAME = 'chrome-devtools-mcp';
export const DAEMON_CLIENT_NAME = 'chrome-devtools-cli-daemon';
// Using these paths due to strict limits on the POSIX socket path length.
export function getSocketPath() {
const uid = os.userInfo().uid;
if (IS_WINDOWS) {
// Windows uses Named Pipes, not file paths.
// This format is required for server.listen()
return path.join('\\\\.\\pipe', APP_NAME, 'server.sock');
}
// 1. Try XDG_RUNTIME_DIR (Linux standard, sometimes macOS)
if (process.env.XDG_RUNTIME_DIR) {
return path.join(process.env.XDG_RUNTIME_DIR, APP_NAME, 'server.sock');
}
// 2. macOS/Unix Fallback: Use /tmp/
// We use /tmp/ because it is much shorter than ~/Library/Application Support/
// and keeps us well under the 104-character limit.
return path.join('/tmp', `${APP_NAME}-${uid}.sock`);
}
export function getRuntimeHome() {
const platform = os.platform();
const uid = os.userInfo().uid;
// 1. Check for the modern Unix standard
if (process.env.XDG_RUNTIME_DIR) {
return path.join(process.env.XDG_RUNTIME_DIR, APP_NAME);
}
// 2. Fallback for macOS and older Linux
if (platform === 'darwin' || platform === 'linux') {
// /tmp is cleared on boot, making it perfect for PIDs
return path.join('/tmp', `${APP_NAME}-${uid}`);
}
// 3. Windows Fallback
return path.join(os.tmpdir(), APP_NAME);
}
export const IS_WINDOWS = os.platform() === 'win32';
export function getPidFilePath() {
const runtimeDir = getRuntimeHome();
return path.join(runtimeDir, 'daemon.pid');
}
export function getDaemonPid() {
try {
const pidFile = getPidFilePath();
logger(`Daemon pid file ${pidFile}`);
if (!fs.existsSync(pidFile)) {
return null;
}
const pidContent = fs.readFileSync(pidFile, 'utf-8');
const pid = parseInt(pidContent.trim(), 10);
logger(`Daemon pid: ${pid}`);
if (isNaN(pid)) {
return null;
}
return pid;
}
catch {
return null;
}
}
export function isDaemonRunning(pid = getDaemonPid()) {
if (pid) {
try {
process.kill(pid, 0); // Throws if process doesn't exist
return true;
}
catch {
// Process is dead, stale PID file. Proceed with startup.
}
}
return false;
}
export function serializeArgs(options, argv) {
const args = [];
for (const key of Object.keys(options)) {
if (argv[key] === undefined || argv[key] === null) {
continue;
}
const value = argv[key];
const kebabKey = key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
if (typeof value === 'boolean') {
if (value) {
args.push(`--${kebabKey}`);
}
else {
args.push(`--no-${kebabKey}`);
}
}
else if (Array.isArray(value)) {
for (const item of value) {
args.push(`--${kebabKey}`, String(item));
}
}
else {
args.push(`--${kebabKey}`, String(value));
}
}
return args;
}
@@ -1,241 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { createStackTraceForConsoleMessage, SymbolizedError, } from '../DevtoolsUtils.js';
import { UncaughtError } from '../PageCollector.js';
import * as DevTools from '../third_party/index.js';
export class ConsoleFormatter {
#id;
#type;
#text;
#argCount;
#resolvedArgs;
#stack;
#cause;
isIgnored;
constructor(params) {
this.#id = params.id;
this.#type = params.type;
this.#text = params.text;
this.#argCount = params.argCount ?? 0;
this.#resolvedArgs = params.resolvedArgs ?? [];
this.#stack = params.stack;
this.#cause = params.cause;
this.isIgnored = params.isIgnored;
}
static async from(msg, options) {
const ignoreListManager = options?.devTools?.universe.context.get(DevTools.DevTools.IgnoreListManager);
const isIgnored = options.isIgnoredForTesting ||
(frame => {
if (!ignoreListManager) {
return false;
}
if (frame.uiSourceCode) {
return ignoreListManager.isUserOrSourceMapIgnoreListedUISourceCode(frame.uiSourceCode);
}
if (frame.url) {
return ignoreListManager.isUserIgnoreListedURL(frame.url);
}
return false;
});
if (msg instanceof UncaughtError) {
const error = await SymbolizedError.fromDetails({
devTools: options?.devTools,
details: msg.details,
targetId: msg.targetId,
includeStackAndCause: options?.fetchDetailedData,
resolvedStackTraceForTesting: options?.resolvedStackTraceForTesting,
resolvedCauseForTesting: options?.resolvedCauseForTesting,
});
return new ConsoleFormatter({
id: options.id,
type: 'error',
text: error.message,
stack: error.stackTrace,
cause: error.cause,
isIgnored,
});
}
let resolvedArgs = [];
if (options.resolvedArgsForTesting) {
resolvedArgs = options.resolvedArgsForTesting;
}
else if (options.fetchDetailedData) {
resolvedArgs = await Promise.all(msg.args().map(async (arg, i) => {
try {
const remoteObject = arg.remoteObject();
if (remoteObject.type === 'object' &&
remoteObject.subtype === 'error') {
return await SymbolizedError.fromError({
devTools: options.devTools,
error: remoteObject,
// @ts-expect-error Internal ConsoleMessage API
targetId: msg._targetId(),
});
}
return await arg.jsonValue();
}
catch {
return `<error: Argument ${i} is no longer available>`;
}
}));
}
let stack;
if (options.resolvedStackTraceForTesting) {
stack = options.resolvedStackTraceForTesting;
}
else if (options.fetchDetailedData && options.devTools) {
try {
stack = await createStackTraceForConsoleMessage(options.devTools, msg);
}
catch {
// ignore
}
}
return new ConsoleFormatter({
id: options.id,
type: msg.type(),
text: msg.text(),
argCount: resolvedArgs.length || msg.args().length,
resolvedArgs,
stack,
isIgnored,
});
}
// The short format for a console message.
toString() {
return convertConsoleMessageConciseToString(this.toJSON());
}
// The verbose format for a console message, including all details.
toStringDetailed() {
return convertConsoleMessageConciseDetailedToString(this.toJSONDetailed());
}
#getArgs() {
if (this.#resolvedArgs.length > 0) {
const args = [...this.#resolvedArgs];
// If there is no text, the first argument serves as text (see formatMessage).
if (!this.#text) {
args.shift();
}
return args;
}
return [];
}
toJSON() {
return {
type: this.#type,
text: this.#text,
argsCount: this.#argCount,
id: this.#id,
};
}
toJSONDetailed() {
return {
id: this.#id,
type: this.#type,
text: this.#text,
argsCount: this.#argCount,
args: this.#getArgs().map(arg => formatArg(arg, this)),
stackTrace: this.#stack
? formatStackTrace(this.#stack, this.#cause, this)
: undefined,
};
}
}
function convertConsoleMessageConciseToString(msg) {
return `msgid=${msg.id} [${msg.type}] ${msg.text} (${msg.argsCount} args)`;
}
function convertConsoleMessageConciseDetailedToString(msg) {
const result = [
`ID: ${msg.id}`,
`Message: ${msg.type}> ${msg.text}`,
formatArgs(msg),
...(msg.stackTrace ? ['### Stack trace', msg.stackTrace] : []),
].filter(line => !!line);
return result.join('\n');
}
function formatArgs(msg) {
const args = msg.args;
if (!args.length) {
return '';
}
const result = ['### Arguments'];
for (const [key, arg] of args.entries()) {
result.push(`Arg #${key}: ${arg}`);
}
return result.join('\n');
}
function formatArg(arg, formatter) {
if (arg instanceof SymbolizedError) {
return [
arg.message,
arg.stackTrace
? formatStackTrace(arg.stackTrace, arg.cause, formatter)
: undefined,
]
.filter(line => !!line)
.join('\n');
}
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
}
const STACK_TRACE_MAX_LINES = 50;
function formatStackTrace(stackTrace, cause, formatter) {
const lines = formatStackTraceInner(stackTrace, cause, formatter);
const includedLines = lines.slice(0, STACK_TRACE_MAX_LINES);
const reminderCount = lines.length - includedLines.length;
return [
...includedLines,
reminderCount > 0 ? `... and ${reminderCount} more frames` : '',
'Note: line and column numbers use 1-based indexing',
]
.filter(line => !!line)
.join('\n');
}
function formatStackTraceInner(stackTrace, cause, formatter) {
if (!stackTrace) {
return [];
}
return [
...formatFragment(stackTrace.syncFragment, formatter),
...stackTrace.asyncFragments
.map(item => formatAsyncFragment(item, formatter))
.flat(),
...formatCause(cause, formatter),
];
}
function formatFragment(fragment, formatter) {
const frames = fragment.frames.filter(frame => !formatter.isIgnored(frame));
return frames.map(formatFrame);
}
function formatAsyncFragment(fragment, formatter) {
const formattedFrames = formatFragment(fragment, formatter);
if (formattedFrames.length === 0) {
return [];
}
const separatorLineLength = 40;
const prefix = `--- ${fragment.description || 'async'} `;
const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
return [separator, ...formattedFrames];
}
function formatFrame(frame) {
let result = `at ${frame.name ?? '<anonymous>'}`;
if (frame.uiSourceCode) {
const location = frame.uiSourceCode.uiLocation(frame.line, frame.column);
result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`;
}
else if (frame.url) {
result += ` (${frame.url}:${frame.line}:${frame.column})`;
}
return result;
}
function formatCause(cause, formatter) {
if (!cause) {
return [];
}
return [
`Caused by: ${cause.message}`,
...formatStackTraceInner(cause.stackTrace, cause.cause, formatter),
];
}
-192
View File
@@ -1,192 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { ISSUE_UTILS } from '../issue-descriptions.js';
import { logger } from '../logger.js';
import { DevTools } from '../third_party/index.js';
export class IssueFormatter {
#issue;
#options;
constructor(issue, options) {
this.#issue = issue;
this.#options = options;
}
toString() {
return convertIssueConciseToString(this.toJSON());
}
toStringDetailed() {
return convertIssueDetailedToString(this.toJSONDetailed());
}
toJSON() {
return {
type: 'issue',
title: this.#getTitle(),
count: this.#issue.getAggregatedIssuesCount(),
id: this.#options.id,
};
}
toJSONDetailed() {
return {
id: this.#options.id,
type: 'issue',
count: this.#issue.getAggregatedIssuesCount(),
title: this.#getTitle(),
description: this.#getDescription(),
links: this.#issue.getDescription()?.links,
affectedResources: this.#getAffectedResources(),
};
}
#getAffectedResources() {
const issues = this.#issue.getAllIssues();
const affectedResources = [];
for (const singleIssue of issues) {
const details = singleIssue.details();
if (!details) {
continue;
}
// We send the remaining details as untyped JSON because the DevTools
// frontend code is currently not re-usable.
const data = structuredClone(details);
let uid;
let request;
if ('violatingNodeId' in details &&
details.violatingNodeId &&
this.#options.elementIdResolver) {
uid = this.#options.elementIdResolver(details.violatingNodeId);
delete data.violatingNodeId;
}
if ('nodeId' in details &&
details.nodeId &&
this.#options.elementIdResolver) {
uid = this.#options.elementIdResolver(details.nodeId);
delete data.nodeId;
}
if ('documentNodeId' in details &&
details.documentNodeId &&
this.#options.elementIdResolver) {
uid = this.#options.elementIdResolver(details.documentNodeId);
delete data.documentNodeId;
}
if ('request' in details && details.request) {
request = details.request.url;
if (details.request.requestId && this.#options.requestIdResolver) {
const resolvedId = this.#options.requestIdResolver(details.request.requestId);
if (resolvedId) {
request = resolvedId;
const requestData = data.request;
delete requestData.requestId;
}
}
}
// These fields has no use for the MCP client (redundant or irrelevant).
delete data.errorType;
delete data.frameId;
affectedResources.push({
uid,
data: data,
request,
});
}
return affectedResources;
}
isValid() {
return this.#getTitle() !== undefined;
}
// Helper to extract title
#getTitle() {
const markdownDescription = this.#issue.getDescription();
const filename = markdownDescription?.file;
if (!filename) {
logger(`no description found for issue:` + this.#issue.code());
return undefined;
}
// We already have the description logic in #getDescription, but title extraction is separate
// We can reuse the logic or cache it.
// Ideally we should process markdown once.
const rawMarkdown = ISSUE_UTILS.getIssueDescription(filename);
if (!rawMarkdown) {
logger(`no markdown ${filename} found for issue:` + this.#issue.code());
return undefined;
}
try {
const processedMarkdown = DevTools.MarkdownIssueDescription.substitutePlaceholders(rawMarkdown, markdownDescription?.substitutions);
const markdownAst = DevTools.Marked.Marked.lexer(processedMarkdown);
const title = DevTools.MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst);
if (!title) {
logger('cannot read issue title from ' + filename);
return undefined;
}
return title;
}
catch {
logger('error parsing markdown for issue ' + this.#issue.code());
return undefined;
}
}
#getDescription() {
const markdownDescription = this.#issue.getDescription();
const filename = markdownDescription?.file;
if (!filename) {
return undefined;
}
const rawMarkdown = ISSUE_UTILS.getIssueDescription(filename);
if (!rawMarkdown) {
return undefined;
}
try {
return DevTools.MarkdownIssueDescription.substitutePlaceholders(rawMarkdown, markdownDescription?.substitutions);
}
catch {
return undefined;
}
}
}
function convertIssueConciseToString(issue) {
return `msgid=${issue.id} [issue] ${issue.title} (count: ${issue.count})`;
}
function convertIssueDetailedToString(issue) {
const result = [];
result.push(`ID: ${issue.id}`);
const bodyParts = [];
const description = issue.description;
let processedMarkdown = description?.trim();
// Remove heading in order not to conflict with the whole console message response markdown
if (processedMarkdown?.startsWith('# ')) {
processedMarkdown = processedMarkdown.substring(2).trimStart();
}
if (processedMarkdown) {
bodyParts.push(processedMarkdown);
}
else {
bodyParts.push(issue.title ?? 'Unknown Issue');
}
const links = issue.links;
if (links && links.length > 0) {
bodyParts.push('Learn more:');
for (const link of links) {
bodyParts.push(`[${link.linkTitle}](${link.link})`);
}
}
const affectedResources = issue.affectedResources;
if (affectedResources.length) {
bodyParts.push('### Affected resources');
bodyParts.push(...affectedResources.map(item => {
const details = [];
if (item.uid) {
details.push(`uid=${item.uid}`);
}
if (item.request) {
details.push((typeof item.request === 'number' ? `reqid=` : 'url=') +
item.request);
}
if (item.data) {
details.push(`data=${JSON.stringify(item.data)}`);
}
return details.join(' ');
}));
}
result.push(`Message: issue> ${bodyParts.join('\n')}`);
return result.join('\n');
}
@@ -1,218 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
* */
import { isUtf8 } from 'node:buffer';
const BODY_CONTEXT_SIZE_LIMIT = 10000;
export class NetworkFormatter {
#request;
#options;
#requestBody;
#responseBody;
#requestBodyFilePath;
#responseBodyFilePath;
constructor(request, options) {
this.#request = request;
this.#options = options;
}
static async from(request, options) {
const instance = new NetworkFormatter(request, options);
if (options.fetchData) {
await instance.#loadDetailedData();
}
return instance;
}
async #loadDetailedData() {
// Load Request Body
if (this.#request.hasPostData()) {
let data;
try {
data =
this.#request.postData() ?? (await this.#request.fetchPostData());
}
catch {
// Ignore parsing errors
}
const requestBodyNotAvailableMessage = '<Request body not available anymore>';
if (this.#options.requestFilePath) {
if (!this.#options.saveFile) {
throw new Error('saveFile is not provided');
}
if (data) {
await this.#options.saveFile(Buffer.from(data), this.#options.requestFilePath);
this.#requestBodyFilePath = this.#options.requestFilePath;
}
else {
this.#requestBody = requestBodyNotAvailableMessage;
}
}
else {
if (data) {
this.#requestBody = getSizeLimitedString(data, BODY_CONTEXT_SIZE_LIMIT);
}
else {
this.#requestBody = requestBodyNotAvailableMessage;
}
}
}
// Load Response Body
const response = this.#request.response();
if (response) {
const responseBodyNotAvailableMessage = '<Response body not available anymore>';
if (this.#options.responseFilePath) {
try {
const buffer = await response.buffer();
if (!this.#options.saveFile) {
throw new Error('saveFile is not provided');
}
await this.#options.saveFile(buffer, this.#options.responseFilePath);
this.#responseBodyFilePath = this.#options.responseFilePath;
}
catch {
// Flatten error handling for buffer() failure and save failure
}
if (!this.#responseBodyFilePath) {
this.#responseBody = responseBodyNotAvailableMessage;
}
}
else {
this.#responseBody = await this.#getFormattedResponseBody(response, BODY_CONTEXT_SIZE_LIMIT);
}
}
}
toString() {
return convertNetworkRequestConciseToString(this.toJSON());
}
toStringDetailed() {
return converNetworkRequestDetailedToStringDetailed(this.toJSONDetailed());
}
toJSON() {
return {
requestId: this.#options.requestId,
method: this.#request.method(),
url: this.#request.url(),
status: this.#getStatusFromRequest(this.#request),
selectedInDevToolsUI: this.#options.selectedInDevToolsUI,
};
}
toJSONDetailed() {
const redirectChain = this.#request.redirectChain();
const formattedRedirectChain = redirectChain.reverse().map(request => {
const id = this.#options.requestIdResolver
? this.#options.requestIdResolver(request)
: undefined;
const formatter = new NetworkFormatter(request, {
requestId: id,
saveFile: this.#options.saveFile,
});
return formatter.toJSON();
});
return {
...this.toJSON(),
requestHeaders: this.#request.headers(),
requestBody: this.#requestBody,
requestBodyFilePath: this.#requestBodyFilePath,
responseHeaders: this.#request.response()?.headers(),
responseBody: this.#responseBody,
responseBodyFilePath: this.#responseBodyFilePath,
failure: this.#request.failure()?.errorText,
redirectChain: formattedRedirectChain.length
? formattedRedirectChain
: undefined,
};
}
#getStatusFromRequest(request) {
const httpResponse = request.response();
const failure = request.failure();
let status;
if (httpResponse) {
status = httpResponse.status().toString();
}
else if (failure) {
status = failure.errorText;
}
else {
status = 'pending';
}
return status;
}
async #getFormattedResponseBody(httpResponse, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
try {
const responseBuffer = await httpResponse.buffer();
if (isUtf8(responseBuffer)) {
const responseAsTest = responseBuffer.toString('utf-8');
if (responseAsTest.length === 0) {
return '<empty response>';
}
return getSizeLimitedString(responseAsTest, sizeLimit);
}
return '<binary data>';
}
catch {
return '<not available anymore>';
}
}
}
function getSizeLimitedString(text, sizeLimit) {
if (text.length > sizeLimit) {
return text.substring(0, sizeLimit) + '... <truncated>';
}
return text;
}
function convertNetworkRequestConciseToString(data) {
// TODO truncate the URL
return `reqid=${data.requestId} ${data.method} ${data.url} [${data.status}]${data.selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
}
function formatHeadlers(headers) {
const response = [];
for (const [name, value] of Object.entries(headers)) {
response.push(`- ${name}:${value}`);
}
return response;
}
function converNetworkRequestDetailedToStringDetailed(data) {
const response = [];
response.push(`## Request ${data.url}`);
response.push(`Status: ${data.status}`);
response.push(`### Request Headers`);
for (const line of formatHeadlers(data.requestHeaders)) {
response.push(line);
}
if (data.requestBody) {
response.push(`### Request Body`);
response.push(data.requestBody);
}
else if (data.requestBodyFilePath) {
response.push(`### Request Body`);
response.push(`Saved to ${data.requestBodyFilePath}.`);
}
if (data.responseHeaders) {
response.push(`### Response Headers`);
for (const line of formatHeadlers(data.responseHeaders)) {
response.push(line);
}
}
if (data.responseBody) {
response.push(`### Response Body`);
response.push(data.responseBody);
}
else if (data.responseBodyFilePath) {
response.push(`### Response Body`);
response.push(`Saved to ${data.responseBodyFilePath}.`);
}
if (data.failure) {
response.push(`### Request failed with`);
response.push(data.failure);
}
const redirectChain = data.redirectChain;
if (redirectChain?.length) {
response.push(`### Redirect chain`);
let indent = 0;
for (const request of redirectChain.reverse()) {
response.push(`${' '.repeat(indent)}${convertNetworkRequestConciseToString(request)})}`);
indent++;
}
}
return response.join('\n');
}
@@ -1,134 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export class SnapshotFormatter {
#snapshot;
constructor(snapshot) {
this.#snapshot = snapshot;
}
toString() {
const chunks = [];
const root = this.#snapshot.root;
// Top-level content of the snapshot.
if (this.#snapshot.verbose &&
this.#snapshot.hasSelectedElement &&
!this.#snapshot.selectedElementUid) {
chunks.push(`Note: there is a selected element in the DevTools Elements panel but it is not included into the current a11y tree snapshot.
Get a verbose snapshot to include all elements if you are interested in the selected element.\n\n`);
}
chunks.push(this.#formatNode(root, 0));
return chunks.join('');
}
toJSON() {
return this.#nodeToJSON(this.#snapshot.root);
}
#formatNode(node, depth = 0) {
const chunks = [];
const attributes = this.#getAttributes(node);
const line = ' '.repeat(depth * 2) +
attributes.join(' ') +
(node.id === this.#snapshot.selectedElementUid
? ' [selected in the DevTools Elements panel]'
: '') +
'\n';
chunks.push(line);
for (const child of node.children) {
chunks.push(this.#formatNode(child, depth + 1));
}
return chunks.join('');
}
#nodeToJSON(node) {
const rawAttrs = this.#getAttributesMap(node);
const children = node.children.map(child => this.#nodeToJSON(child));
const result = structuredClone(rawAttrs);
if (children.length > 0) {
result.children = children;
}
return result;
}
#getAttributes(serializedAXNodeRoot) {
const attributes = [`uid=${serializedAXNodeRoot.id}`];
if (serializedAXNodeRoot.role) {
attributes.push(serializedAXNodeRoot.role === 'none'
? 'ignored'
: serializedAXNodeRoot.role);
}
if (serializedAXNodeRoot.name) {
attributes.push(`"${serializedAXNodeRoot.name}"`);
}
const simpleAttrs = this.#getAttributesMap(serializedAXNodeRoot,
/* excludeSpecial */ true);
for (const attr of Object.keys(serializedAXNodeRoot).sort()) {
if (excludedAttributes.has(attr)) {
continue;
}
const mapped = booleanPropertyMap[attr];
if (mapped && simpleAttrs[mapped]) {
attributes.push(mapped);
}
const val = simpleAttrs[attr];
if (val === true) {
attributes.push(attr);
}
else if (typeof val === 'string' || typeof val === 'number') {
attributes.push(`${attr}="${val}"`);
}
}
return attributes;
}
#getAttributesMap(node, excludeSpecial = false) {
const result = {};
if (!excludeSpecial) {
result.id = node.id;
if (node.role) {
result.role = node.role;
}
if (node.name) {
result.name = node.name;
}
}
// Re-implementing the exact logic from original function for #getAttributes to be safe:
return {
...result,
...this.#extractedAttributes(node),
};
}
#extractedAttributes(node) {
const result = {};
for (const attr of Object.keys(node).sort()) {
if (excludedAttributes.has(attr)) {
continue;
}
const value = node[attr];
if (typeof value === 'boolean') {
if (booleanPropertyMap[attr]) {
result[booleanPropertyMap[attr]] = true;
}
if (value) {
result[attr] = true;
}
}
else if (typeof value === 'string' || typeof value === 'number') {
result[attr] = value;
}
}
return result;
}
}
const booleanPropertyMap = {
disabled: 'disableable',
expanded: 'expandable',
focused: 'focusable',
selected: 'selectable',
};
const excludedAttributes = new Set([
'id',
'role',
'name',
'elementHandle',
'children',
'backendNodeId',
'loaderId',
]);

Some files were not shown because too many files have changed in this diff Show More