browser-debugger-cli 0.11.0 → 0.13.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 +4 -4
- package/README.md +143 -79
- 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 +62 -12
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +42 -11
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- 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.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +169 -49
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +12 -7
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/dom/wait.js +5 -3
- 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 +22 -12
- package/dist/commands/optionBehaviors.js +53 -16
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +9 -2
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +20 -5
- package/dist/commands/start.js +84 -23
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +36 -14
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +19 -53
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +9234 -7222
- package/dist/errors/messages.d.ts +88 -15
- package/dist/errors/messages.js +177 -27
- package/dist/index.js +19322 -13961
- package/dist/ipc/client.d.ts +22 -2
- package/dist/ipc/client.js +34 -5
- package/dist/ipc/protocol/auditTypes.d.ts +135 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +35 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +16 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +9 -2
- package/dist/runtime/dom/actionEffects.js +30 -14
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +37 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +220 -0
- package/dist/runtime/dom/auditScripts.d.ts +113 -0
- package/dist/runtime/dom/auditScripts.js +148 -0
- package/dist/runtime/dom/elementGeometry.d.ts +16 -3
- package/dist/runtime/dom/elementGeometry.js +49 -10
- package/dist/runtime/dom/elementInfo.d.ts +74 -17
- package/dist/runtime/dom/elementInfo.js +187 -34
- 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 +8 -12
- 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/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +92 -28
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +90 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +5 -1
- package/dist/runtime/dom/inspectModel.js +37 -10
- package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
- package/dist/runtime/dom/inspectPaintModel.js +182 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +112 -12
- package/dist/runtime/dom/inspectScripts.js +357 -32
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +40 -16
- package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
- package/dist/runtime/dom/reactEventHelpers.js +90 -36
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- 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/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -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.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +115 -0
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +2 -2
- package/dist/ui/formatters/dom.js +10 -8
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +50 -17
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +168 -11
- package/dist/ui/messages/commands.js +245 -18
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +12 -0
- package/dist/ui/messages/preview.js +18 -2
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- 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/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
package/dist/commands/peek.js
CHANGED
|
@@ -8,9 +8,12 @@ import { fetchPreviewOutput, createErrorResult, } from './shared/dataFetcher.js'
|
|
|
8
8
|
import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
|
|
9
9
|
import { handleValidationError } from './shared/handleValidationError.js';
|
|
10
10
|
import { MAX_LAST_ITEMS, positiveIntRule, resourceTypeRule } from './shared/validation.js';
|
|
11
|
+
import { CommandError } from '../errors/index.js';
|
|
12
|
+
import { intervalWithoutFollowError } from '../errors/messages.js';
|
|
11
13
|
import { filterByResourceType } from '../telemetry/filters.js';
|
|
12
14
|
import { buildPreviewJsonData, formatPreview, } from '../ui/formatters/preview.js';
|
|
13
15
|
import { followingPreviewMessage, stoppedFollowingPreviewMessage } from '../ui/messages/preview.js';
|
|
16
|
+
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
14
17
|
function parseOptions(options) {
|
|
15
18
|
const lastN = positiveIntRule({
|
|
16
19
|
name: '--last',
|
|
@@ -20,7 +23,17 @@ function parseOptions(options) {
|
|
|
20
23
|
allowZeroForAll: true,
|
|
21
24
|
}).validate(options.last);
|
|
22
25
|
const resourceTypes = resourceTypeRule().validate(options.type);
|
|
23
|
-
|
|
26
|
+
if (options.interval !== undefined && !options.follow) {
|
|
27
|
+
const err = intervalWithoutFollowError();
|
|
28
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
29
|
+
}
|
|
30
|
+
const interval = positiveIntRule({
|
|
31
|
+
name: '--interval',
|
|
32
|
+
min: 100,
|
|
33
|
+
max: 60000,
|
|
34
|
+
default: 1000,
|
|
35
|
+
}).validate(options.interval);
|
|
36
|
+
return { lastN, resourceTypes, interval };
|
|
24
37
|
}
|
|
25
38
|
/**
|
|
26
39
|
* Data section selected by `--network` / `--console`, if any.
|
|
@@ -75,11 +88,12 @@ function createPreviewOptions(base, resourceTypes, unfilteredCount) {
|
|
|
75
88
|
}
|
|
76
89
|
return options;
|
|
77
90
|
}
|
|
78
|
-
async function runFollowMode(options,
|
|
91
|
+
async function runFollowMode(options, parsed, baseOptions) {
|
|
92
|
+
const { lastN, resourceTypes, interval } = parsed;
|
|
79
93
|
const showPreview = async () => {
|
|
80
94
|
const result = await fetchAndFilterPreview(lastN, resourceTypes, peekSection(options));
|
|
81
95
|
if (!result.success) {
|
|
82
|
-
return followFetchFailure(result, { json: options.json, retryIntervalMs:
|
|
96
|
+
return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
|
|
83
97
|
}
|
|
84
98
|
noteFollowConnected();
|
|
85
99
|
if (!options.json)
|
|
@@ -91,9 +105,51 @@ async function runFollowMode(options, lastN, resourceTypes, baseOptions) {
|
|
|
91
105
|
await setupFollowMode(showPreview, {
|
|
92
106
|
startMessage: followingPreviewMessage,
|
|
93
107
|
stopMessage: stoppedFollowingPreviewMessage,
|
|
94
|
-
intervalMs:
|
|
108
|
+
intervalMs: interval,
|
|
95
109
|
});
|
|
96
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Parse the options, reporting an invalid one and exiting.
|
|
113
|
+
*
|
|
114
|
+
* @param options - Peek options
|
|
115
|
+
* @returns Parsed options
|
|
116
|
+
*/
|
|
117
|
+
function parsePeekOptions(options) {
|
|
118
|
+
try {
|
|
119
|
+
return parseOptions(options);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
handleValidationError(error, options.json ?? false);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* How the preview is shown.
|
|
127
|
+
*
|
|
128
|
+
* @param options - Peek options
|
|
129
|
+
* @param lastN - Items to show
|
|
130
|
+
* @returns Preview options
|
|
131
|
+
*/
|
|
132
|
+
function previewDisplayOptions(options, lastN) {
|
|
133
|
+
return {
|
|
134
|
+
json: options.json,
|
|
135
|
+
network: options.network,
|
|
136
|
+
console: options.console,
|
|
137
|
+
last: lastN,
|
|
138
|
+
verbose: options.verbose,
|
|
139
|
+
follow: options.follow,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Watch the session data (`peek --follow`, and the deprecated `tail`).
|
|
144
|
+
*
|
|
145
|
+
* @param options - Peek options (follow implied)
|
|
146
|
+
*/
|
|
147
|
+
export async function followPreview(options) {
|
|
148
|
+
const following = { ...options, follow: true };
|
|
149
|
+
showBothSectionsWhenBothRequested(following);
|
|
150
|
+
const parsed = parsePeekOptions(following);
|
|
151
|
+
await runFollowMode(following, parsed, previewDisplayOptions(following, parsed.lastN));
|
|
152
|
+
}
|
|
97
153
|
export function registerPeekCommand(program) {
|
|
98
154
|
program
|
|
99
155
|
.command('peek')
|
|
@@ -103,6 +159,7 @@ export function registerPeekCommand(program) {
|
|
|
103
159
|
.option('-n, --network', 'Show only network requests', false)
|
|
104
160
|
.option('-c, --console', 'Show only console messages', false)
|
|
105
161
|
.option('-f, --follow', 'Watch for updates (like tail -f)', false)
|
|
162
|
+
.option('--interval <ms>', 'Refresh interval of --follow in ms, 100-60000 (default: 1000)')
|
|
106
163
|
.option('--last <count>', 'Show last N items, 0 for all', '10')
|
|
107
164
|
.option('--type <types>', 'Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)')
|
|
108
165
|
.action(async (options) => {
|
|
@@ -110,26 +167,11 @@ export function registerPeekCommand(program) {
|
|
|
110
167
|
if (options.network && !options.json) {
|
|
111
168
|
console.error('Note: "bdg peek --network" is deprecated. Use "bdg network list" for enhanced filtering.');
|
|
112
169
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const parsed = parseOptions(options);
|
|
117
|
-
lastN = parsed.lastN;
|
|
118
|
-
resourceTypes = parsed.resourceTypes;
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
handleValidationError(error, options.json ?? false);
|
|
122
|
-
}
|
|
123
|
-
const baseOptions = {
|
|
124
|
-
json: options.json,
|
|
125
|
-
network: options.network,
|
|
126
|
-
console: options.console,
|
|
127
|
-
last: lastN,
|
|
128
|
-
verbose: options.verbose,
|
|
129
|
-
follow: options.follow,
|
|
130
|
-
};
|
|
170
|
+
const parsed = parsePeekOptions(options);
|
|
171
|
+
const { lastN, resourceTypes } = parsed;
|
|
172
|
+
const baseOptions = previewDisplayOptions(options, lastN);
|
|
131
173
|
if (options.follow) {
|
|
132
|
-
await runFollowMode(options,
|
|
174
|
+
await runFollowMode(options, parsed, baseOptions);
|
|
133
175
|
return;
|
|
134
176
|
}
|
|
135
177
|
await runCommand(async () => {
|
|
@@ -28,8 +28,11 @@ export declare function noteFollowConnected(): void;
|
|
|
28
28
|
* Handle daemon connection errors with consistent formatting and behavior.
|
|
29
29
|
*
|
|
30
30
|
* Outside follow mode the command exits. In follow mode, a session that never
|
|
31
|
-
* answered exits too (there is nothing to follow, exit 83)
|
|
32
|
-
*
|
|
31
|
+
* answered exits too (there is nothing to follow, exit 83), and so does one
|
|
32
|
+
* that ends while followed (no session any more, exit 83), so a follower
|
|
33
|
+
* running in the background finds out. Other failures (a busy page, a
|
|
34
|
+
* timeout) are retried: reported once in text, and on every failed refresh
|
|
35
|
+
* in JSON, one object per line.
|
|
33
36
|
*
|
|
34
37
|
* @param error - Error message to display
|
|
35
38
|
* @param options - Error handling options
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { sessionUnavailableSuggestion } from '../../errors/messages.js';
|
|
5
5
|
import { genericError } from '../../errors/messages.js';
|
|
6
6
|
import { OutputBuilder } from '../../ui/OutputBuilder.js';
|
|
7
|
-
import { connectionLostRetryMessage, connectionLostStopHintMessage, } from '../../ui/messages/preview.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) : JSON.stringify(envelope, null, 2));
|
|
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 };
|
|
@@ -47,18 +47,26 @@ export declare function fetchPreviewData(query?: PreviewQuery): Promise<FetchRes
|
|
|
47
47
|
* Fetch all captured network requests from daemon.
|
|
48
48
|
*
|
|
49
49
|
* @param withHeaders - Include request/response headers (needed by header filters)
|
|
50
|
-
* @returns Requests
|
|
50
|
+
* @returns Requests, and when the page crashed (while it is not loaded
|
|
51
|
+
* again), or a fetch error
|
|
51
52
|
*/
|
|
52
|
-
export declare function fetchNetworkRequests(withHeaders?: boolean): Promise<FetchResult<
|
|
53
|
+
export declare function fetchNetworkRequests(withHeaders?: boolean): Promise<FetchResult<{
|
|
54
|
+
requests: NetworkRequest[];
|
|
55
|
+
pageCrashedAt: number | undefined;
|
|
56
|
+
}>>;
|
|
53
57
|
/**
|
|
54
58
|
* Fetch all console messages from daemon.
|
|
55
59
|
*
|
|
56
|
-
* @returns Messages (with their session-wide index)
|
|
57
|
-
* the page currently loaded
|
|
60
|
+
* @returns Messages (with their session-wide index), the navigation id of
|
|
61
|
+
* the page currently loaded, how many of the oldest messages the session
|
|
62
|
+
* dropped at its limit and when the page crashed (while it is not loaded
|
|
63
|
+
* again)
|
|
58
64
|
*/
|
|
59
65
|
export declare function fetchConsoleMessages(): Promise<FetchResult<{
|
|
60
66
|
messages: ConsoleMessage[];
|
|
61
67
|
currentNavigationId: number | undefined;
|
|
68
|
+
dropped: number;
|
|
69
|
+
pageCrashedAt: number | undefined;
|
|
62
70
|
}>>;
|
|
63
71
|
interface ErrorResult {
|
|
64
72
|
success: false;
|
|
@@ -90,19 +90,25 @@ 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, and when the page crashed (while it is not loaded
|
|
94
|
+
* again), 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
|
-
return {
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
data: { requests: result.data.network, pageCrashedAt: result.data.output.pageCrashedAt },
|
|
103
|
+
};
|
|
100
104
|
}
|
|
101
105
|
/**
|
|
102
106
|
* Fetch all console messages from daemon.
|
|
103
107
|
*
|
|
104
|
-
* @returns Messages (with their session-wide index)
|
|
105
|
-
* the page currently loaded
|
|
108
|
+
* @returns Messages (with their session-wide index), the navigation id of
|
|
109
|
+
* the page currently loaded, how many of the oldest messages the session
|
|
110
|
+
* dropped at its limit and when the page crashed (while it is not loaded
|
|
111
|
+
* again)
|
|
106
112
|
*/
|
|
107
113
|
export async function fetchConsoleMessages() {
|
|
108
114
|
const result = await fetchPreviewData({ lastN: 0, only: 'console' });
|
|
@@ -113,6 +119,8 @@ export async function fetchConsoleMessages() {
|
|
|
113
119
|
data: {
|
|
114
120
|
messages: result.data.console,
|
|
115
121
|
currentNavigationId: result.data.output.currentNavigationId,
|
|
122
|
+
dropped: result.data.output.totals?.consoleDropped ?? 0,
|
|
123
|
+
pageCrashedAt: result.data.output.pageCrashedAt,
|
|
116
124
|
},
|
|
117
125
|
};
|
|
118
126
|
}
|
|
@@ -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
|
|
@@ -93,8 +93,10 @@ export interface ScreenshotOptions {
|
|
|
93
93
|
fullPage?: boolean;
|
|
94
94
|
/** CSS selector for element capture */
|
|
95
95
|
selector?: string;
|
|
96
|
-
/** Cached element index (0-based) from previous query */
|
|
96
|
+
/** Cached element index (0-based) from previous query, or with `selector`, which of its matches */
|
|
97
97
|
index?: number;
|
|
98
|
+
/** Extra space (CSS px) around an element capture */
|
|
99
|
+
padding?: number;
|
|
98
100
|
/** Continuous capture mode to directory */
|
|
99
101
|
follow?: boolean;
|
|
100
102
|
/** Capture interval in ms for follow mode (string from CLI) */
|
|
@@ -140,7 +142,10 @@ export type DetailsCommandOptions = BaseOptions & {
|
|
|
140
142
|
id: string;
|
|
141
143
|
};
|
|
142
144
|
/** Options for DOM query command */
|
|
143
|
-
export type DomQueryCommandOptions = BaseOptions
|
|
145
|
+
export type DomQueryCommandOptions = BaseOptions & {
|
|
146
|
+
/** Matches listed (0 = all); default 50, or 1000 with --json */
|
|
147
|
+
limit?: number;
|
|
148
|
+
};
|
|
144
149
|
/** Options for DOM get command */
|
|
145
150
|
export type DomGetCommandOptions = BaseOptions & RawOptions & SelectionOptions & {
|
|
146
151
|
/** All of the element's text instead of its first 500 characters (semantic output) */
|
|
@@ -373,6 +378,8 @@ export interface PeekCommandOptions extends BaseOptions, PreviewDisplayOptions {
|
|
|
373
378
|
last?: string;
|
|
374
379
|
/** Filter network requests by resource type (comma-separated) */
|
|
375
380
|
type?: string;
|
|
381
|
+
/** Refresh interval of --follow in ms (string from CLI, default: 1000) */
|
|
382
|
+
interval?: string;
|
|
376
383
|
}
|
|
377
384
|
/**
|
|
378
385
|
* 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
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface CollectorOptions {
|
|
|
26
26
|
chromeFlags?: string;
|
|
27
27
|
/** Viewport size, e.g. `1280x800`. */
|
|
28
28
|
viewport?: string;
|
|
29
|
+
/** Emulate a phone (`--mobile`) */
|
|
30
|
+
mobile?: boolean;
|
|
29
31
|
/** `prefers-color-scheme` to emulate: light or dark. */
|
|
30
32
|
colorScheme?: string;
|
|
31
33
|
}
|
|
@@ -53,6 +55,17 @@ export declare function extractUserDataDirFromFlags(flags: string[]): {
|
|
|
53
55
|
* @returns The modified Command instance with all telemetry options applied
|
|
54
56
|
*/
|
|
55
57
|
export declare function applyCollectorOptions(command: Command): Command;
|
|
58
|
+
/** Viewport of `--mobile` without `--viewport` (a common phone, CSS px) */
|
|
59
|
+
export declare const MOBILE_VIEWPORT: ViewportSize;
|
|
60
|
+
/**
|
|
61
|
+
* The viewport to emulate from `--viewport` and `--mobile`.
|
|
62
|
+
*
|
|
63
|
+
* @param viewport - `--viewport` value
|
|
64
|
+
* @param mobile - `--mobile` was given
|
|
65
|
+
* @returns Viewport (a phone's with `--mobile`), or undefined for neither
|
|
66
|
+
* @throws CommandError (81) for an invalid size
|
|
67
|
+
*/
|
|
68
|
+
export declare function requestedViewport(viewport: string | undefined, mobile: boolean | undefined): ViewportSize | undefined;
|
|
56
69
|
/**
|
|
57
70
|
* Parse a `--viewport` value: width and height in CSS px joined by `x`
|
|
58
71
|
* (`1280x800`; `X`, `×` and `,` work too).
|
|
@@ -71,15 +84,17 @@ export declare function parseViewport(value: string): ViewportSize;
|
|
|
71
84
|
*/
|
|
72
85
|
export declare function parseColorScheme(value: string): ColorScheme;
|
|
73
86
|
/**
|
|
74
|
-
* Reject a
|
|
75
|
-
* `
|
|
76
|
-
*
|
|
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.
|
|
77
92
|
*
|
|
78
93
|
* @param program - Root command with all commands registered
|
|
79
94
|
* @param argv - Process arguments
|
|
80
|
-
* @throws CommandError (81) naming the
|
|
95
|
+
* @throws CommandError (81) naming the command meant
|
|
81
96
|
*/
|
|
82
|
-
export declare function
|
|
97
|
+
export declare function assertNotMistypedCommand(program: Command, argv: string[]): void;
|
|
83
98
|
/**
|
|
84
99
|
* Register the start command
|
|
85
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,14 +67,31 @@ 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)
|
|
81
74
|
.addOption(jsonOption())
|
|
82
75
|
.option('--chrome-flags <flags>', 'Custom Chrome flags (space-separated, e.g., --chrome-flags="--ignore-certificate-errors --disable-web-security")')
|
|
83
76
|
.option('--viewport <WxH>', 'Viewport size in CSS px for the whole session, e.g. 1280x800 (default: 1920x1080 window)')
|
|
84
|
-
.option('--color-scheme <scheme>', 'Emulate prefers-color-scheme for the session: light or dark (default: the system setting)')
|
|
77
|
+
.option('--color-scheme <scheme>', 'Emulate prefers-color-scheme for the session: light or dark (default: the system setting)')
|
|
78
|
+
.option('--mobile', `Emulate a phone for the session: mobile viewport (${MOBILE_VIEWPORT.width}x${MOBILE_VIEWPORT.height} unless --viewport), touch, mobile user agent`);
|
|
79
|
+
}
|
|
80
|
+
/** Viewport of `--mobile` without `--viewport` (a common phone, CSS px) */
|
|
81
|
+
export const MOBILE_VIEWPORT = { width: 390, height: 844 };
|
|
82
|
+
/**
|
|
83
|
+
* The viewport to emulate from `--viewport` and `--mobile`.
|
|
84
|
+
*
|
|
85
|
+
* @param viewport - `--viewport` value
|
|
86
|
+
* @param mobile - `--mobile` was given
|
|
87
|
+
* @returns Viewport (a phone's with `--mobile`), or undefined for neither
|
|
88
|
+
* @throws CommandError (81) for an invalid size
|
|
89
|
+
*/
|
|
90
|
+
export function requestedViewport(viewport, mobile) {
|
|
91
|
+
const size = viewport !== undefined ? parseViewport(viewport) : undefined;
|
|
92
|
+
if (!mobile)
|
|
93
|
+
return size;
|
|
94
|
+
return { ...(size ?? MOBILE_VIEWPORT), mobile: true };
|
|
85
95
|
}
|
|
86
96
|
/** Largest viewport side accepted by `--viewport` (CSS px) */
|
|
87
97
|
const MAX_VIEWPORT_SIDE = 10000;
|
|
@@ -157,12 +167,14 @@ function buildSessionOptions(options) {
|
|
|
157
167
|
quiet: options.quiet ?? false,
|
|
158
168
|
json: options.json ?? false,
|
|
159
169
|
chromeFlags,
|
|
160
|
-
viewport: options.viewport
|
|
170
|
+
viewport: requestedViewport(options.viewport, options.mobile),
|
|
161
171
|
colorScheme: options.colorScheme !== undefined ? parseColorScheme(options.colorScheme) : undefined,
|
|
162
172
|
};
|
|
163
173
|
}
|
|
164
174
|
/** Telemetry collected by every session. */
|
|
165
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;
|
|
166
178
|
/** Flags users type as commands (`bdg version`). */
|
|
167
179
|
const FLAG_WORDS = { version: '--version', help: '--help' };
|
|
168
180
|
/**
|
|
@@ -177,29 +189,59 @@ const FLAG_WORDS = { version: '--version', help: '--help' };
|
|
|
177
189
|
* @throws CommandError (81) for a bare word other than `localhost`
|
|
178
190
|
*/
|
|
179
191
|
function assertNotCommandTypo(arg, commandNames) {
|
|
180
|
-
if (
|
|
192
|
+
if (!BARE_WORD.test(arg) || arg.toLowerCase() === 'localhost')
|
|
181
193
|
return;
|
|
182
194
|
const flag = FLAG_WORDS[arg.toLowerCase()];
|
|
183
195
|
const err = unknownCommandError(arg, flag ? [flag] : findSimilar(arg, commandNames));
|
|
184
196
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
185
197
|
}
|
|
186
198
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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.
|
|
190
226
|
*
|
|
191
227
|
* @param program - Root command with all commands registered
|
|
192
228
|
* @param argv - Process arguments
|
|
193
|
-
* @throws CommandError (81) naming the
|
|
229
|
+
* @throws CommandError (81) naming the command meant
|
|
194
230
|
*/
|
|
195
|
-
export function
|
|
196
|
-
const [first] = argv.slice(2)
|
|
197
|
-
|
|
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))
|
|
198
235
|
return;
|
|
199
236
|
const group = program.commands.find((command) => command.commands.some((sub) => sub.name() === first));
|
|
200
|
-
|
|
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)
|
|
201
243
|
return;
|
|
202
|
-
const err = unknownCommandError(first,
|
|
244
|
+
const err = unknownCommandError(first, similar);
|
|
203
245
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
204
246
|
}
|
|
205
247
|
/**
|
|
@@ -230,7 +272,10 @@ function validateStartInput(url, options, program) {
|
|
|
230
272
|
...(process.env['BDG_CHROME_FLAGS']?.split(' ') ?? []),
|
|
231
273
|
...(options.chromeFlags?.split(' ') ?? []),
|
|
232
274
|
]);
|
|
233
|
-
|
|
275
|
+
const sessionOptions = buildSessionOptions(options);
|
|
276
|
+
if (sessionOptions.userDataDir !== undefined)
|
|
277
|
+
assertUsableProfile(sessionOptions.userDataDir);
|
|
278
|
+
return { url, sessionOptions };
|
|
234
279
|
}
|
|
235
280
|
/**
|
|
236
281
|
* Register the start command
|
|
@@ -329,6 +374,22 @@ function assertUserDataDir(value) {
|
|
|
329
374
|
const err = invalidUserDataDirError(value, reason);
|
|
330
375
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
331
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
|
+
}
|
|
332
393
|
/**
|
|
333
394
|
* Options given that only apply to a Chrome bdg launches. `--headless` has a
|
|
334
395
|
* default, so it counts only when given on the command line.
|