chrome-devtools-mcp 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/build/src/DevToolsConnectionAdapter.js +1 -0
- package/build/src/DevtoolsUtils.js +1 -0
- package/build/src/HeapSnapshotManager.js +16 -0
- package/build/src/McpContext.js +54 -126
- package/build/src/McpPage.js +204 -0
- package/build/src/McpResponse.js +44 -6
- package/build/src/Mutex.js +1 -0
- package/build/src/PageCollector.js +1 -0
- package/build/src/SlimMcpResponse.js +1 -0
- package/build/src/TextSnapshot.js +236 -0
- package/build/src/WaitForHelper.js +6 -0
- package/build/src/bin/check-latest-version.js +1 -0
- package/build/src/bin/chrome-devtools-cli-options.js +206 -46
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +13 -1
- package/build/src/bin/chrome-devtools-mcp-main.js +1 -0
- package/build/src/bin/chrome-devtools-mcp.js +1 -0
- package/build/src/bin/chrome-devtools.js +27 -27
- package/build/src/browser.js +1 -0
- package/build/src/daemon/client.js +14 -12
- package/build/src/daemon/daemon.js +7 -5
- package/build/src/daemon/types.js +1 -0
- package/build/src/daemon/utils.js +20 -14
- package/build/src/formatters/ConsoleFormatter.js +48 -1
- package/build/src/formatters/HeapSnapshotFormatter.js +18 -2
- package/build/src/formatters/IssueFormatter.js +1 -0
- package/build/src/formatters/NetworkFormatter.js +1 -0
- package/build/src/formatters/SnapshotFormatter.js +2 -1
- package/build/src/index.js +114 -51
- package/build/src/issue-descriptions.js +1 -0
- package/build/src/logger.js +1 -0
- package/build/src/polyfill.js +1 -0
- package/build/src/telemetry/ClearcutLogger.js +13 -1
- package/build/src/telemetry/WatchdogClient.js +1 -0
- package/build/src/telemetry/flagUtils.js +1 -0
- package/build/src/telemetry/metricUtils.js +1 -0
- package/build/src/telemetry/persistence.js +1 -0
- package/build/src/telemetry/toolMetricsUtils.js +2 -1
- package/build/src/telemetry/types.js +1 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +1 -0
- package/build/src/telemetry/watchdog/main.js +1 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +32 -5
- package/build/src/third_party/bundled-packages.json +3 -2
- package/build/src/third_party/devtools-formatter-worker.js +2451 -2933
- package/build/src/third_party/devtools-heap-snapshot-worker.js +32 -26
- package/build/src/third_party/index.js +1942 -1536
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +21717 -20261
- package/build/src/tools/ToolDefinition.js +1 -0
- package/build/src/tools/categories.js +6 -2
- package/build/src/tools/console.js +3 -0
- package/build/src/tools/emulation.js +2 -0
- package/build/src/tools/extensions.js +6 -0
- package/build/src/tools/inPage.js +5 -35
- package/build/src/tools/input.js +13 -2
- package/build/src/tools/lighthouse.js +17 -9
- package/build/src/tools/memory.js +34 -1
- package/build/src/tools/network.js +7 -2
- package/build/src/tools/pages.js +218 -146
- package/build/src/tools/performance.js +6 -0
- package/build/src/tools/screencast.js +25 -10
- package/build/src/tools/screenshot.js +3 -0
- package/build/src/tools/script.js +2 -0
- package/build/src/tools/slim/tools.js +4 -0
- package/build/src/tools/snapshot.js +5 -1
- package/build/src/tools/tools.js +1 -0
- package/build/src/tools/webmcp.js +3 -0
- package/build/src/trace-processing/parse.js +1 -0
- package/build/src/types.js +1 -0
- package/build/src/utils/check-for-updates.js +1 -0
- package/build/src/utils/files.js +5 -10
- package/build/src/utils/id.js +1 -0
- package/build/src/utils/keyboard.js +1 -0
- package/build/src/utils/pagination.js +1 -0
- package/build/src/utils/string.js +1 -0
- package/build/src/utils/types.js +1 -0
- package/build/src/version.js +2 -1
- package/package.json +10 -9
- package/build/src/bin/cliDefinitions.js +0 -621
|
@@ -164,6 +164,11 @@ export const cliOptions = {
|
|
|
164
164
|
describe: 'Whether to include all kinds of pages such as webviews or background pages as pages.',
|
|
165
165
|
hidden: true,
|
|
166
166
|
},
|
|
167
|
+
experimentalNavigationAllowlist: {
|
|
168
|
+
type: 'boolean',
|
|
169
|
+
describe: 'Whether to enable navigation allowlist tool parameter.',
|
|
170
|
+
hidden: true,
|
|
171
|
+
},
|
|
167
172
|
experimentalInteropTools: {
|
|
168
173
|
type: 'boolean',
|
|
169
174
|
describe: 'Whether to enable interoperability tools',
|
|
@@ -173,6 +178,11 @@ export const cliOptions = {
|
|
|
173
178
|
type: 'boolean',
|
|
174
179
|
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.',
|
|
175
180
|
},
|
|
181
|
+
experimentalFfmpegPath: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
describe: 'Path to ffmpeg executable for screencast recording.',
|
|
184
|
+
implies: 'experimentalScreencast',
|
|
185
|
+
},
|
|
176
186
|
experimentalWebmcp: {
|
|
177
187
|
type: 'boolean',
|
|
178
188
|
describe: 'Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`',
|
|
@@ -206,9 +216,10 @@ export const cliOptions = {
|
|
|
206
216
|
default: false,
|
|
207
217
|
describe: 'Set to true to include tools related to extensions. Note: This feature is currently only supported with a pipe connection. autoConnect, browserUrl, and wsEndpoint are not supported with this feature until 149 will be released.',
|
|
208
218
|
},
|
|
209
|
-
|
|
219
|
+
categoryExperimentalInPage: {
|
|
210
220
|
type: 'boolean',
|
|
211
221
|
hidden: true,
|
|
222
|
+
default: false,
|
|
212
223
|
describe: 'Set to true to enable tools exposed by the inspected page itself',
|
|
213
224
|
},
|
|
214
225
|
performanceCrux: {
|
|
@@ -334,3 +345,4 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
334
345
|
.version(version)
|
|
335
346
|
.parseSync();
|
|
336
347
|
}
|
|
348
|
+
//# sourceMappingURL=chrome-devtools-mcp-cli-options.js.map
|
|
@@ -15,9 +15,9 @@ import { VERSION } from '../version.js';
|
|
|
15
15
|
import { commands } from './chrome-devtools-cli-options.js';
|
|
16
16
|
import { cliOptions, parseArguments } from './chrome-devtools-mcp-cli-options.js';
|
|
17
17
|
await checkForUpdates('Run `npm install -g chrome-devtools-mcp@latest` and `chrome-devtools start` to update and restart the daemon.');
|
|
18
|
-
async function start(args) {
|
|
18
|
+
async function start(args, sessionId) {
|
|
19
19
|
const combinedArgs = [...args, ...defaultArgs];
|
|
20
|
-
await startDaemon(combinedArgs);
|
|
20
|
+
await startDaemon(combinedArgs, sessionId);
|
|
21
21
|
logDisclaimers(parseArguments(VERSION, combinedArgs));
|
|
22
22
|
}
|
|
23
23
|
const defaultArgs = ['--viaCli', '--experimentalStructuredContent'];
|
|
@@ -28,20 +28,10 @@ const startCliOptions = {
|
|
|
28
28
|
delete startCliOptions.autoConnect;
|
|
29
29
|
// Missing CLI serialization.
|
|
30
30
|
delete startCliOptions.viewport;
|
|
31
|
-
//
|
|
32
|
-
// tools, they need to be enabled during CLI generation.
|
|
33
|
-
delete startCliOptions.experimentalPageIdRouting;
|
|
34
|
-
delete startCliOptions.experimentalVision;
|
|
35
|
-
delete startCliOptions.experimentalWebmcp;
|
|
36
|
-
delete startCliOptions.experimentalInteropTools;
|
|
37
|
-
delete startCliOptions.experimentalScreencast;
|
|
38
|
-
delete startCliOptions.categoryEmulation;
|
|
39
|
-
delete startCliOptions.categoryPerformance;
|
|
40
|
-
delete startCliOptions.categoryNetwork;
|
|
41
|
-
delete startCliOptions.categoryExtensions;
|
|
42
|
-
// Always on in CLI.
|
|
31
|
+
// Change the defaults for the CLI.
|
|
43
32
|
delete startCliOptions.experimentalStructuredContent;
|
|
44
|
-
|
|
33
|
+
delete startCliOptions.experimentalInteropTools;
|
|
34
|
+
delete startCliOptions.experimentalPageIdRouting;
|
|
45
35
|
if (!('default' in cliOptions.headless)) {
|
|
46
36
|
throw new Error('headless cli option unexpectedly does not have a default');
|
|
47
37
|
}
|
|
@@ -51,11 +41,18 @@ if ('default' in cliOptions.isolated) {
|
|
|
51
41
|
startCliOptions.headless.default = true;
|
|
52
42
|
startCliOptions.isolated.description =
|
|
53
43
|
'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.';
|
|
44
|
+
startCliOptions.categoryExtensions.default = true;
|
|
54
45
|
const y = yargs(hideBin(process.argv))
|
|
55
46
|
.scriptName('chrome-devtools')
|
|
56
47
|
.showHelpOnFail(true)
|
|
57
48
|
.usage('chrome-devtools <command> [...args] --flags')
|
|
58
49
|
.usage(`Run 'chrome-devtools <command> --help' for help on the specific command.`)
|
|
50
|
+
.option('sessionId', {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Session ID for daemon scoping',
|
|
53
|
+
default: '',
|
|
54
|
+
hidden: true,
|
|
55
|
+
})
|
|
59
56
|
.demandCommand()
|
|
60
57
|
.version(VERSION)
|
|
61
58
|
.strict()
|
|
@@ -65,8 +62,8 @@ y.command('start', 'Start or restart chrome-devtools-mcp', y => y
|
|
|
65
62
|
.options(startCliOptions)
|
|
66
63
|
.example('$0 start --browserUrl http://localhost:9222', 'Start the server connecting to an existing browser')
|
|
67
64
|
.strict(), async (argv) => {
|
|
68
|
-
if (isDaemonRunning()) {
|
|
69
|
-
await stopDaemon();
|
|
65
|
+
if (isDaemonRunning(argv.sessionId)) {
|
|
66
|
+
await stopDaemon(argv.sessionId);
|
|
70
67
|
}
|
|
71
68
|
// Defaults but we do not want to affect the yargs conflict resolution.
|
|
72
69
|
if (argv.isolated === undefined && argv.userDataDir === undefined) {
|
|
@@ -76,15 +73,15 @@ y.command('start', 'Start or restart chrome-devtools-mcp', y => y
|
|
|
76
73
|
argv.headless = true;
|
|
77
74
|
}
|
|
78
75
|
const args = serializeArgs(cliOptions, argv);
|
|
79
|
-
await start(args);
|
|
76
|
+
await start(args, argv.sessionId);
|
|
80
77
|
process.exit(0);
|
|
81
78
|
}).strict(); // Re-enable strict validation for other commands; this is applied to the yargs instance itself
|
|
82
|
-
y.command('status', 'Checks if chrome-devtools-mcp is running', async () => {
|
|
83
|
-
if (isDaemonRunning()) {
|
|
79
|
+
y.command('status', 'Checks if chrome-devtools-mcp is running', y => y, async (argv) => {
|
|
80
|
+
if (isDaemonRunning(argv.sessionId)) {
|
|
84
81
|
console.log('chrome-devtools-mcp daemon is running.');
|
|
85
82
|
const response = await sendCommand({
|
|
86
83
|
method: 'status',
|
|
87
|
-
});
|
|
84
|
+
}, argv.sessionId);
|
|
88
85
|
if (response.success) {
|
|
89
86
|
const data = JSON.parse(response.result);
|
|
90
87
|
console.log(`pid=${data.pid} socket=${data.socketPath} start-date=${data.startDate} version=${data.version}`);
|
|
@@ -100,11 +97,12 @@ y.command('status', 'Checks if chrome-devtools-mcp is running', async () => {
|
|
|
100
97
|
}
|
|
101
98
|
process.exit(0);
|
|
102
99
|
});
|
|
103
|
-
y.command('stop', 'Stop chrome-devtools-mcp if any', async () => {
|
|
104
|
-
|
|
100
|
+
y.command('stop', 'Stop chrome-devtools-mcp if any', y => y, async (argv) => {
|
|
101
|
+
const sessionId = argv.sessionId;
|
|
102
|
+
if (!isDaemonRunning(sessionId)) {
|
|
105
103
|
process.exit(0);
|
|
106
104
|
}
|
|
107
|
-
await stopDaemon();
|
|
105
|
+
await stopDaemon(sessionId);
|
|
108
106
|
process.exit(0);
|
|
109
107
|
});
|
|
110
108
|
for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
@@ -159,9 +157,10 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
}, async (argv) => {
|
|
160
|
+
const sessionId = argv.sessionId;
|
|
162
161
|
try {
|
|
163
|
-
if (!isDaemonRunning()) {
|
|
164
|
-
await start([]);
|
|
162
|
+
if (!isDaemonRunning(sessionId)) {
|
|
163
|
+
await start([], sessionId);
|
|
165
164
|
}
|
|
166
165
|
const commandArgs = {};
|
|
167
166
|
for (const argName of Object.keys(args)) {
|
|
@@ -173,7 +172,7 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
|
173
172
|
method: 'invoke_tool',
|
|
174
173
|
tool: commandName,
|
|
175
174
|
args: commandArgs,
|
|
176
|
-
});
|
|
175
|
+
}, sessionId);
|
|
177
176
|
if (response.success) {
|
|
178
177
|
console.log(await handleResponse(JSON.parse(response.result), argv['output-format']));
|
|
179
178
|
}
|
|
@@ -189,3 +188,4 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
|
189
188
|
});
|
|
190
189
|
}
|
|
191
190
|
await y.parse();
|
|
191
|
+
//# sourceMappingURL=chrome-devtools.js.map
|
package/build/src/browser.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import net from 'node:net';
|
|
9
9
|
import { logger } from '../logger.js';
|
|
10
10
|
import { PipeTransport } from '../third_party/index.js';
|
|
11
|
-
import {
|
|
11
|
+
import { getTempFilePath } from '../utils/files.js';
|
|
12
12
|
import { DAEMON_SCRIPT_PATH, getSocketPath, getPidFilePath, isDaemonRunning, } from './utils.js';
|
|
13
13
|
const FILE_TIMEOUT = 10_000;
|
|
14
14
|
/**
|
|
@@ -48,12 +48,12 @@ function waitForFile(filePath, removed = false) {
|
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
export async function startDaemon(mcpArgs = []) {
|
|
52
|
-
if (isDaemonRunning()) {
|
|
51
|
+
export async function startDaemon(mcpArgs = [], sessionId) {
|
|
52
|
+
if (isDaemonRunning(sessionId)) {
|
|
53
53
|
logger('Daemon is already running');
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
const pidFilePath = getPidFilePath();
|
|
56
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
57
57
|
if (fs.existsSync(pidFilePath)) {
|
|
58
58
|
fs.unlinkSync(pidFilePath);
|
|
59
59
|
}
|
|
@@ -61,7 +61,7 @@ export async function startDaemon(mcpArgs = []) {
|
|
|
61
61
|
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], {
|
|
62
62
|
detached: true,
|
|
63
63
|
stdio: 'ignore',
|
|
64
|
-
env: process.env,
|
|
64
|
+
env: { ...process.env, CHROME_DEVTOOLS_MCP_SESSION_ID: sessionId },
|
|
65
65
|
cwd: process.cwd(),
|
|
66
66
|
windowsHide: true,
|
|
67
67
|
});
|
|
@@ -72,8 +72,8 @@ const SEND_COMMAND_TIMEOUT = 60_000; // ms
|
|
|
72
72
|
/**
|
|
73
73
|
* `sendCommand` opens a socket connection sends a single command and disconnects.
|
|
74
74
|
*/
|
|
75
|
-
export async function sendCommand(command) {
|
|
76
|
-
const socketPath = getSocketPath();
|
|
75
|
+
export async function sendCommand(command, sessionId) {
|
|
76
|
+
const socketPath = getSocketPath(sessionId);
|
|
77
77
|
const socket = net.createConnection({
|
|
78
78
|
path: socketPath,
|
|
79
79
|
});
|
|
@@ -102,13 +102,13 @@ export async function sendCommand(command) {
|
|
|
102
102
|
transport.send(JSON.stringify(command));
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
-
export async function stopDaemon() {
|
|
106
|
-
if (!isDaemonRunning()) {
|
|
105
|
+
export async function stopDaemon(sessionId) {
|
|
106
|
+
if (!isDaemonRunning(sessionId)) {
|
|
107
107
|
logger('Daemon is not running');
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
const pidFilePath = getPidFilePath();
|
|
111
|
-
await sendCommand({ method: 'stop' });
|
|
110
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
111
|
+
await sendCommand({ method: 'stop' }, sessionId);
|
|
112
112
|
await waitForFile(pidFilePath, /*removed=*/ true);
|
|
113
113
|
}
|
|
114
114
|
export async function handleResponse(response, format) {
|
|
@@ -141,7 +141,8 @@ export async function handleResponse(response, format) {
|
|
|
141
141
|
}
|
|
142
142
|
const data = Buffer.from(imageData, 'base64');
|
|
143
143
|
const name = crypto.randomUUID();
|
|
144
|
-
const
|
|
144
|
+
const filepath = await getTempFilePath(`${name}${extension}`);
|
|
145
|
+
fs.writeFileSync(filepath, data);
|
|
145
146
|
chunks.push(`Saved to ${filepath}.`);
|
|
146
147
|
}
|
|
147
148
|
else {
|
|
@@ -150,3 +151,4 @@ export async function handleResponse(response, format) {
|
|
|
150
151
|
}
|
|
151
152
|
return format === 'md' ? chunks.join(' ') : JSON.stringify(chunks);
|
|
152
153
|
}
|
|
154
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -11,19 +11,20 @@ import process from 'node:process';
|
|
|
11
11
|
import { logger } from '../logger.js';
|
|
12
12
|
import { Client, PipeTransport, StdioClientTransport, } from '../third_party/index.js';
|
|
13
13
|
import { VERSION } from '../version.js';
|
|
14
|
-
import { DAEMON_CLIENT_NAME,
|
|
15
|
-
const
|
|
16
|
-
|
|
14
|
+
import { DAEMON_CLIENT_NAME, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
|
|
15
|
+
const sessionId = process.env.CHROME_DEVTOOLS_MCP_SESSION_ID || '';
|
|
16
|
+
logger(`Daemon sessionId: ${sessionId}`);
|
|
17
|
+
if (isDaemonRunning(sessionId)) {
|
|
17
18
|
logger('Another daemon process is running.');
|
|
18
19
|
process.exit(1);
|
|
19
20
|
}
|
|
20
|
-
const pidFilePath = getPidFilePath();
|
|
21
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
21
22
|
fs.mkdirSync(path.dirname(pidFilePath), {
|
|
22
23
|
recursive: true,
|
|
23
24
|
});
|
|
24
25
|
fs.writeFileSync(pidFilePath, process.pid.toString());
|
|
25
26
|
logger(`Writing ${process.pid.toString()} to ${pidFilePath}`);
|
|
26
|
-
const socketPath = getSocketPath();
|
|
27
|
+
const socketPath = getSocketPath(sessionId);
|
|
27
28
|
const startDate = new Date();
|
|
28
29
|
const mcpServerArgs = process.argv.slice(2);
|
|
29
30
|
let mcpClient = null;
|
|
@@ -200,3 +201,4 @@ const started = startSocketServer().catch(error => {
|
|
|
200
201
|
logger('Failed to start daemon server:', error);
|
|
201
202
|
process.exit(1);
|
|
202
203
|
});
|
|
204
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -13,46 +13,50 @@ export const INDEX_SCRIPT_PATH = path.join(import.meta.dirname, '..', 'bin', 'ch
|
|
|
13
13
|
const APP_NAME = 'chrome-devtools-mcp';
|
|
14
14
|
export const DAEMON_CLIENT_NAME = 'chrome-devtools-cli-daemon';
|
|
15
15
|
// Using these paths due to strict limits on the POSIX socket path length.
|
|
16
|
-
export function getSocketPath() {
|
|
16
|
+
export function getSocketPath(sessionId) {
|
|
17
17
|
const uid = os.userInfo().uid;
|
|
18
|
+
const suffix = sessionId ? `-${sessionId}` : '';
|
|
19
|
+
const appName = APP_NAME + suffix;
|
|
18
20
|
if (IS_WINDOWS) {
|
|
19
21
|
// Windows uses Named Pipes, not file paths.
|
|
20
22
|
// This format is required for server.listen()
|
|
21
|
-
return path.join('\\\\.\\pipe',
|
|
23
|
+
return path.join('\\\\.\\pipe', appName, 'server.sock');
|
|
22
24
|
}
|
|
23
25
|
// 1. Try XDG_RUNTIME_DIR (Linux standard, sometimes macOS)
|
|
24
26
|
if (process.env.XDG_RUNTIME_DIR) {
|
|
25
|
-
return path.join(process.env.XDG_RUNTIME_DIR,
|
|
27
|
+
return path.join(process.env.XDG_RUNTIME_DIR, appName, 'server.sock');
|
|
26
28
|
}
|
|
27
29
|
// 2. macOS/Unix Fallback: Use /tmp/
|
|
28
30
|
// We use /tmp/ because it is much shorter than ~/Library/Application Support/
|
|
29
31
|
// and keeps us well under the 104-character limit.
|
|
30
|
-
return path.join('/tmp', `${
|
|
32
|
+
return path.join('/tmp', `${appName}-${uid}.sock`);
|
|
31
33
|
}
|
|
32
|
-
export function getRuntimeHome() {
|
|
34
|
+
export function getRuntimeHome(sessionId) {
|
|
33
35
|
const platform = os.platform();
|
|
34
36
|
const uid = os.userInfo().uid;
|
|
37
|
+
const suffix = sessionId ? `-${sessionId}` : '';
|
|
38
|
+
const appName = APP_NAME + suffix;
|
|
35
39
|
// 1. Check for the modern Unix standard
|
|
36
40
|
if (process.env.XDG_RUNTIME_DIR) {
|
|
37
|
-
return path.join(process.env.XDG_RUNTIME_DIR,
|
|
41
|
+
return path.join(process.env.XDG_RUNTIME_DIR, appName);
|
|
38
42
|
}
|
|
39
43
|
// 2. Fallback for macOS and older Linux
|
|
40
44
|
if (platform === 'darwin' || platform === 'linux') {
|
|
41
45
|
// /tmp is cleared on boot, making it perfect for PIDs
|
|
42
|
-
return path.join('/tmp', `${
|
|
46
|
+
return path.join('/tmp', `${appName}-${uid}`);
|
|
43
47
|
}
|
|
44
48
|
// 3. Windows Fallback
|
|
45
|
-
return path.join(os.tmpdir(),
|
|
49
|
+
return path.join(os.tmpdir(), appName);
|
|
46
50
|
}
|
|
47
51
|
export const IS_WINDOWS = os.platform() === 'win32';
|
|
48
|
-
export function getPidFilePath() {
|
|
49
|
-
const runtimeDir = getRuntimeHome();
|
|
52
|
+
export function getPidFilePath(sessionId) {
|
|
53
|
+
const runtimeDir = getRuntimeHome(sessionId);
|
|
50
54
|
return path.join(runtimeDir, 'daemon.pid');
|
|
51
55
|
}
|
|
52
|
-
export function getDaemonPid() {
|
|
56
|
+
export function getDaemonPid(sessionId) {
|
|
53
57
|
try {
|
|
54
|
-
const pidFile = getPidFilePath();
|
|
55
|
-
logger(`Daemon pid file ${pidFile}`);
|
|
58
|
+
const pidFile = getPidFilePath(sessionId);
|
|
59
|
+
logger(`Daemon pid file ${pidFile} sessionId=${sessionId}`);
|
|
56
60
|
if (!fs.existsSync(pidFile)) {
|
|
57
61
|
return null;
|
|
58
62
|
}
|
|
@@ -68,7 +72,8 @@ export function getDaemonPid() {
|
|
|
68
72
|
return null;
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
export function isDaemonRunning(
|
|
75
|
+
export function isDaemonRunning(sessionId) {
|
|
76
|
+
const pid = getDaemonPid(sessionId);
|
|
72
77
|
if (pid) {
|
|
73
78
|
try {
|
|
74
79
|
process.kill(pid, 0); // Throws if process doesn't exist
|
|
@@ -107,3 +112,4 @@ export function serializeArgs(options, argv) {
|
|
|
107
112
|
}
|
|
108
113
|
return args;
|
|
109
114
|
}
|
|
115
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -131,6 +131,36 @@ export class ConsoleFormatter {
|
|
|
131
131
|
id: this.#id,
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Groups consecutive messages with the same type, text, and argument count.
|
|
136
|
+
* Similar to Chrome DevTools' console grouping behavior.
|
|
137
|
+
*/
|
|
138
|
+
static groupConsecutive(messages) {
|
|
139
|
+
const grouped = [];
|
|
140
|
+
for (const msg of messages) {
|
|
141
|
+
const prev = grouped[grouped.length - 1];
|
|
142
|
+
if (prev &&
|
|
143
|
+
prev.message instanceof ConsoleFormatter &&
|
|
144
|
+
msg instanceof ConsoleFormatter &&
|
|
145
|
+
prev.message.#type === msg.#type &&
|
|
146
|
+
prev.message.#text === msg.#text &&
|
|
147
|
+
prev.message.#argCount === msg.#argCount) {
|
|
148
|
+
prev.count++;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
grouped.push({ message: msg, count: 1 });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return grouped.map(({ message, count }) => count > 1 && message instanceof ConsoleFormatter
|
|
155
|
+
? new GroupedConsoleFormatter({
|
|
156
|
+
id: message.#id,
|
|
157
|
+
type: message.#type,
|
|
158
|
+
text: message.#text,
|
|
159
|
+
argCount: message.#argCount,
|
|
160
|
+
isIgnored: message.isIgnored,
|
|
161
|
+
}, count)
|
|
162
|
+
: message);
|
|
163
|
+
}
|
|
134
164
|
toJSONDetailed() {
|
|
135
165
|
return {
|
|
136
166
|
id: this.#id,
|
|
@@ -144,8 +174,24 @@ export class ConsoleFormatter {
|
|
|
144
174
|
};
|
|
145
175
|
}
|
|
146
176
|
}
|
|
177
|
+
export class GroupedConsoleFormatter extends ConsoleFormatter {
|
|
178
|
+
#count;
|
|
179
|
+
constructor(params, count) {
|
|
180
|
+
super(params);
|
|
181
|
+
this.#count = count;
|
|
182
|
+
}
|
|
183
|
+
toString() {
|
|
184
|
+
return convertConsoleMessageConciseToString(this.toJSON());
|
|
185
|
+
}
|
|
186
|
+
toJSON() {
|
|
187
|
+
const json = super.toJSON();
|
|
188
|
+
json.count = this.#count;
|
|
189
|
+
return json;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
147
192
|
function convertConsoleMessageConciseToString(msg) {
|
|
148
|
-
|
|
193
|
+
const countSuffix = msg.count && msg.count > 1 ? ` [${msg.count} times]` : '';
|
|
194
|
+
return `msgid=${msg.id} [${msg.type}] ${msg.text} (${msg.argsCount} args)${countSuffix}`;
|
|
149
195
|
}
|
|
150
196
|
function convertConsoleMessageConciseDetailedToString(msg) {
|
|
151
197
|
const result = [
|
|
@@ -239,3 +285,4 @@ function formatCause(cause, formatter) {
|
|
|
239
285
|
...formatStackTraceInner(cause.stackTrace, cause.cause, formatter),
|
|
240
286
|
];
|
|
241
287
|
}
|
|
288
|
+
//# sourceMappingURL=ConsoleFormatter.js.map
|
|
@@ -4,13 +4,28 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { stableIdSymbol } from '../utils/id.js';
|
|
7
|
+
export function isNodeLike(item) {
|
|
8
|
+
return (typeof item === 'object' && item !== null && 'id' in item && 'name' in item);
|
|
9
|
+
}
|
|
7
10
|
export class HeapSnapshotFormatter {
|
|
8
11
|
#aggregates;
|
|
9
12
|
constructor(aggregates) {
|
|
10
13
|
this.#aggregates = aggregates;
|
|
11
14
|
}
|
|
15
|
+
static formatNodes(items) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
if (items.length > 0 && isNodeLike(items[0])) {
|
|
18
|
+
lines.push('id,name,type,distance,selfSize,retainedSize');
|
|
19
|
+
}
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
if (isNodeLike(item)) {
|
|
22
|
+
lines.push(`${item.id},"${item.name}",${item.type},${item.distance},${item.selfSize},${item.retainedSize}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
12
27
|
#getSortedAggregates() {
|
|
13
|
-
return Object.values(this.#aggregates).sort((a, b) => b.
|
|
28
|
+
return Object.values(this.#aggregates).sort((a, b) => b.maxRet - a.maxRet);
|
|
14
29
|
}
|
|
15
30
|
toString() {
|
|
16
31
|
const sorted = this.#getSortedAggregates();
|
|
@@ -33,6 +48,7 @@ export class HeapSnapshotFormatter {
|
|
|
33
48
|
}));
|
|
34
49
|
}
|
|
35
50
|
static sort(aggregates) {
|
|
36
|
-
return Object.entries(aggregates).sort((a, b) => b[1].
|
|
51
|
+
return Object.entries(aggregates).sort((a, b) => b[1].maxRet - a[1].maxRet);
|
|
37
52
|
}
|
|
38
53
|
}
|
|
54
|
+
//# sourceMappingURL=HeapSnapshotFormatter.js.map
|
|
@@ -12,7 +12,7 @@ export class SnapshotFormatter {
|
|
|
12
12
|
const chunks = [];
|
|
13
13
|
const root = this.#snapshot.root;
|
|
14
14
|
// Top-level content of the snapshot.
|
|
15
|
-
if (this.#snapshot.verbose &&
|
|
15
|
+
if (!this.#snapshot.verbose &&
|
|
16
16
|
this.#snapshot.hasSelectedElement &&
|
|
17
17
|
!this.#snapshot.selectedElementUid) {
|
|
18
18
|
chunks.push(`Note: there is a selected element in the DevTools Elements panel but it is not included into the current a11y tree snapshot.
|
|
@@ -132,3 +132,4 @@ const excludedAttributes = new Set([
|
|
|
132
132
|
'backendNodeId',
|
|
133
133
|
'loaderId',
|
|
134
134
|
]);
|
|
135
|
+
//# sourceMappingURL=SnapshotFormatter.js.map
|