browser-debugger-cli 0.12.0 → 0.14.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 +100 -186
- package/README.md +5 -4
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +67 -13
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +23 -22
- package/dist/commands/dom/eval.d.ts +4 -2
- package/dist/commands/dom/eval.js +31 -7
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +9 -8
- package/dist/commands/dom/get.js +32 -14
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.js +13 -13
- package/dist/commands/dom/index.js +10 -3
- package/dist/commands/dom/query.d.ts +20 -2
- package/dist/commands/dom/query.js +39 -6
- package/dist/commands/dom/screenshot.js +3 -1
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +65 -12
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +81 -46
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +21 -10
- package/dist/commands/shared/dataFetcher.d.ts +14 -4
- package/dist/commands/shared/dataFetcher.js +20 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +17 -3
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/start.d.ts +7 -5
- package/dist/commands/start.js +65 -21
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands.js +1 -1
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher.js +3 -2
- package/dist/constants.d.ts +29 -1
- package/dist/constants.js +35 -1
- package/dist/daemon/SessionController.js +8 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +42 -3
- package/dist/daemon/session/TelemetryStore.d.ts +15 -1
- package/dist/daemon/session/TelemetryStore.js +19 -1
- package/dist/daemon/session/commandRegistry.js +52 -18
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +17 -2
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +2385 -1229
- package/dist/errors/messages.d.ts +62 -11
- package/dist/errors/messages.js +119 -22
- package/dist/index.js +14995 -9866
- package/dist/ipc/client.d.ts +18 -2
- package/dist/ipc/client.js +26 -5
- package/dist/ipc/protocol/auditTypes.d.ts +8 -2
- package/dist/ipc/protocol/commands.d.ts +16 -0
- package/dist/ipc/protocol/domTypes.d.ts +12 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +7 -2
- package/dist/ipc/session/types.d.ts +7 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/actionEffects.d.ts +5 -1
- package/dist/runtime/dom/actionEffects.js +26 -14
- package/dist/runtime/dom/audit.js +3 -2
- package/dist/runtime/dom/auditModel.js +6 -1
- package/dist/runtime/dom/auditScripts.d.ts +9 -3
- package/dist/runtime/dom/auditScripts.js +41 -5
- package/dist/runtime/dom/elementGeometry.d.ts +33 -3
- package/dist/runtime/dom/elementGeometry.js +44 -19
- package/dist/runtime/dom/elementInfo.d.ts +76 -18
- package/dist/runtime/dom/elementInfo.js +190 -40
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +7 -11
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +45 -32
- package/dist/runtime/dom/inspectAllStyles.js +1 -0
- package/dist/runtime/dom/inspectHints.d.ts +1 -1
- package/dist/runtime/dom/inspectModel.d.ts +5 -4
- package/dist/runtime/dom/inspectModel.js +7 -3
- package/dist/runtime/dom/inspectPaintModel.d.ts +2 -0
- package/dist/runtime/dom/inspectPaintModel.js +3 -1
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/inspectScripts.d.ts +29 -2
- package/dist/runtime/dom/inspectScripts.js +49 -10
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +10 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +17 -4
- package/dist/runtime/dom/reactEventHelpers.js +71 -28
- package/dist/runtime/dom/targetNode.d.ts +27 -10
- package/dist/runtime/dom/targetNode.js +283 -16
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +85 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/har/builder.js +1 -1
- 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/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +42 -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/audit.js +14 -5
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +7 -5
- package/dist/ui/formatters/console/follow.d.ts +5 -2
- package/dist/ui/formatters/console/follow.js +7 -4
- package/dist/ui/formatters/console/json.d.ts +4 -7
- package/dist/ui/formatters/console/json.js +16 -14
- package/dist/ui/formatters/console/shared.d.ts +47 -2
- package/dist/ui/formatters/console/shared.js +33 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +57 -11
- package/dist/ui/formatters/console.d.ts +3 -2
- package/dist/ui/formatters/console.js +8 -10
- package/dist/ui/formatters/details.js +4 -2
- package/dist/ui/formatters/dom.d.ts +14 -5
- package/dist/ui/formatters/dom.js +30 -13
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +9 -3
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +4 -2
- 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 -3
- package/dist/ui/formatters/preview.d.ts +6 -1
- package/dist/ui/formatters/preview.js +67 -15
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +20 -1
- package/dist/ui/messages/chrome.js +29 -3
- package/dist/ui/messages/commands.d.ts +153 -12
- package/dist/ui/messages/commands.js +198 -15
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/networkMessages.d.ts +24 -0
- package/dist/ui/messages/networkMessages.js +45 -0
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { sessionUnavailableSuggestion } from '../../errors/messages.js';
|
|
5
5
|
import { genericError } from '../../errors/messages.js';
|
|
6
|
-
import { OutputBuilder } from '../../ui/OutputBuilder.js';
|
|
7
|
-
import { connectionLostRetryMessage, connectionLostStopHintMessage, } from '../../ui/messages/preview.js';
|
|
6
|
+
import { OutputBuilder, stringifyEnvelope } from '../../ui/OutputBuilder.js';
|
|
7
|
+
import { connectionLostRetryMessage, connectionLostStopHintMessage, followedSessionEndedMessage, } from '../../ui/messages/preview.js';
|
|
8
8
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
9
9
|
/** Follow-mode state: whether a session ever answered, and whether its loss was reported */
|
|
10
10
|
const followState = { connected: false, lossReported: false };
|
|
@@ -20,8 +20,11 @@ export function noteFollowConnected() {
|
|
|
20
20
|
* Handle daemon connection errors with consistent formatting and behavior.
|
|
21
21
|
*
|
|
22
22
|
* Outside follow mode the command exits. In follow mode, a session that never
|
|
23
|
-
* answered exits too (there is nothing to follow, exit 83)
|
|
24
|
-
*
|
|
23
|
+
* answered exits too (there is nothing to follow, exit 83), and so does one
|
|
24
|
+
* that ends while followed (no session any more, exit 83), so a follower
|
|
25
|
+
* running in the background finds out. Other failures (a busy page, a
|
|
26
|
+
* timeout) are retried: reported once in text, and on every failed refresh
|
|
27
|
+
* in JSON, one object per line.
|
|
25
28
|
*
|
|
26
29
|
* @param error - Error message to display
|
|
27
30
|
* @param options - Error handling options
|
|
@@ -29,22 +32,30 @@ export function noteFollowConnected() {
|
|
|
29
32
|
*/
|
|
30
33
|
export function handleDaemonConnectionError(error, options) {
|
|
31
34
|
const { json = false, follow = false, retryIntervalMs = 1000, exitCode = EXIT_CODES.RESOURCE_NOT_FOUND, } = options;
|
|
32
|
-
const
|
|
33
|
-
|
|
35
|
+
const sessionGone = exitCode === EXIT_CODES.RESOURCE_NOT_FOUND;
|
|
36
|
+
const exits = !follow || !followState.connected || sessionGone;
|
|
37
|
+
const message = follow && followState.connected && sessionGone ? followedSessionEndedMessage() : error;
|
|
38
|
+
if (exits || json || !followState.lossReported) {
|
|
34
39
|
if (json) {
|
|
35
40
|
const suggestion = exits ? sessionUnavailableSuggestion(exitCode) : undefined;
|
|
36
|
-
|
|
41
|
+
const envelope = OutputBuilder.buildJsonError(message, {
|
|
42
|
+
exitCode,
|
|
43
|
+
...(suggestion && { suggestion }),
|
|
44
|
+
});
|
|
45
|
+
console.log(follow ? JSON.stringify(envelope) : stringifyEnvelope(envelope));
|
|
37
46
|
}
|
|
38
47
|
else {
|
|
39
|
-
console.error(genericError(
|
|
48
|
+
console.error(genericError(message));
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
if (exits)
|
|
43
52
|
return { shouldExit: true, exitCode };
|
|
44
53
|
if (!followState.lossReported) {
|
|
45
54
|
const retryMessage = retryIntervalMs >= 1000 ? `${retryIntervalMs / 1000}s` : `${retryIntervalMs}ms`;
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
if (!json) {
|
|
56
|
+
console.error(connectionLostRetryMessage(new Date().toISOString(), retryMessage));
|
|
57
|
+
console.error(connectionLostStopHintMessage());
|
|
58
|
+
}
|
|
48
59
|
followState.lossReported = true;
|
|
49
60
|
}
|
|
50
61
|
return { shouldExit: false };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { PeekSection } from '../../ipc/protocol/commands.js';
|
|
5
5
|
import type { BdgOutput, ConsoleMessage, NetworkRequest } from '../../types.js';
|
|
6
|
+
import type { NetworkEvictionCounts } from '../../ui/messages/networkMessages.js';
|
|
6
7
|
export type FetchSuccess<T> = {
|
|
7
8
|
success: true;
|
|
8
9
|
data: T;
|
|
@@ -47,18 +48,27 @@ export declare function fetchPreviewData(query?: PreviewQuery): Promise<FetchRes
|
|
|
47
48
|
* Fetch all captured network requests from daemon.
|
|
48
49
|
*
|
|
49
50
|
* @param withHeaders - Include request/response headers (needed by header filters)
|
|
50
|
-
* @returns Requests
|
|
51
|
+
* @returns Requests, when the page crashed (while it is not loaded again)
|
|
52
|
+
* and what the session let go at its capture limits, or a fetch error
|
|
51
53
|
*/
|
|
52
|
-
export declare function fetchNetworkRequests(withHeaders?: boolean): Promise<FetchResult<
|
|
54
|
+
export declare function fetchNetworkRequests(withHeaders?: boolean): Promise<FetchResult<{
|
|
55
|
+
requests: NetworkRequest[];
|
|
56
|
+
pageCrashedAt: number | undefined;
|
|
57
|
+
evictions: NetworkEvictionCounts;
|
|
58
|
+
}>>;
|
|
53
59
|
/**
|
|
54
60
|
* Fetch all console messages from daemon.
|
|
55
61
|
*
|
|
56
|
-
* @returns Messages (with their session-wide index)
|
|
57
|
-
* the page currently loaded
|
|
62
|
+
* @returns Messages (with their session-wide index), the navigation id of
|
|
63
|
+
* the page currently loaded, how many of the oldest messages the session
|
|
64
|
+
* dropped at its limit and when the page crashed (while it is not loaded
|
|
65
|
+
* again)
|
|
58
66
|
*/
|
|
59
67
|
export declare function fetchConsoleMessages(): Promise<FetchResult<{
|
|
60
68
|
messages: ConsoleMessage[];
|
|
61
69
|
currentNavigationId: number | undefined;
|
|
70
|
+
dropped: number;
|
|
71
|
+
pageCrashedAt: number | undefined;
|
|
62
72
|
}>>;
|
|
63
73
|
interface ErrorResult {
|
|
64
74
|
success: false;
|
|
@@ -90,19 +90,33 @@ export async function fetchPreviewData(query = {}) {
|
|
|
90
90
|
* Fetch all captured network requests from daemon.
|
|
91
91
|
*
|
|
92
92
|
* @param withHeaders - Include request/response headers (needed by header filters)
|
|
93
|
-
* @returns Requests
|
|
93
|
+
* @returns Requests, when the page crashed (while it is not loaded again)
|
|
94
|
+
* and what the session let go at its capture limits, or a fetch error
|
|
94
95
|
*/
|
|
95
96
|
export async function fetchNetworkRequests(withHeaders = false) {
|
|
96
97
|
const result = await fetchPreviewData({ lastN: 0, only: 'network', withHeaders });
|
|
97
98
|
if (!result.success)
|
|
98
99
|
return result;
|
|
99
|
-
|
|
100
|
+
const { totals, pageCrashedAt } = result.data.output;
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
data: {
|
|
104
|
+
requests: result.data.network,
|
|
105
|
+
pageCrashedAt,
|
|
106
|
+
evictions: {
|
|
107
|
+
requestsDropped: totals?.networkDropped ?? 0,
|
|
108
|
+
bodiesEvicted: totals?.networkBodiesEvicted ?? 0,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
100
112
|
}
|
|
101
113
|
/**
|
|
102
114
|
* Fetch all console messages from daemon.
|
|
103
115
|
*
|
|
104
|
-
* @returns Messages (with their session-wide index)
|
|
105
|
-
* the page currently loaded
|
|
116
|
+
* @returns Messages (with their session-wide index), the navigation id of
|
|
117
|
+
* the page currently loaded, how many of the oldest messages the session
|
|
118
|
+
* dropped at its limit and when the page crashed (while it is not loaded
|
|
119
|
+
* again)
|
|
106
120
|
*/
|
|
107
121
|
export async function fetchConsoleMessages() {
|
|
108
122
|
const result = await fetchPreviewData({ lastN: 0, only: 'console' });
|
|
@@ -113,6 +127,8 @@ export async function fetchConsoleMessages() {
|
|
|
113
127
|
data: {
|
|
114
128
|
messages: result.data.console,
|
|
115
129
|
currentNavigationId: result.data.output.currentNavigationId,
|
|
130
|
+
dropped: result.data.output.totals?.consoleDropped ?? 0,
|
|
131
|
+
pageCrashedAt: result.data.output.pageCrashedAt,
|
|
116
132
|
},
|
|
117
133
|
};
|
|
118
134
|
}
|
|
@@ -23,6 +23,14 @@ export declare function followFetchFailure(failure: {
|
|
|
23
23
|
json?: boolean | undefined;
|
|
24
24
|
retryIntervalMs: number;
|
|
25
25
|
}): FollowPoll;
|
|
26
|
+
/**
|
|
27
|
+
* Report each page crash of a stream once: a page loaded again that crashes
|
|
28
|
+
* again is a new crash.
|
|
29
|
+
*
|
|
30
|
+
* @returns Function taking the crash time a refresh fetched, returning it
|
|
31
|
+
* when that crash was not reported yet
|
|
32
|
+
*/
|
|
33
|
+
export declare function newPageCrashes(): (crashedAt: number | undefined) => number | undefined;
|
|
26
34
|
/**
|
|
27
35
|
* Options for configuring follow mode behavior.
|
|
28
36
|
*/
|
|
@@ -42,7 +50,7 @@ export interface FollowModeOptions {
|
|
|
42
50
|
* - Initial display of start message
|
|
43
51
|
* - First refresh call (awaited)
|
|
44
52
|
* - Periodic interval-based refresh
|
|
45
|
-
* - SIGINT
|
|
53
|
+
* - SIGINT/SIGTERM handlers that stop with 130/143, as shells expect
|
|
46
54
|
* - Stopping with the exit code a refresh returns (e.g. the session is gone)
|
|
47
55
|
*
|
|
48
56
|
* @param refreshFn - Async function to call on each refresh cycle
|
|
@@ -27,6 +27,22 @@ export function followFetchFailure(failure, options) {
|
|
|
27
27
|
? { exitCode: result.exitCode ?? EXIT_CODES.RESOURCE_NOT_FOUND }
|
|
28
28
|
: undefined;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Report each page crash of a stream once: a page loaded again that crashes
|
|
32
|
+
* again is a new crash.
|
|
33
|
+
*
|
|
34
|
+
* @returns Function taking the crash time a refresh fetched, returning it
|
|
35
|
+
* when that crash was not reported yet
|
|
36
|
+
*/
|
|
37
|
+
export function newPageCrashes() {
|
|
38
|
+
let reported;
|
|
39
|
+
return (crashedAt) => {
|
|
40
|
+
if (crashedAt === undefined || crashedAt === reported)
|
|
41
|
+
return undefined;
|
|
42
|
+
reported = crashedAt;
|
|
43
|
+
return crashedAt;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
30
46
|
/**
|
|
31
47
|
* Sets up follow mode with periodic refresh and graceful shutdown.
|
|
32
48
|
*
|
|
@@ -35,7 +51,7 @@ export function followFetchFailure(failure, options) {
|
|
|
35
51
|
* - Initial display of start message
|
|
36
52
|
* - First refresh call (awaited)
|
|
37
53
|
* - Periodic interval-based refresh
|
|
38
|
-
* - SIGINT
|
|
54
|
+
* - SIGINT/SIGTERM handlers that stop with 130/143, as shells expect
|
|
39
55
|
* - Stopping with the exit code a refresh returns (e.g. the session is gone)
|
|
40
56
|
*
|
|
41
57
|
* @param refreshFn - Async function to call on each refresh cycle
|
|
@@ -71,10 +87,12 @@ export async function setupFollowMode(refreshFn, options) {
|
|
|
71
87
|
console.error(genericError(getErrorMessage(error)));
|
|
72
88
|
});
|
|
73
89
|
}, intervalMs);
|
|
74
|
-
|
|
90
|
+
const stop = (exitCode) => {
|
|
75
91
|
clearInterval(intervalId);
|
|
76
92
|
console.error(stopMessage());
|
|
77
|
-
process.exit(
|
|
78
|
-
}
|
|
93
|
+
process.exit(exitCode);
|
|
94
|
+
};
|
|
95
|
+
process.on('SIGINT', () => stop(EXIT_CODES.INTERRUPTED));
|
|
96
|
+
process.on('SIGTERM', () => stop(EXIT_CODES.TERMINATED));
|
|
79
97
|
}
|
|
80
98
|
//# sourceMappingURL=followMode.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CommandError } from '../../errors/index.js';
|
|
2
2
|
import { genericError } from '../../errors/messages.js';
|
|
3
|
-
import { OutputBuilder } from '../../ui/OutputBuilder.js';
|
|
3
|
+
import { OutputBuilder, stringifyEnvelope } from '../../ui/OutputBuilder.js';
|
|
4
4
|
import { escapeControlChars } from '../../ui/formatting.js';
|
|
5
5
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
6
6
|
/**
|
|
@@ -24,7 +24,7 @@ export function handleValidationError(error, json) {
|
|
|
24
24
|
if (error.metadata.suggestion) {
|
|
25
25
|
errorOptions.suggestion = error.metadata.suggestion;
|
|
26
26
|
}
|
|
27
|
-
console.log(
|
|
27
|
+
console.log(stringifyEnvelope(OutputBuilder.buildJsonError(error.message, errorOptions)));
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
console.error(genericError(error.message));
|
|
@@ -38,7 +38,7 @@ export function handleValidationError(error, json) {
|
|
|
38
38
|
const envelope = OutputBuilder.buildJsonError(message, {
|
|
39
39
|
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
40
40
|
});
|
|
41
|
-
console.log(
|
|
41
|
+
console.log(stringifyEnvelope(envelope));
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
44
|
console.error(genericError(message));
|
|
@@ -142,10 +142,13 @@ export type DetailsCommandOptions = BaseOptions & {
|
|
|
142
142
|
id: string;
|
|
143
143
|
};
|
|
144
144
|
/** Options for DOM query command */
|
|
145
|
-
export type DomQueryCommandOptions = BaseOptions
|
|
145
|
+
export type DomQueryCommandOptions = BaseOptions & {
|
|
146
|
+
/** Matches listed (0 = all); default 50, or 100 with --json */
|
|
147
|
+
limit?: number;
|
|
148
|
+
};
|
|
146
149
|
/** Options for DOM get command */
|
|
147
150
|
export type DomGetCommandOptions = BaseOptions & RawOptions & SelectionOptions & {
|
|
148
|
-
/** All of the element's text
|
|
151
|
+
/** All of the element's text (semantic output) or HTML (`--raw`), instead of its start */
|
|
149
152
|
full?: boolean;
|
|
150
153
|
/** Which match of the selector (0-based); `--nth` is its alias */
|
|
151
154
|
index?: number;
|
|
@@ -156,6 +159,8 @@ export type DomScreenshotCommandOptions = BaseOptions & ScreenshotOptions;
|
|
|
156
159
|
export interface DomEvalCommandOptions extends BaseOptions {
|
|
157
160
|
/** Iframe to evaluate in: index, name/id attribute, or part of the name, id or URL */
|
|
158
161
|
frame?: string;
|
|
162
|
+
/** The whole value instead of its first 20000 characters */
|
|
163
|
+
full?: boolean;
|
|
159
164
|
}
|
|
160
165
|
/** Options for DOM frames command */
|
|
161
166
|
export type DomFramesCommandOptions = BaseOptions;
|
|
@@ -273,7 +278,12 @@ export interface WaitCommandOptions extends BaseOptions {
|
|
|
273
278
|
timeout: number;
|
|
274
279
|
}
|
|
275
280
|
/** Options for A11y tree command */
|
|
276
|
-
export
|
|
281
|
+
export interface A11yTreeCommandOptions extends BaseOptions {
|
|
282
|
+
/** Nodes to list (0 = all); default 50, also with --json */
|
|
283
|
+
limit?: number;
|
|
284
|
+
/** Levels below the root to list (0 = root only) */
|
|
285
|
+
depth?: number;
|
|
286
|
+
}
|
|
277
287
|
/** Options for A11y query command */
|
|
278
288
|
export interface A11yQueryCommandOptions extends BaseOptions {
|
|
279
289
|
/** Matches to list (0 = all) */
|
|
@@ -351,6 +361,8 @@ export interface ConsoleCommandOptions extends BaseOptions {
|
|
|
351
361
|
history?: boolean;
|
|
352
362
|
/** Filter by message level (error, warning, info, debug) */
|
|
353
363
|
level?: ConsoleLevel;
|
|
364
|
+
/** Message texts whole instead of cut */
|
|
365
|
+
full?: boolean;
|
|
354
366
|
}
|
|
355
367
|
/**
|
|
356
368
|
* Options for preview display.
|
|
@@ -377,6 +389,8 @@ export interface PeekCommandOptions extends BaseOptions, PreviewDisplayOptions {
|
|
|
377
389
|
type?: string;
|
|
378
390
|
/** Refresh interval of --follow in ms (string from CLI, default: 1000) */
|
|
379
391
|
interval?: string;
|
|
392
|
+
/** Console message texts whole instead of cut */
|
|
393
|
+
full?: boolean;
|
|
380
394
|
}
|
|
381
395
|
/**
|
|
382
396
|
* Options for tail command.
|
|
@@ -7,6 +7,7 @@ import * as path from 'path';
|
|
|
7
7
|
import { CommandError } from '../../errors/index.js';
|
|
8
8
|
import { emptyOutputPathError, outputFileError } from '../../errors/messages.js';
|
|
9
9
|
import { AtomicFileWriter } from '../../utils/atomicFile.js';
|
|
10
|
+
import { makeDirectory } from '../../utils/directories.js';
|
|
10
11
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
11
12
|
/** What went wrong with a path, by error code */
|
|
12
13
|
const PATH_PROBLEMS = {
|
|
@@ -19,6 +20,10 @@ const PATH_PROBLEMS = {
|
|
|
19
20
|
ENAMETOOLONG: { reason: 'the name is too long', exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
20
21
|
ENOENT: { reason: 'the directory cannot be created', exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
21
22
|
ENOSPC: { reason: 'no space left on the device', exitCode: EXIT_CODES.SESSION_FILE_ERROR },
|
|
23
|
+
EPSEUDOFS: {
|
|
24
|
+
reason: 'it is on a pseudo-filesystem (/proc, /sys)',
|
|
25
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
26
|
+
},
|
|
22
27
|
};
|
|
23
28
|
/**
|
|
24
29
|
* A file-system error as a user-facing error about the given path.
|
|
@@ -64,7 +69,7 @@ export async function writeOutputFile(filePath, data, extension) {
|
|
|
64
69
|
assertFilePath(filePath, extension);
|
|
65
70
|
const absolutePath = path.resolve(filePath);
|
|
66
71
|
try {
|
|
67
|
-
|
|
72
|
+
makeDirectory(path.dirname(absolutePath));
|
|
68
73
|
if (typeof data === 'string')
|
|
69
74
|
await AtomicFileWriter.writeAsync(absolutePath, data);
|
|
70
75
|
else
|
|
@@ -14,7 +14,7 @@ import { IPCErrorCode, } from '../../ipc/index.js';
|
|
|
14
14
|
import { IPCTimeoutError } from '../../ipc/transport/index.js';
|
|
15
15
|
import { isConnectionError } from '../../ipc/utils/errors.js';
|
|
16
16
|
import { getSessionName } from '../../session/paths.js';
|
|
17
|
-
import { OutputBuilder, buildSuccessResponse } from '../../ui/OutputBuilder.js';
|
|
17
|
+
import { OutputBuilder, buildSuccessResponse, stringifyEnvelope } from '../../ui/OutputBuilder.js';
|
|
18
18
|
import { escapeControlChars, joinLines } from '../../ui/formatting.js';
|
|
19
19
|
import { createLogger } from '../../ui/logging/index.js';
|
|
20
20
|
import { daemonStillExitingHint, daemonStillExitingSuggestion, startNotices, } from '../../ui/messages/session.js';
|
|
@@ -318,7 +318,7 @@ function reportStartOutcome(outcome, options) {
|
|
|
318
318
|
exitCode: outcome.exitCode,
|
|
319
319
|
...outcome.details,
|
|
320
320
|
});
|
|
321
|
-
console.log(
|
|
321
|
+
console.log(stringifyEnvelope(envelope));
|
|
322
322
|
}
|
|
323
323
|
else {
|
|
324
324
|
console.error(escapeControlChars(outcome.human));
|
|
@@ -340,7 +340,7 @@ function reportStartOutcome(outcome, options) {
|
|
|
340
340
|
daemonPid: data.daemonPid,
|
|
341
341
|
...(autoStopAt && { autoStopAt: autoStopAt.toISOString() }),
|
|
342
342
|
};
|
|
343
|
-
console.log(
|
|
343
|
+
console.log(stringifyEnvelope(buildSuccessResponse(result)));
|
|
344
344
|
}
|
|
345
345
|
else {
|
|
346
346
|
const page = {
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -84,15 +84,17 @@ export declare function parseViewport(value: string): ViewportSize;
|
|
|
84
84
|
*/
|
|
85
85
|
export declare function parseColorScheme(value: string): ColorScheme;
|
|
86
86
|
/**
|
|
87
|
-
* Reject a
|
|
88
|
-
* `
|
|
89
|
-
*
|
|
87
|
+
* Reject a mistyped command before Commander reads it as a start URL with
|
|
88
|
+
* extra arguments (`too many arguments`): a subcommand typed without its
|
|
89
|
+
* group (`bdg query x` for `bdg dom query x`), or a word close to a command
|
|
90
|
+
* followed by more words (`bdg netwrk list`). A single word is left to the
|
|
91
|
+
* start command, which checks it the same way.
|
|
90
92
|
*
|
|
91
93
|
* @param program - Root command with all commands registered
|
|
92
94
|
* @param argv - Process arguments
|
|
93
|
-
* @throws CommandError (81) naming the
|
|
95
|
+
* @throws CommandError (81) naming the command meant
|
|
94
96
|
*/
|
|
95
|
-
export declare function
|
|
97
|
+
export declare function assertNotMistypedCommand(program: Command, argv: string[]): void;
|
|
96
98
|
/**
|
|
97
99
|
* Register the start command
|
|
98
100
|
*
|
package/dist/commands/start.js
CHANGED
|
@@ -9,19 +9,12 @@ import { PORT_OPTION_DESCRIPTION } from '../constants.js';
|
|
|
9
9
|
import { CommandError } from '../errors/index.js';
|
|
10
10
|
import { chromeWsUrlConflictError, externalChromeUnreachableError, invalidChromeFlagError, notDevToolsEndpointError, invalidColorSchemeError, invalidUserDataDirError, invalidViewportError, missingStartUrlError, unknownCommandError, } from '../errors/messages.js';
|
|
11
11
|
import { startCommandHelpMessage } from '../ui/messages/commands.js';
|
|
12
|
+
import { directoryProblem } from '../utils/directories.js';
|
|
13
|
+
import { hasDisplay } from '../utils/display.js';
|
|
12
14
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
13
15
|
import { probeDevToolsEndpoint } from '../utils/http.js';
|
|
14
16
|
import { findSimilar } from '../utils/suggestions.js';
|
|
15
17
|
import { devToolsHttpEndpoint, validateChromeWsUrl, validateUrl } from '../utils/url.js';
|
|
16
|
-
/**
|
|
17
|
-
* Check if a display server (X11 or Wayland) is available.
|
|
18
|
-
* Used to determine default headless mode.
|
|
19
|
-
*/
|
|
20
|
-
function hasDisplay() {
|
|
21
|
-
const display = process.env['DISPLAY'];
|
|
22
|
-
const wayland = process.env['WAYLAND_DISPLAY'];
|
|
23
|
-
return (display !== undefined && display !== '') || (wayland !== undefined && wayland !== '');
|
|
24
|
-
}
|
|
25
18
|
/**
|
|
26
19
|
* Expand a leading `~/` in a path to the user's home directory.
|
|
27
20
|
* Chrome itself does not expand `~`, so bdg normalizes it for users.
|
|
@@ -74,7 +67,7 @@ export function applyCollectorOptions(command) {
|
|
|
74
67
|
.option('-a, --all', 'Include all data: no tracking/analytics filtering, and capture every response body (incl. binary)', false)
|
|
75
68
|
.option('-m, --max-body-size <megabytes>', 'Maximum response body size in MB', '5')
|
|
76
69
|
.addOption(new Option('--compact', 'No effect; kept for compatibility').hideHelp())
|
|
77
|
-
.option('--headless', 'Run Chrome without a window (default
|
|
70
|
+
.option('--headless', 'Run Chrome without a window (default without a display: Linux without DISPLAY or WAYLAND_DISPLAY, macOS over SSH or in CI)', defaultHeadless)
|
|
78
71
|
.option('--no-headless', 'Show browser window')
|
|
79
72
|
.option('--chrome-ws-url <url>', 'Connect to an existing Chrome: its DevTools port (9222, host:port, http://host:port), or a WebSocket URL: browser (ws://host:port/devtools/browser/<id>, uses the first tab) or page (.../devtools/page/<id>)')
|
|
80
73
|
.option('-q, --quiet', 'Quiet mode - minimal output for AI agents', false)
|
|
@@ -180,6 +173,8 @@ function buildSessionOptions(options) {
|
|
|
180
173
|
}
|
|
181
174
|
/** Telemetry collected by every session. */
|
|
182
175
|
const SESSION_TELEMETRY = ['dom', 'network', 'console'];
|
|
176
|
+
/** A word that cannot be a URL: no dot, colon or slash */
|
|
177
|
+
const BARE_WORD = /^[a-z][a-z0-9_-]*$/i;
|
|
183
178
|
/** Flags users type as commands (`bdg version`). */
|
|
184
179
|
const FLAG_WORDS = { version: '--version', help: '--help' };
|
|
185
180
|
/**
|
|
@@ -194,29 +189,59 @@ const FLAG_WORDS = { version: '--version', help: '--help' };
|
|
|
194
189
|
* @throws CommandError (81) for a bare word other than `localhost`
|
|
195
190
|
*/
|
|
196
191
|
function assertNotCommandTypo(arg, commandNames) {
|
|
197
|
-
if (
|
|
192
|
+
if (!BARE_WORD.test(arg) || arg.toLowerCase() === 'localhost')
|
|
198
193
|
return;
|
|
199
194
|
const flag = FLAG_WORDS[arg.toLowerCase()];
|
|
200
195
|
const err = unknownCommandError(arg, flag ? [flag] : findSimilar(arg, commandNames));
|
|
201
196
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
202
197
|
}
|
|
203
198
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
199
|
+
* The words of the command line that are not options or option values of
|
|
200
|
+
* the root command (`bdg --session a netwrk list` gives `netwrk`, `list`).
|
|
201
|
+
*
|
|
202
|
+
* @param program - Root command
|
|
203
|
+
* @param args - Arguments after the executable and script
|
|
204
|
+
* @returns Positional words, in order
|
|
205
|
+
*/
|
|
206
|
+
function positionalWords(program, args) {
|
|
207
|
+
const words = [];
|
|
208
|
+
for (let i = 0; i < args.length; i++) {
|
|
209
|
+
const arg = args[i] ?? '';
|
|
210
|
+
if (!arg.startsWith('-')) {
|
|
211
|
+
words.push(arg);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const option = program.options.find((o) => o.long === arg || o.short === arg);
|
|
215
|
+
if (option && (option.required || option.optional))
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
return words;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Reject a mistyped command before Commander reads it as a start URL with
|
|
222
|
+
* extra arguments (`too many arguments`): a subcommand typed without its
|
|
223
|
+
* group (`bdg query x` for `bdg dom query x`), or a word close to a command
|
|
224
|
+
* followed by more words (`bdg netwrk list`). A single word is left to the
|
|
225
|
+
* start command, which checks it the same way.
|
|
207
226
|
*
|
|
208
227
|
* @param program - Root command with all commands registered
|
|
209
228
|
* @param argv - Process arguments
|
|
210
|
-
* @throws CommandError (81) naming the
|
|
229
|
+
* @throws CommandError (81) naming the command meant
|
|
211
230
|
*/
|
|
212
|
-
export function
|
|
213
|
-
const [first] = argv.slice(2)
|
|
214
|
-
|
|
231
|
+
export function assertNotMistypedCommand(program, argv) {
|
|
232
|
+
const [first, ...rest] = positionalWords(program, argv.slice(2));
|
|
233
|
+
const commandNames = program.commands.map((command) => command.name());
|
|
234
|
+
if (first === undefined || commandNames.includes(first))
|
|
215
235
|
return;
|
|
216
236
|
const group = program.commands.find((command) => command.commands.some((sub) => sub.name() === first));
|
|
217
|
-
|
|
237
|
+
const similar = group
|
|
238
|
+
? [`${group.name()} ${first}`]
|
|
239
|
+
: rest.length > 0 && BARE_WORD.test(first)
|
|
240
|
+
? findSimilar(first, commandNames)
|
|
241
|
+
: [];
|
|
242
|
+
if (similar.length === 0)
|
|
218
243
|
return;
|
|
219
|
-
const err = unknownCommandError(first,
|
|
244
|
+
const err = unknownCommandError(first, similar);
|
|
220
245
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
221
246
|
}
|
|
222
247
|
/**
|
|
@@ -247,7 +272,10 @@ function validateStartInput(url, options, program) {
|
|
|
247
272
|
...(process.env['BDG_CHROME_FLAGS']?.split(' ') ?? []),
|
|
248
273
|
...(options.chromeFlags?.split(' ') ?? []),
|
|
249
274
|
]);
|
|
250
|
-
|
|
275
|
+
const sessionOptions = buildSessionOptions(options);
|
|
276
|
+
if (sessionOptions.userDataDir !== undefined)
|
|
277
|
+
assertUsableProfile(sessionOptions.userDataDir);
|
|
278
|
+
return { url, sessionOptions };
|
|
251
279
|
}
|
|
252
280
|
/**
|
|
253
281
|
* Register the start command
|
|
@@ -346,6 +374,22 @@ function assertUserDataDir(value) {
|
|
|
346
374
|
const err = invalidUserDataDirError(value, reason);
|
|
347
375
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
348
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Check that Chrome can create and write its profile directory (given with
|
|
379
|
+
* `-u` or in `--chrome-flags`), before a daemon is spawned: a profile under
|
|
380
|
+
* `/proc` spun the daemon at full CPU and wedged the session.
|
|
381
|
+
*
|
|
382
|
+
* @param dir - Profile directory, `~/` expanded
|
|
383
|
+
* @throws CommandError (81) for a path that cannot hold a directory, (82)
|
|
384
|
+
* when its nearest existing directory is not writable
|
|
385
|
+
*/
|
|
386
|
+
function assertUsableProfile(dir) {
|
|
387
|
+
const problem = directoryProblem(dir);
|
|
388
|
+
if (!problem)
|
|
389
|
+
return;
|
|
390
|
+
const err = invalidUserDataDirError(dir, problem.reason);
|
|
391
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, problem.denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.INVALID_ARGUMENTS);
|
|
392
|
+
}
|
|
349
393
|
/**
|
|
350
394
|
* Options given that only apply to a Chrome bdg launches. `--headless` has a
|
|
351
395
|
* default, so it counts only when given on the command line.
|
package/dist/commands/stop.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Wait until the stopped session's daemon has exited: it removes the
|
|
4
|
+
* session's files on the way out, and a command run right after (`bdg
|
|
5
|
+
* sessions`, a new start) would otherwise still see the session. The wait is
|
|
6
|
+
* bounded, as after a failed start.
|
|
7
|
+
*
|
|
8
|
+
* @param pid - Daemon PID read before the stop, or null when unknown
|
|
9
|
+
* @param waitMs - Milliseconds to wait at most
|
|
10
|
+
* @returns Warning when the daemon still runs after the wait
|
|
11
|
+
*/
|
|
12
|
+
export declare function waitForDaemonExit(pid: number | null, waitMs?: number): Promise<string | undefined>;
|
|
2
13
|
/**
|
|
3
14
|
* Register stop command
|
|
4
15
|
*
|
package/dist/commands/stop.js
CHANGED
|
@@ -3,13 +3,33 @@ import { jsonOption } from './shared/commonOptions.js';
|
|
|
3
3
|
import { stopSession } from '../ipc/client.js';
|
|
4
4
|
import { IPCErrorCode } from '../ipc/index.js';
|
|
5
5
|
import { IPCTimeoutError } from '../ipc/transport/index.js';
|
|
6
|
+
import { readLiveDaemonPid } from '../session/cleanup/staleSession.js';
|
|
6
7
|
import { joinLines } from '../ui/formatting.js';
|
|
7
8
|
import { chromeClosedMessage, orphanedDaemonsCleanedMessage, warningMessage, } from '../ui/messages/commands.js';
|
|
8
|
-
import { sessionStopped, STOP_MESSAGES, stopFailedError } from '../ui/messages/session.js';
|
|
9
|
+
import { daemonStillExitingHint, daemonStillExitingSuggestion, sessionStopped, STOP_MESSAGES, stopFailedError, } from '../ui/messages/session.js';
|
|
9
10
|
import { noActiveSessionMessage, sessionCommand, startSessionSuggestion, } from '../ui/messages/sessionCommand.js';
|
|
11
|
+
import { waitUntil } from '../utils/async.js';
|
|
10
12
|
import { getExitCodeForIPCError, isDaemonNotRunningError } from '../utils/errorMapping.js';
|
|
11
13
|
import { getErrorMessage } from '../utils/errors.js';
|
|
12
14
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
15
|
+
import { isProcessAlive } from '../utils/process.js';
|
|
16
|
+
/** How long `bdg stop` waits for the session's daemon to exit */
|
|
17
|
+
const DAEMON_EXIT_WAIT_MS = 3000;
|
|
18
|
+
/**
|
|
19
|
+
* Wait until the stopped session's daemon has exited: it removes the
|
|
20
|
+
* session's files on the way out, and a command run right after (`bdg
|
|
21
|
+
* sessions`, a new start) would otherwise still see the session. The wait is
|
|
22
|
+
* bounded, as after a failed start.
|
|
23
|
+
*
|
|
24
|
+
* @param pid - Daemon PID read before the stop, or null when unknown
|
|
25
|
+
* @param waitMs - Milliseconds to wait at most
|
|
26
|
+
* @returns Warning when the daemon still runs after the wait
|
|
27
|
+
*/
|
|
28
|
+
export async function waitForDaemonExit(pid, waitMs = DAEMON_EXIT_WAIT_MS) {
|
|
29
|
+
if (pid === null || (await waitUntil(() => !isProcessAlive(pid), waitMs)))
|
|
30
|
+
return undefined;
|
|
31
|
+
return `${daemonStillExitingHint(pid, waitMs)}; ${daemonStillExitingSuggestion()}`;
|
|
32
|
+
}
|
|
13
33
|
/**
|
|
14
34
|
* Format stop result for human-readable output.
|
|
15
35
|
*
|
|
@@ -36,8 +56,10 @@ export function registerStopCommand(program) {
|
|
|
36
56
|
.action(async (options) => {
|
|
37
57
|
await runCommand(async () => {
|
|
38
58
|
try {
|
|
59
|
+
const daemonPid = readLiveDaemonPid();
|
|
39
60
|
const response = await stopSession();
|
|
40
61
|
if (response.status === 'ok') {
|
|
62
|
+
const warning = await waitForDaemonExit(daemonPid);
|
|
41
63
|
return {
|
|
42
64
|
success: true,
|
|
43
65
|
data: {
|
|
@@ -48,6 +70,7 @@ export function registerStopCommand(program) {
|
|
|
48
70
|
},
|
|
49
71
|
orphanedDaemonsCount: 0,
|
|
50
72
|
message: response.message ?? STOP_MESSAGES.SUCCESS,
|
|
73
|
+
...(warning && { warnings: [warning] }),
|
|
51
74
|
},
|
|
52
75
|
};
|
|
53
76
|
}
|
package/dist/commands.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { registerCdpCommand } from './commands/cdp.js';
|
|
2
2
|
import { registerCleanupCommand } from './commands/cleanup.js';
|
|
3
3
|
import { registerConsoleCommand } from './commands/console.js';
|
|
4
|
+
import { registerCssCommands } from './commands/css.js';
|
|
4
5
|
import { registerDetailsCommand } from './commands/details.js';
|
|
5
6
|
import { registerFormInteractionCommands } from './commands/dom/formInteraction.js';
|
|
6
7
|
import { registerDomCommands } from './commands/dom/index.js';
|
|
7
8
|
import { registerInstallSkillCommand } from './commands/installSkill.js';
|
|
8
9
|
import { registerNetworkCommands } from './commands/network/index.js';
|
|
9
|
-
import { registerCssCommands } from './commands/css.js';
|
|
10
10
|
import { registerPageCommands } from './commands/page.js';
|
|
11
11
|
import { registerPeekCommand } from './commands/peek.js';
|
|
12
12
|
import { registerSessionsCommand } from './commands/sessions.js';
|
package/dist/connection/cdp.d.ts
CHANGED
|
@@ -181,6 +181,13 @@ export declare class CDPConnection implements CDPEventSource {
|
|
|
181
181
|
* @returns Calculated delay in milliseconds (base delay * 2^attempt, capped at maxDelay)
|
|
182
182
|
*/
|
|
183
183
|
private calculateBackoffDelay;
|
|
184
|
+
/**
|
|
185
|
+
* Fail every command still waiting for an answer, e.g. after the page's
|
|
186
|
+
* renderer crashed: commands it was running are never answered.
|
|
187
|
+
*
|
|
188
|
+
* @param error - Error the waiting commands fail with
|
|
189
|
+
*/
|
|
190
|
+
rejectPending(error: Error): void;
|
|
184
191
|
/**
|
|
185
192
|
* Clear all pending command promises with the given error.
|
|
186
193
|
*
|
package/dist/connection/cdp.js
CHANGED
|
@@ -390,6 +390,15 @@ export class CDPConnection {
|
|
|
390
390
|
calculateBackoffDelay(attempt, maxDelay) {
|
|
391
391
|
return Math.min(this.config.baseRetryDelay * Math.pow(2, attempt), maxDelay);
|
|
392
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Fail every command still waiting for an answer, e.g. after the page's
|
|
395
|
+
* renderer crashed: commands it was running are never answered.
|
|
396
|
+
*
|
|
397
|
+
* @param error - Error the waiting commands fail with
|
|
398
|
+
*/
|
|
399
|
+
rejectPending(error) {
|
|
400
|
+
this.clearPendingMessages(error);
|
|
401
|
+
}
|
|
393
402
|
/**
|
|
394
403
|
* Clear all pending command promises with the given error.
|
|
395
404
|
*
|