browser-debugger-cli 0.13.0 → 0.15.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/.claude/skills/bdg/SKILL.md +101 -187
- package/README.md +4 -4
- package/dist/commands/cdp.js +1 -0
- package/dist/commands/cleanup.js +3 -0
- package/dist/commands/console.js +5 -1
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +20 -20
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +8 -5
- package/dist/commands/dom/formInteraction.js +1 -1
- package/dist/commands/dom/get.js +25 -7
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +2 -1
- package/dist/commands/dom/query.js +5 -3
- package/dist/commands/dom/screenshot.js +1 -0
- package/dist/commands/helpJson.d.ts +1 -1
- package/dist/commands/helpJson.js +4 -4
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/network/list.js +46 -3
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +60 -42
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.js +2 -2
- package/dist/commands/shared/dataFetcher.d.ts +4 -2
- package/dist/commands/shared/dataFetcher.js +11 -3
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +15 -3
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +1 -1
- package/dist/connection/launcher.js +1 -2
- package/dist/constants.d.ts +31 -5
- package/dist/constants.js +37 -5
- package/dist/daemon/SessionController.js +2 -0
- package/dist/daemon/launcher.d.ts +17 -3
- package/dist/daemon/launcher.js +37 -7
- package/dist/daemon/session/Session.d.ts +2 -1
- package/dist/daemon/session/Session.js +10 -2
- package/dist/daemon/session/TelemetryStore.d.ts +7 -0
- package/dist/daemon/session/TelemetryStore.js +6 -0
- package/dist/daemon/session/commandRegistry.js +25 -7
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +1 -0
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +8742 -8315
- package/dist/errors/messages.d.ts +31 -0
- package/dist/errors/messages.js +96 -6
- package/dist/index.js +1129 -548
- package/dist/ipc/client.d.ts +6 -1
- package/dist/ipc/client.js +11 -2
- package/dist/ipc/protocol/commands.d.ts +8 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
- package/dist/ipc/session/types.d.ts +5 -1
- package/dist/ipc/transport/index.d.ts +6 -0
- package/dist/ipc/transport/index.js +16 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/elementGeometry.d.ts +23 -0
- package/dist/runtime/dom/elementGeometry.js +17 -15
- package/dist/runtime/dom/elementInfo.d.ts +13 -4
- package/dist/runtime/dom/elementInfo.js +15 -5
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +40 -26
- package/dist/runtime/dom/inspectModel.d.ts +3 -3
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +1 -2
- package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
- package/dist/runtime/dom/reactEventHelpers.js +9 -2
- package/dist/runtime/dom/targetNode.d.ts +10 -6
- package/dist/runtime/dom/targetNode.js +15 -8
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +38 -3
- package/dist/session/paths.js +154 -7
- package/dist/session/portClaims.d.ts +0 -8
- package/dist/session/portClaims.js +1 -22
- package/dist/session/sessionList.d.ts +5 -1
- package/dist/session/sessionList.js +5 -1
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +83 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +11 -3
- package/dist/telemetry/har/sanitize.d.ts +24 -0
- package/dist/telemetry/har/sanitize.js +138 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
- package/dist/telemetry/har/sanitizeBody.js +168 -0
- package/dist/telemetry/network.d.ts +13 -16
- package/dist/telemetry/network.js +30 -52
- package/dist/telemetry/networkRetention.d.ts +83 -0
- package/dist/telemetry/networkRetention.js +117 -0
- package/dist/types.d.ts +26 -0
- package/dist/ui/OutputBuilder.d.ts +10 -0
- package/dist/ui/OutputBuilder.js +12 -0
- package/dist/ui/formatters/a11y.d.ts +5 -7
- package/dist/ui/formatters/a11y.js +7 -61
- package/dist/ui/formatters/console/chronological.js +4 -4
- package/dist/ui/formatters/console/follow.d.ts +4 -2
- package/dist/ui/formatters/console/follow.js +6 -3
- package/dist/ui/formatters/console/json.d.ts +3 -6
- package/dist/ui/formatters/console/json.js +9 -13
- package/dist/ui/formatters/console/shared.d.ts +17 -2
- package/dist/ui/formatters/console/shared.js +17 -0
- package/dist/ui/formatters/console/summarize.d.ts +2 -2
- package/dist/ui/formatters/console/summarize.js +22 -7
- package/dist/ui/formatters/console.d.ts +1 -1
- package/dist/ui/formatters/console.js +1 -5
- package/dist/ui/formatters/details.js +1 -1
- package/dist/ui/formatters/dom.d.ts +13 -4
- package/dist/ui/formatters/dom.js +25 -7
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/longValues.d.ts +14 -0
- package/dist/ui/formatters/longValues.js +23 -0
- package/dist/ui/formatters/networkList.d.ts +8 -2
- package/dist/ui/formatters/networkList.js +11 -2
- package/dist/ui/formatters/preview.d.ts +4 -1
- package/dist/ui/formatters/preview.js +55 -13
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/messages/chrome.d.ts +34 -7
- package/dist/ui/messages/chrome.js +81 -15
- package/dist/ui/messages/commands.d.ts +29 -8
- package/dist/ui/messages/commands.js +36 -8
- package/dist/ui/messages/networkMessages.d.ts +50 -0
- package/dist/ui/messages/networkMessages.js +66 -0
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/atomicFile.d.ts +2 -1
- package/dist/utils/atomicFile.js +5 -2
- package/dist/utils/directories.d.ts +41 -0
- package/dist/utils/directories.js +48 -0
- package/dist/utils/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -193,16 +193,14 @@ export function valueMismatchWarning(mismatch) {
|
|
|
193
193
|
*/
|
|
194
194
|
export const CLICK_NOT_RECEIVED_WARNING = 'The click may not have reached the element: the page saw no mouse press (the browser may be showing a dialog or bubble that captures input)';
|
|
195
195
|
/**
|
|
196
|
-
* Note under a shortened list
|
|
196
|
+
* Note under a shortened human list whose JSON output lists more, up to a cap.
|
|
197
197
|
*
|
|
198
|
-
* @param hidden -
|
|
199
|
-
* @param jsonLimit -
|
|
200
|
-
* @returns e.g. "... and
|
|
201
|
-
* "... and 8980 more (--json lists the first 100)"
|
|
198
|
+
* @param hidden - Items not listed
|
|
199
|
+
* @param jsonLimit - Most items the JSON output lists
|
|
200
|
+
* @returns e.g. "... and 8980 more (--json lists up to 100)"
|
|
202
201
|
*/
|
|
203
202
|
export function moreMatchesNote(hidden, jsonLimit) {
|
|
204
|
-
|
|
205
|
-
return `... and ${hidden} more (${where})`;
|
|
203
|
+
return `... and ${hidden} more (--json lists up to ${jsonLimit})`;
|
|
206
204
|
}
|
|
207
205
|
/**
|
|
208
206
|
* Note under `dom query` matches cut by `--limit`.
|
|
@@ -225,6 +223,24 @@ export function queryMoreMatchesNote(omitted, indexed) {
|
|
|
225
223
|
export function queryViewportCheckedNote(checked) {
|
|
226
224
|
return `Visibility is checked for the first ${checked} matches only; ${sessionCommand('bdg dom layout <index>')} checks any of them`;
|
|
227
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* First line under an accessibility tree cut by `--limit` or `--depth`.
|
|
228
|
+
*
|
|
229
|
+
* @param listed - Nodes listed
|
|
230
|
+
* @returns e.g. "Showing the first 50 nodes (text boxes and repeated text left out)"
|
|
231
|
+
*/
|
|
232
|
+
export function a11yTreeShownNote(listed) {
|
|
233
|
+
return `Showing the first ${listed} nodes (text boxes and repeated text left out)`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* How to see the rest of an accessibility tree cut by `--limit` or `--depth`.
|
|
237
|
+
*
|
|
238
|
+
* @param omitted - Nodes left out
|
|
239
|
+
* @returns e.g. "51391 more: --limit 0 lists all, --depth <n> limits the levels, or search with bdg dom a11y query \"role:<role>\""
|
|
240
|
+
*/
|
|
241
|
+
export function a11yTreeMoreNote(omitted) {
|
|
242
|
+
return `${omitted} more: --limit 0 lists all, --depth <n> limits the levels, or search with bdg dom a11y query "role:<role>"`;
|
|
243
|
+
}
|
|
228
244
|
/**
|
|
229
245
|
* Note under a list of a11y query matches cut by `--limit`.
|
|
230
246
|
*
|
|
@@ -585,10 +601,13 @@ export function coverText(cover, transparent) {
|
|
|
585
601
|
* Note when `bdg dom inspect` could not read the element's matched rules, so
|
|
586
602
|
* no hints, rules or why were computed.
|
|
587
603
|
*
|
|
588
|
-
* @param reason - `timeout` (very large stylesheets)
|
|
604
|
+
* @param reason - `timeout` (very large stylesheets), `failed` (Chrome reported
|
|
605
|
+
* an error) or `skipped` (hints not read: an earlier read on this page timed out)
|
|
589
606
|
* @returns Note
|
|
590
607
|
*/
|
|
591
608
|
export function inspectCascadeNote(reason) {
|
|
609
|
+
if (reason === 'skipped')
|
|
610
|
+
return "hints skipped: this page's stylesheets are slow to read (--rules waits 5 s)";
|
|
592
611
|
return reason === 'timeout'
|
|
593
612
|
? "CSS rules not read: the page's stylesheets took too long (hints wait 1 s; --rules and --why 5 s)"
|
|
594
613
|
: 'CSS rules not read: Chrome could not report the rules matching this element';
|
|
@@ -1279,4 +1298,13 @@ Examples:
|
|
|
1279
1298
|
export function helpJsonDetailsNote() {
|
|
1280
1299
|
return 'Option behaviors, defaults, choices and examples: bdg <command> --help --json (e.g. bdg dom query --help --json). Everything at once: bdg --help --json --full';
|
|
1281
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Pointer after a value cut for human output.
|
|
1303
|
+
*
|
|
1304
|
+
* @param count - Characters left out
|
|
1305
|
+
* @returns e.g. `… 299800 more chars (use --full)`
|
|
1306
|
+
*/
|
|
1307
|
+
export function moreCharsNote(count) {
|
|
1308
|
+
return `… ${count} more chars (use --full)`;
|
|
1309
|
+
}
|
|
1282
1310
|
//# sourceMappingURL=commands.js.map
|
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* User-facing messages for the network list command output and formatting.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Why a stored response body was replaced by a placeholder (shown by
|
|
8
|
+
* `bdg details network <id>` as `bodyNotCaptured`).
|
|
9
|
+
*
|
|
10
|
+
* @param budgetBytes - Total body budget of the session
|
|
11
|
+
* @returns e.g. `evicted: total body budget (bdg keeps the newest 100 MB of response bodies)`
|
|
12
|
+
*/
|
|
13
|
+
export declare function bodyEvictedReason(budgetBytes: number): string;
|
|
14
|
+
/** What a session's network capture let go at its limits */
|
|
15
|
+
export interface NetworkEvictionCounts {
|
|
16
|
+
/** Oldest finished requests dropped at the request cap */
|
|
17
|
+
requestsDropped: number;
|
|
18
|
+
/** Oldest response bodies evicted at the body budget */
|
|
19
|
+
bodiesEvicted: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Note that the session dropped its oldest requests or evicted its oldest
|
|
23
|
+
* response bodies at its limits.
|
|
24
|
+
*
|
|
25
|
+
* @param counts - Requests dropped and bodies evicted
|
|
26
|
+
* @returns e.g. `⚠ 2000 older network requests were dropped: bdg keeps the newest 10000`;
|
|
27
|
+
* undefined when nothing was let go
|
|
28
|
+
*/
|
|
29
|
+
export declare function networkEvictedNote(counts: NetworkEvictionCounts): string | undefined;
|
|
6
30
|
/**
|
|
7
31
|
* Generate message for following network output.
|
|
8
32
|
*
|
|
@@ -29,4 +53,30 @@ export declare function headerRepeatedNote(count: number): string;
|
|
|
29
53
|
* @returns Note text
|
|
30
54
|
*/
|
|
31
55
|
export declare function localProxyNote(): string;
|
|
56
|
+
/**
|
|
57
|
+
* HAR log comment of a sanitized export.
|
|
58
|
+
*
|
|
59
|
+
* @returns Comment naming what was redacted and the flag that keeps it
|
|
60
|
+
*/
|
|
61
|
+
export declare function harSanitizedComment(): string;
|
|
62
|
+
/**
|
|
63
|
+
* Result of a HAR export to a file.
|
|
64
|
+
*/
|
|
65
|
+
export interface HarExportSummary {
|
|
66
|
+
/** Absolute path written */
|
|
67
|
+
file: string;
|
|
68
|
+
/** Requests exported */
|
|
69
|
+
entries: number;
|
|
70
|
+
/** Whether --filter left requests out */
|
|
71
|
+
filtered: boolean;
|
|
72
|
+
/** Whether credentials were redacted */
|
|
73
|
+
sanitized: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Success message of `bdg network har`.
|
|
77
|
+
*
|
|
78
|
+
* @param result - Export result
|
|
79
|
+
* @returns e.g. `✓ Exported 4 requests to /tmp/out.har` and a line on sanitization
|
|
80
|
+
*/
|
|
81
|
+
export declare function harExportedMessage(result: HarExportSummary): string;
|
|
32
82
|
//# sourceMappingURL=networkMessages.d.ts.map
|
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
*
|
|
4
4
|
* User-facing messages for the network list command output and formatting.
|
|
5
5
|
*/
|
|
6
|
+
import { MAX_NETWORK_REQUESTS, MAX_TOTAL_BODY_BYTES } from '../../constants.js';
|
|
7
|
+
import { pluralize } from '../formatting.js';
|
|
8
|
+
/**
|
|
9
|
+
* A byte budget in whole megabytes.
|
|
10
|
+
*
|
|
11
|
+
* @param bytes - Budget
|
|
12
|
+
* @returns e.g. `100 MB`
|
|
13
|
+
*/
|
|
14
|
+
function megabytes(bytes) {
|
|
15
|
+
return `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Why a stored response body was replaced by a placeholder (shown by
|
|
19
|
+
* `bdg details network <id>` as `bodyNotCaptured`).
|
|
20
|
+
*
|
|
21
|
+
* @param budgetBytes - Total body budget of the session
|
|
22
|
+
* @returns e.g. `evicted: total body budget (bdg keeps the newest 100 MB of response bodies)`
|
|
23
|
+
*/
|
|
24
|
+
export function bodyEvictedReason(budgetBytes) {
|
|
25
|
+
return `evicted: total body budget (bdg keeps the newest ${megabytes(budgetBytes)} of response bodies)`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Note that the session dropped its oldest requests or evicted its oldest
|
|
29
|
+
* response bodies at its limits.
|
|
30
|
+
*
|
|
31
|
+
* @param counts - Requests dropped and bodies evicted
|
|
32
|
+
* @returns e.g. `⚠ 2000 older network requests were dropped: bdg keeps the newest 10000`;
|
|
33
|
+
* undefined when nothing was let go
|
|
34
|
+
*/
|
|
35
|
+
export function networkEvictedNote(counts) {
|
|
36
|
+
const { requestsDropped, bodiesEvicted } = counts;
|
|
37
|
+
const requests = pluralize(requestsDropped, 'older network request');
|
|
38
|
+
const bodies = pluralize(bodiesEvicted, 'older response body', 'older response bodies');
|
|
39
|
+
const budget = megabytes(MAX_TOTAL_BODY_BYTES);
|
|
40
|
+
if (requestsDropped > 0 && bodiesEvicted > 0) {
|
|
41
|
+
return `⚠ ${requests} dropped, ${bodies} evicted: bdg keeps the newest ${MAX_NETWORK_REQUESTS} requests and ${budget} of bodies`;
|
|
42
|
+
}
|
|
43
|
+
if (requestsDropped > 0) {
|
|
44
|
+
return `⚠ ${requests} ${requestsDropped === 1 ? 'was' : 'were'} dropped: bdg keeps the newest ${MAX_NETWORK_REQUESTS}`;
|
|
45
|
+
}
|
|
46
|
+
if (bodiesEvicted > 0) {
|
|
47
|
+
return `⚠ ${bodies} ${bodiesEvicted === 1 ? 'was' : 'were'} evicted: bdg keeps the newest ${budget} of bodies`;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
6
51
|
/**
|
|
7
52
|
* Generate message for following network output.
|
|
8
53
|
*
|
|
@@ -37,4 +82,25 @@ export function headerRepeatedNote(count) {
|
|
|
37
82
|
export function localProxyNote() {
|
|
38
83
|
return '(loopback; likely a local proxy)';
|
|
39
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* HAR log comment of a sanitized export.
|
|
87
|
+
*
|
|
88
|
+
* @returns Comment naming what was redacted and the flag that keeps it
|
|
89
|
+
*/
|
|
90
|
+
export function harSanitizedComment() {
|
|
91
|
+
return 'Sanitized by bdg: values of auth, cookie, API key, token and session headers, cookies, credential query parameters in URLs, and password/token fields of request bodies are [redacted] (by name, so some harmless values are too); response bodies and WebSocket messages are not sanitized. Export with --include-sensitive to keep everything';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Success message of `bdg network har`.
|
|
95
|
+
*
|
|
96
|
+
* @param result - Export result
|
|
97
|
+
* @returns e.g. `✓ Exported 4 requests to /tmp/out.har` and a line on sanitization
|
|
98
|
+
*/
|
|
99
|
+
export function harExportedMessage(result) {
|
|
100
|
+
const filterNote = result.filtered ? ' (filtered)' : '';
|
|
101
|
+
const note = result.sanitized
|
|
102
|
+
? 'Credentials sanitized (auth/cookie/API key/token headers, cookies, URL tokens, password and token body fields are [redacted]); --include-sensitive keeps them'
|
|
103
|
+
: '⚠ Includes credentials (--include-sensitive): share this file with care';
|
|
104
|
+
return `✓ Exported ${result.entries} requests${filterNote} to ${result.file}\n ${note}`;
|
|
105
|
+
}
|
|
40
106
|
//# sourceMappingURL=networkMessages.js.map
|
|
@@ -88,6 +88,14 @@ export declare function endedSessionText(label: string, end: {
|
|
|
88
88
|
reason: string;
|
|
89
89
|
endedAt: number;
|
|
90
90
|
}): string;
|
|
91
|
+
/**
|
|
92
|
+
* A session whose directory is not safe to use, for `bdg sessions`.
|
|
93
|
+
*
|
|
94
|
+
* @param label - Session name as listed
|
|
95
|
+
* @param why - Untrusted directory and why
|
|
96
|
+
* @returns One line
|
|
97
|
+
*/
|
|
98
|
+
export declare function untrustedSessionText(label: string, why: string): string;
|
|
91
99
|
/**
|
|
92
100
|
* Note after a failed start or a stop whose daemon had not exited when bdg stopped waiting.
|
|
93
101
|
*
|
|
@@ -101,6 +101,16 @@ export function lastSessionEndText(end) {
|
|
|
101
101
|
export function endedSessionText(label, end) {
|
|
102
102
|
return `${label} ended ${sessionEndText(end)}`;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* A session whose directory is not safe to use, for `bdg sessions`.
|
|
106
|
+
*
|
|
107
|
+
* @param label - Session name as listed
|
|
108
|
+
* @param why - Untrusted directory and why
|
|
109
|
+
* @returns One line
|
|
110
|
+
*/
|
|
111
|
+
export function untrustedSessionText(label, why) {
|
|
112
|
+
return `${label}: ${why}`;
|
|
113
|
+
}
|
|
104
114
|
/**
|
|
105
115
|
* When and why a session ended without `bdg stop`.
|
|
106
116
|
*
|
|
@@ -38,12 +38,13 @@ export declare class AtomicFileWriter {
|
|
|
38
38
|
*
|
|
39
39
|
* @param filePath - Target file path
|
|
40
40
|
* @param data - Data to write
|
|
41
|
-
* @param options - Write options
|
|
41
|
+
* @param options - Write options (`mode`: permissions of the new file, less the umask)
|
|
42
42
|
* @returns Promise that resolves when write completes
|
|
43
43
|
* @throws Error if write operation fails
|
|
44
44
|
*/
|
|
45
45
|
static writeAsync(filePath: string, data: string, options?: {
|
|
46
46
|
encoding?: BufferEncoding;
|
|
47
|
+
mode?: number;
|
|
47
48
|
}): Promise<void>;
|
|
48
49
|
/**
|
|
49
50
|
* Write binary data (Buffer) to a file atomically (asynchronous).
|
package/dist/utils/atomicFile.js
CHANGED
|
@@ -59,14 +59,17 @@ export class AtomicFileWriter {
|
|
|
59
59
|
*
|
|
60
60
|
* @param filePath - Target file path
|
|
61
61
|
* @param data - Data to write
|
|
62
|
-
* @param options - Write options
|
|
62
|
+
* @param options - Write options (`mode`: permissions of the new file, less the umask)
|
|
63
63
|
* @returns Promise that resolves when write completes
|
|
64
64
|
* @throws Error if write operation fails
|
|
65
65
|
*/
|
|
66
66
|
static async writeAsync(filePath, data, options = {}) {
|
|
67
67
|
const tmpPath = this.getTempPath(filePath);
|
|
68
68
|
try {
|
|
69
|
-
await fs.promises.writeFile(tmpPath, data, {
|
|
69
|
+
await fs.promises.writeFile(tmpPath, data, {
|
|
70
|
+
encoding: options.encoding ?? 'utf-8',
|
|
71
|
+
...(options.mode !== undefined && { mode: options.mode }),
|
|
72
|
+
});
|
|
70
73
|
await fs.promises.rename(tmpPath, filePath);
|
|
71
74
|
}
|
|
72
75
|
catch (error) {
|
|
@@ -31,4 +31,45 @@ export declare function directoryProblem(dir: string): DirectoryProblem | null;
|
|
|
31
31
|
* file on the path), `EACCES` (not writable), or the `mkdir` error
|
|
32
32
|
*/
|
|
33
33
|
export declare function makeDirectory(dir: string, mode?: number): void;
|
|
34
|
+
/**
|
|
35
|
+
* Kind of untrusted directory: a symlink, not a directory, another user's,
|
|
36
|
+
* a shared sticky directory such as `/tmp` (others may create entries in
|
|
37
|
+
* it), or writable by others
|
|
38
|
+
*/
|
|
39
|
+
export type DirTrustKind = 'symlink' | 'not-directory' | 'owner' | 'shared' | 'writable';
|
|
40
|
+
/** Why a directory cannot be trusted */
|
|
41
|
+
export interface DirTrustProblem {
|
|
42
|
+
/** e.g. `owned by uid 1001`, `writable by others (mode 777)` */
|
|
43
|
+
reason: string;
|
|
44
|
+
kind: DirTrustKind;
|
|
45
|
+
}
|
|
46
|
+
/** Options of {@link dirTrustProblem} */
|
|
47
|
+
export interface DirTrustOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Accept a directory its group may write to (only others' write access is
|
|
50
|
+
* refused): under umask 002, common with per-user groups, directories are
|
|
51
|
+
* created 0775 and the group holds only the user
|
|
52
|
+
*/
|
|
53
|
+
allowGroupWrite?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a path is a directory the current user can trust: a real directory
|
|
57
|
+
* (not a symlink), owned by the user, not writable by group or others (by
|
|
58
|
+
* others only, with `allowGroupWrite`).
|
|
59
|
+
*
|
|
60
|
+
* @param dir - Existing path
|
|
61
|
+
* @param options - What else to accept
|
|
62
|
+
* @returns Why it cannot be trusted, or null if it can
|
|
63
|
+
* @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
|
|
64
|
+
*/
|
|
65
|
+
export declare function dirTrustProblem(dir: string, options?: DirTrustOptions): DirTrustProblem | null;
|
|
66
|
+
/**
|
|
67
|
+
* Strict form of {@link dirTrustProblem}: not a symlink, owned by the user,
|
|
68
|
+
* not writable by group or others.
|
|
69
|
+
*
|
|
70
|
+
* @param dir - Existing path
|
|
71
|
+
* @returns Why it cannot be trusted, or null if it can
|
|
72
|
+
* @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
|
|
73
|
+
*/
|
|
74
|
+
export declare function untrustedDirReason(dir: string): string | null;
|
|
34
75
|
//# sourceMappingURL=directories.d.ts.map
|
|
@@ -85,4 +85,52 @@ export function makeDirectory(dir, mode) {
|
|
|
85
85
|
}
|
|
86
86
|
fs.mkdirSync(dir, { recursive: true, ...(mode !== undefined && { mode }) });
|
|
87
87
|
}
|
|
88
|
+
/** Permission bits that let group or others write */
|
|
89
|
+
const GROUP_OTHER_WRITE = 0o022;
|
|
90
|
+
/** Permission bit that lets others (not the group) write */
|
|
91
|
+
const OTHER_WRITE = 0o002;
|
|
92
|
+
/** Sticky bit: a shared directory like `/tmp` where only owners remove their entries */
|
|
93
|
+
const STICKY = 0o1000;
|
|
94
|
+
/**
|
|
95
|
+
* Whether a path is a directory the current user can trust: a real directory
|
|
96
|
+
* (not a symlink), owned by the user, not writable by group or others (by
|
|
97
|
+
* others only, with `allowGroupWrite`).
|
|
98
|
+
*
|
|
99
|
+
* @param dir - Existing path
|
|
100
|
+
* @param options - What else to accept
|
|
101
|
+
* @returns Why it cannot be trusted, or null if it can
|
|
102
|
+
* @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
|
|
103
|
+
*/
|
|
104
|
+
export function dirTrustProblem(dir, options = {}) {
|
|
105
|
+
const stat = fs.lstatSync(dir);
|
|
106
|
+
const problem = (reason, kind) => ({ reason, kind });
|
|
107
|
+
if (stat.isSymbolicLink())
|
|
108
|
+
return problem('it is a symbolic link', 'symlink');
|
|
109
|
+
if (!stat.isDirectory())
|
|
110
|
+
return problem('not a directory', 'not-directory');
|
|
111
|
+
const uid = process.getuid?.();
|
|
112
|
+
if (uid !== undefined && stat.uid !== uid)
|
|
113
|
+
return problem(`owned by uid ${stat.uid}`, 'owner');
|
|
114
|
+
if (process.platform === 'win32')
|
|
115
|
+
return null;
|
|
116
|
+
const writeBits = options.allowGroupWrite ? OTHER_WRITE : GROUP_OTHER_WRITE;
|
|
117
|
+
if ((stat.mode & writeBits) === 0)
|
|
118
|
+
return null;
|
|
119
|
+
const mode = (stat.mode & 0o7777).toString(8);
|
|
120
|
+
if ((stat.mode & STICKY) !== 0 && (stat.mode & OTHER_WRITE) !== 0) {
|
|
121
|
+
return problem(`a shared sticky directory (mode ${mode})`, 'shared');
|
|
122
|
+
}
|
|
123
|
+
return problem(`writable by others (mode ${mode})`, 'writable');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Strict form of {@link dirTrustProblem}: not a symlink, owned by the user,
|
|
127
|
+
* not writable by group or others.
|
|
128
|
+
*
|
|
129
|
+
* @param dir - Existing path
|
|
130
|
+
* @returns Why it cannot be trusted, or null if it can
|
|
131
|
+
* @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
|
|
132
|
+
*/
|
|
133
|
+
export function untrustedDirReason(dir) {
|
|
134
|
+
return dirTrustProblem(dir)?.reason ?? null;
|
|
135
|
+
}
|
|
88
136
|
//# sourceMappingURL=directories.js.map
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { CDPTarget } from '../types.js';
|
|
2
2
|
import type { Logger } from '../ui/logging/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Timeout for CDP HTTP requests in milliseconds.
|
|
5
|
+
*
|
|
6
|
+
* Chrome's HTTP API should respond quickly when running.
|
|
7
|
+
* A 5-second timeout helps detect when Chrome is not responding.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CDP_HTTP_TIMEOUT_MS = 5000;
|
|
3
10
|
/**
|
|
4
11
|
* Options for CDP HTTP target fetch.
|
|
5
12
|
*/
|
|
@@ -47,10 +54,10 @@ export type DevToolsProbe = {
|
|
|
47
54
|
*
|
|
48
55
|
* @param port - Chrome debugging port
|
|
49
56
|
* @param logger - Optional logger for debug output
|
|
50
|
-
* @param options - Host and
|
|
57
|
+
* @param options - Host, HTTPS and request timeout
|
|
51
58
|
* @returns What answered
|
|
52
59
|
*/
|
|
53
|
-
export declare function probeDevToolsEndpoint(port: number, logger?: Logger, options?:
|
|
60
|
+
export declare function probeDevToolsEndpoint(port: number, logger?: Logger, options?: FetchCDPTargetsOptions): Promise<DevToolsProbe>;
|
|
54
61
|
/**
|
|
55
62
|
* The browser-level DevTools WebSocket URL of a Chrome (`/json/version`).
|
|
56
63
|
*
|
package/dist/utils/http.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getErrorMessage } from './errors.js';
|
|
|
6
6
|
* Chrome's HTTP API should respond quickly when running.
|
|
7
7
|
* A 5-second timeout helps detect when Chrome is not responding.
|
|
8
8
|
*/
|
|
9
|
-
const CDP_HTTP_TIMEOUT_MS = 5000;
|
|
9
|
+
export const CDP_HTTP_TIMEOUT_MS = 5000;
|
|
10
10
|
/**
|
|
11
11
|
* Fetch CDP targets from Chrome's HTTP API.
|
|
12
12
|
*
|
|
@@ -67,14 +67,15 @@ export async function fetchCDPTargets(port = DEFAULT_CDP_PORT, logger, options)
|
|
|
67
67
|
*
|
|
68
68
|
* @param port - Chrome debugging port
|
|
69
69
|
* @param logger - Optional logger for debug output
|
|
70
|
-
* @param options - Host and
|
|
70
|
+
* @param options - Host, HTTPS and request timeout
|
|
71
71
|
* @returns What answered
|
|
72
72
|
*/
|
|
73
73
|
export async function probeDevToolsEndpoint(port, logger, options) {
|
|
74
74
|
const url = `${options?.secure ? 'https' : 'http'}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
|
|
75
|
+
const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
|
|
75
76
|
let response;
|
|
76
77
|
try {
|
|
77
|
-
response = await fetch(url, { signal: AbortSignal.timeout(
|
|
78
|
+
response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
78
79
|
}
|
|
79
80
|
catch (error) {
|
|
80
81
|
logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
|
package/dist/utils/strings.d.ts
CHANGED
|
@@ -11,4 +11,23 @@
|
|
|
11
11
|
* @returns Truncated text with ellipsis if needed
|
|
12
12
|
*/
|
|
13
13
|
export declare function truncateByLength(text: string, maxLength?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* A text cut to a maximum length, with its original length when it was cut.
|
|
16
|
+
*/
|
|
17
|
+
export interface CappedText {
|
|
18
|
+
/** The text, or its first `maxLength` characters */
|
|
19
|
+
text: string;
|
|
20
|
+
/** Original length, set only when the text was cut */
|
|
21
|
+
truncatedFrom?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cut a text to its first `maxLength` characters (UTF-16 code units),
|
|
25
|
+
* keeping the original length (the `truncatedFrom` of JSON output). A
|
|
26
|
+
* surrogate pair (an emoji) is never split: the cut ends before it.
|
|
27
|
+
*
|
|
28
|
+
* @param text - Text to cut
|
|
29
|
+
* @param maxLength - Characters kept (at most)
|
|
30
|
+
* @returns The text, with `truncatedFrom` when it was cut
|
|
31
|
+
*/
|
|
32
|
+
export declare function capLength(text: string, maxLength: number): CappedText;
|
|
14
33
|
//# sourceMappingURL=strings.d.ts.map
|
package/dist/utils/strings.js
CHANGED
|
@@ -20,4 +20,20 @@ export function truncateByLength(text, maxLength = DEFAULT_MAX_LENGTH) {
|
|
|
20
20
|
}
|
|
21
21
|
return text.slice(0, maxLength - 1) + '…';
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Cut a text to its first `maxLength` characters (UTF-16 code units),
|
|
25
|
+
* keeping the original length (the `truncatedFrom` of JSON output). A
|
|
26
|
+
* surrogate pair (an emoji) is never split: the cut ends before it.
|
|
27
|
+
*
|
|
28
|
+
* @param text - Text to cut
|
|
29
|
+
* @param maxLength - Characters kept (at most)
|
|
30
|
+
* @returns The text, with `truncatedFrom` when it was cut
|
|
31
|
+
*/
|
|
32
|
+
export function capLength(text, maxLength) {
|
|
33
|
+
if (text.length <= maxLength)
|
|
34
|
+
return { text };
|
|
35
|
+
const lastKept = text.charCodeAt(maxLength - 1);
|
|
36
|
+
const end = lastKept >= 0xd800 && lastKept <= 0xdbff ? maxLength - 1 : maxLength;
|
|
37
|
+
return { text: text.slice(0, end), truncatedFrom: text.length };
|
|
38
|
+
}
|
|
23
39
|
//# sourceMappingURL=strings.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-debugger-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "DevTools telemetry in your terminal. For humans and agents. Direct WebSocket to Chrome's debugging port.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"validate:ts-version": "node -e \"const ts = require('typescript'); const v = ts.version.split('.'); if (parseInt(v[0]) < 5 || (parseInt(v[0]) === 5 && parseInt(v[1]) < 6)) { console.error('Requires TypeScript 5.6+, found:', ts.version); process.exit(1); } else { console.log('✅ TypeScript version:', ts.version); }\"",
|
|
28
28
|
"check": "npm run format:check && npm run type-check && npm run lint",
|
|
29
29
|
"check:enhanced": "npm run format:check && npm run type-check && npm run lint && npm run validate:module-type && npm run validate:ts-version",
|
|
30
|
-
"test": "tsx --test 'src/**/__tests__/**/!(*.smoke).test.ts'",
|
|
30
|
+
"test": "tsx --test --test-timeout=120000 'src/**/__tests__/**/!(*.smoke).test.ts'",
|
|
31
31
|
"test:watch": "tsx --test --watch 'src/**/__tests__/**/!(*.smoke).test.ts'",
|
|
32
32
|
"test:coverage": "c8 npm test",
|
|
33
33
|
"test:smoke": "tsx --test --test-concurrency=1 'src/__tests__/smoke/*.smoke.test.ts'",
|