browser-debugger-cli 0.14.0 → 0.16.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 +3 -2
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +201 -133
- package/dist/commands/cleanup.js +21 -4
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +6 -21
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/helpJson.d.ts +1 -1
- package/dist/commands/helpJson.js +3 -3
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/optionBehaviors.js +24 -9
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +3 -0
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +35 -2
- package/dist/connection/launcher.js +99 -12
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +3 -5
- package/dist/constants.js +3 -5
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +22 -3
- package/dist/daemon/launcher.js +45 -8
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +48 -13
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +18520 -17014
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +81 -0
- package/dist/errors/messages.js +198 -6
- package/dist/index.js +1446 -1078
- package/dist/ipc/client.d.ts +20 -2
- package/dist/ipc/client.js +32 -6
- package/dist/ipc/protocol/commands.d.ts +36 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +10 -2
- package/dist/ipc/transport/index.js +29 -4
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +13 -4
- package/dist/runtime/dom/elementInfo.js +12 -3
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +52 -3
- package/dist/session/paths.js +179 -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/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +32 -9
- package/dist/telemetry/har/sanitize.d.ts +28 -0
- package/dist/telemetry/har/sanitize.js +184 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +78 -0
- package/dist/telemetry/har/sanitizeBody.js +541 -0
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +27 -6
- package/dist/ui/messages/chrome.js +78 -12
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +50 -5
- package/dist/ui/messages/networkMessages.js +50 -6
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- 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 +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { extname } from 'path';
|
|
5
5
|
import { DomElementResolver } from './DomElementResolver.js';
|
|
6
|
-
import {
|
|
6
|
+
import { captureScreenshot, resolveSelector, screenshotInterrupted, selectMatch, } from './helpers/index.js';
|
|
7
7
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
8
|
+
import { abortOnInterrupt, unlessInterrupted } from '../shared/interrupt.js';
|
|
8
9
|
import { assertFilePath, outputPathError } from '../shared/outputFile.js';
|
|
9
10
|
import { positiveIntRule } from '../shared/validation.js';
|
|
10
11
|
import { CommandError } from '../../errors/index.js';
|
|
@@ -47,34 +48,43 @@ export function resolveImageFormat(outputPath, requested) {
|
|
|
47
48
|
}
|
|
48
49
|
return requested ?? fromExtension ?? 'png';
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
/**
|
|
52
|
+
* What to capture, from the command's options: the element `backendNodeId`
|
|
53
|
+
* names (with its `--padding`), else the page (`--full-page`, `--scroll`).
|
|
54
|
+
*
|
|
55
|
+
* @param options - Command options
|
|
56
|
+
* @param backendNodeId - Element to capture, if any
|
|
57
|
+
* @returns Daemon request
|
|
58
|
+
*/
|
|
59
|
+
function buildScreenshotRequest(options, backendNodeId) {
|
|
60
|
+
const shared = {
|
|
61
|
+
format: options.format ?? 'png',
|
|
62
|
+
...filterDefined({ quality: options.quality }),
|
|
63
|
+
...(options.resize === false && { noResize: true }),
|
|
64
|
+
};
|
|
65
|
+
if (backendNodeId !== undefined) {
|
|
66
|
+
return { ...shared, backendNodeId, ...filterDefined({ padding: options.padding }) };
|
|
67
|
+
}
|
|
68
|
+
return { ...shared, ...filterDefined({ fullPage: options.fullPage, scroll: options.scroll }) };
|
|
66
69
|
}
|
|
67
70
|
function hasElementTarget(options) {
|
|
68
71
|
return options.selector !== undefined || options.index !== undefined;
|
|
69
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The element to capture: the selector's match (`--index` picks one), or the
|
|
75
|
+
* cached query result at `--index`.
|
|
76
|
+
*
|
|
77
|
+
* @param options - Command options
|
|
78
|
+
* @returns Its backend node id
|
|
79
|
+
* @throws CommandError (81) when neither is given
|
|
80
|
+
*/
|
|
70
81
|
async function resolveElementNodeId(options) {
|
|
71
82
|
if (options.selector !== undefined && options.index !== undefined) {
|
|
72
|
-
return
|
|
83
|
+
return selectMatch(options.selector, options.index);
|
|
73
84
|
}
|
|
74
85
|
if (options.index !== undefined) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
return { backendNodeId: node.nodeId };
|
|
86
|
+
const node = await DomElementResolver.getInstance().getNodeIdForIndex(options.index);
|
|
87
|
+
return node.nodeId;
|
|
78
88
|
}
|
|
79
89
|
if (options.selector !== undefined) {
|
|
80
90
|
return resolveSelector(options.selector);
|
|
@@ -120,32 +130,42 @@ function ensureDirectory(dirPath, fs) {
|
|
|
120
130
|
function formatFrameFilename(frameNumber, format) {
|
|
121
131
|
return `${String(frameNumber).padStart(3, '0')}.${format}`;
|
|
122
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Capture the page. Ctrl-C (or SIGTERM) cancels the capture and exits 130
|
|
135
|
+
* (143), with the error envelope under `--json`.
|
|
136
|
+
*
|
|
137
|
+
* @param outputPath - File to write
|
|
138
|
+
* @param options - Command options
|
|
139
|
+
*/
|
|
123
140
|
async function handlePageScreenshot(outputPath, options) {
|
|
141
|
+
const interrupt = abortOnInterrupt();
|
|
124
142
|
await runCommand(async () => {
|
|
125
|
-
const
|
|
126
|
-
const result = await
|
|
143
|
+
const request = buildScreenshotRequest(options);
|
|
144
|
+
const result = await captureScreenshot(outputPath, request, interrupt);
|
|
127
145
|
return { success: true, data: result };
|
|
128
146
|
}, options, formatDomScreenshot);
|
|
129
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Capture one element; interrupted like {@link handlePageScreenshot}, also
|
|
150
|
+
* while the element is looked up.
|
|
151
|
+
*
|
|
152
|
+
* @param outputPath - File to write
|
|
153
|
+
* @param options - Command options
|
|
154
|
+
*/
|
|
130
155
|
async function handleElementScreenshot(outputPath, options) {
|
|
156
|
+
const interrupt = abortOnInterrupt();
|
|
131
157
|
await runCommand(async () => {
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const
|
|
158
|
+
const lookup = resolveElementNodeId(options);
|
|
159
|
+
const backendNodeId = await unlessInterrupted(lookup, interrupt, screenshotInterrupted);
|
|
160
|
+
const request = buildScreenshotRequest(options, backendNodeId);
|
|
161
|
+
const shot = await captureScreenshot(outputPath, request, interrupt);
|
|
162
|
+
const elementResult = addElementInfo(shot, options);
|
|
136
163
|
return { success: true, data: elementResult };
|
|
137
164
|
}, options, formatDomScreenshot);
|
|
138
165
|
}
|
|
139
166
|
async function captureSequenceFrame(outputPath, options) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const elementOptions = buildElementScreenshotOptions(options);
|
|
143
|
-
await captureElementScreenshot(outputPath, nodeRef, elementOptions);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
const pageOptions = buildPageScreenshotOptions(options);
|
|
147
|
-
await capturePageScreenshot(outputPath, pageOptions);
|
|
148
|
-
}
|
|
167
|
+
const backendNodeId = hasElementTarget(options) ? await resolveElementNodeId(options) : undefined;
|
|
168
|
+
await captureScreenshot(outputPath, buildScreenshotRequest(options, backendNodeId));
|
|
149
169
|
}
|
|
150
170
|
async function handleSequenceCapture(outputDir, options) {
|
|
151
171
|
const fs = await import('fs');
|
|
@@ -93,7 +93,7 @@ export interface CompactCommand {
|
|
|
93
93
|
name: string;
|
|
94
94
|
/** Command aliases (only when it has some) */
|
|
95
95
|
aliases?: readonly string[];
|
|
96
|
-
/**
|
|
96
|
+
/** Summary if set, else the first line of the description */
|
|
97
97
|
description: string;
|
|
98
98
|
/** Arguments as in usage, e.g. "<selector> [index]" (only when it takes some) */
|
|
99
99
|
arguments?: string;
|
|
@@ -114,8 +114,8 @@ function argumentTerm(argument) {
|
|
|
114
114
|
return argument.required ? `<${name}>` : `[${name}]`;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* Recursively converts a Commander Command to its compact summary:
|
|
118
|
-
* description line, arguments, and visible options with their descriptions.
|
|
117
|
+
* Recursively converts a Commander Command to its compact summary: summary
|
|
118
|
+
* (or first description line), arguments, and visible options with their descriptions.
|
|
119
119
|
* Empty fields are left out.
|
|
120
120
|
*
|
|
121
121
|
* @param command - Commander command instance
|
|
@@ -128,7 +128,7 @@ function convertCompactCommand(command) {
|
|
|
128
128
|
return {
|
|
129
129
|
name: command.name(),
|
|
130
130
|
...(aliases.length > 0 && { aliases }),
|
|
131
|
-
description: command.description().split('\n')[0] ?? '',
|
|
131
|
+
description: command.summary() || (command.description().split('\n')[0] ?? ''),
|
|
132
132
|
...(args && { arguments: args }),
|
|
133
133
|
...(options.length > 0 && {
|
|
134
134
|
options: Object.fromEntries(options.map((option) => [option.flags, option.description])),
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { commandPath } from './helpJson.js';
|
|
5
5
|
import { CommandError } from '../errors/index.js';
|
|
6
|
-
import { missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
|
|
6
|
+
import { didYouMeanSuggestion, missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
|
|
7
7
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
8
8
|
import { findSimilar } from '../utils/suggestions.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Commander's typo hint on its own line: "(Did you mean query?)" or, for
|
|
11
|
+
* several equally close candidates, "(Did you mean one of form, frames?)"
|
|
12
|
+
*/
|
|
13
|
+
const COMMANDER_HINT = /\n?\(Did you mean (?:one of )?(.+)\?\)\s*$/;
|
|
11
14
|
/**
|
|
12
15
|
* The command path of `bdg help <path...>`: the words before the first option.
|
|
13
16
|
*
|
|
@@ -54,7 +57,10 @@ export function splitCommanderHint(text) {
|
|
|
54
57
|
const hint = COMMANDER_HINT.exec(message);
|
|
55
58
|
if (!hint)
|
|
56
59
|
return { message };
|
|
57
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
message: message.slice(0, hint.index).trim(),
|
|
62
|
+
suggestion: didYouMeanSuggestion((hint[1] ?? '').split(', ')),
|
|
63
|
+
};
|
|
58
64
|
}
|
|
59
65
|
/** The option named in Commander's "unknown option '--x'" message, without an `=value` */
|
|
60
66
|
const UNKNOWN_OPTION = /unknown option '([^'=]+)/;
|
|
@@ -15,6 +15,7 @@ import { getSessionFilePath } from '../../session/paths.js';
|
|
|
15
15
|
import { applyFilters, parseFilterString } from '../../telemetry/filterDsl.js';
|
|
16
16
|
import { buildHAR } from '../../telemetry/har/builder.js';
|
|
17
17
|
import { createLogger } from '../../ui/logging/index.js';
|
|
18
|
+
import { harExportedMessage } from '../../ui/messages/networkMessages.js';
|
|
18
19
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
19
20
|
import { VERSION } from '../../utils/version.js';
|
|
20
21
|
import { getNetworkRequests, validateFilterOption } from './shared.js';
|
|
@@ -67,15 +68,20 @@ async function getChromeVersion() {
|
|
|
67
68
|
function formatHARExport(data) {
|
|
68
69
|
if ('log' in data)
|
|
69
70
|
return JSON.stringify(data, null, 2);
|
|
70
|
-
|
|
71
|
-
return `✓ Exported ${data.entries} requests${filterNote} to ${data.file}`;
|
|
71
|
+
return harExportedMessage(data);
|
|
72
72
|
}
|
|
73
|
+
/** HAR files may hold credentials (--include-sensitive): readable by their owner only */
|
|
74
|
+
const HAR_FILE_MODE = 0o600;
|
|
73
75
|
/** Output path meaning "write the HAR to stdout" */
|
|
74
76
|
const STDOUT_PATH = '-';
|
|
75
77
|
/**
|
|
76
78
|
* Option for filter DSL.
|
|
77
79
|
*/
|
|
78
80
|
const filterDslOption = new Option('--filter <dsl>', 'Filter requests using DevTools DSL (e.g., "status-code:>=400")');
|
|
81
|
+
/**
|
|
82
|
+
* Option to keep credentials in the export.
|
|
83
|
+
*/
|
|
84
|
+
const includeSensitiveOption = new Option('--include-sensitive', 'Keep credentials (auth/cookie/API key headers, cookie values, URL tokens, password and token body fields); redacted by default');
|
|
79
85
|
/**
|
|
80
86
|
* Register HAR export command.
|
|
81
87
|
*
|
|
@@ -88,6 +94,7 @@ export function registerHarCommand(networkCmd) {
|
|
|
88
94
|
.description('Export network data as HAR 1.2 format')
|
|
89
95
|
.addOption(jsonOption())
|
|
90
96
|
.addOption(filterDslOption)
|
|
97
|
+
.addOption(includeSensitiveOption)
|
|
91
98
|
.action(async (outputFile, options) => {
|
|
92
99
|
await runCommand(async () => {
|
|
93
100
|
let requests = await getNetworkRequests();
|
|
@@ -103,21 +110,18 @@ export function registerHarCommand(networkCmd) {
|
|
|
103
110
|
if (outputPath !== STDOUT_PATH)
|
|
104
111
|
assertFilePath(outputPath, '.har');
|
|
105
112
|
const chromeVersion = await getChromeVersion();
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
...(chromeVersion && { chromeVersion }),
|
|
109
|
-
});
|
|
113
|
+
const includeSensitive = options.includeSensitive === true;
|
|
114
|
+
const har = buildHAR(requests, { version: VERSION, ...(chromeVersion && { chromeVersion }) }, { includeSensitive });
|
|
110
115
|
if (outputPath === STDOUT_PATH)
|
|
111
116
|
return { success: true, data: har };
|
|
112
|
-
const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), '.har');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
filtered,
|
|
119
|
-
},
|
|
117
|
+
const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), '.har', HAR_FILE_MODE);
|
|
118
|
+
const data = {
|
|
119
|
+
file,
|
|
120
|
+
entries: har.log.entries.length,
|
|
121
|
+
filtered,
|
|
122
|
+
sanitized: !includeSensitive,
|
|
120
123
|
};
|
|
124
|
+
return { success: true, data };
|
|
121
125
|
}, options, formatHARExport);
|
|
122
126
|
});
|
|
123
127
|
}
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see docs/principles/SELF_DOCUMENTING_SYSTEMS.md
|
|
8
8
|
*/
|
|
9
|
-
import { MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD, } from '
|
|
9
|
+
import { MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD, } from '../runtime/page/screenshotResize.js';
|
|
10
10
|
/** What DOM actions report about the network requests they triggered */
|
|
11
11
|
const TRIGGERED_REQUESTS_BEHAVIOR = 'Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them';
|
|
12
12
|
/** What every DOM action reports about the page besides its requests */
|
|
13
|
-
const ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "…" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
13
|
+
const ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "…" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Downloads that began meanwhile are listed (Download: <name> → <path> (<state>, <size>); JSON downloads [{ url, suggestedFilename, path, state: inProgress|completed|canceled, bytes }]); a Chrome bdg launched saves them into <session dir>/downloads (never ~/Downloads), named as suggested with (1), (2)… when taken, and one still running when the action returns is inProgress and appears at its path once complete; downloads of tabs the page opens (target=_blank, window.open) count too; when that directory cannot be created they are refused (canceled, with reason) instead of going to ~/Downloads, and if Chrome refuses the download behavior bdg status warns that downloads are not redirected; ones that begin after the action returned are listed by bdg status and bdg peek (Downloads: N (last: …)); files stay after stop and cleanup; with --chrome-ws-url (the exception: not a browser bdg owns) downloads go to the download folder of that Chrome (usually ~/Downloads), only downloads of the session page are tracked (not those of tabs or popups it opens), and bdg sets behavior default with events, which replaces one another CDP client set and which Chrome drops when the session connection closes. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
14
14
|
/** What click and pressKey report when the page was still changing as they returned */
|
|
15
|
-
const STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes
|
|
15
|
+
const STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes came in bursts and, at a second look 250 ms later, again, with no quiet gap over 150 ms; time the page could not run its tasks, a long task or timers a starved renderer runs late, is not quiet, and a single burst more than 150 ms old with at most 75 ms of quiet time since also gets the second look (only that 250 ms; domChanging still needs a fresh change at it); a single render, ticking text and style animations do not count, and changes more than about 150 ms apart while the page could run look settled), busy (the page did not answer within 250 ms and ran a long task since the action began, or did not answer a check within 250 ms more: a long script; a page that answered late without a long task, a starved renderer, gets its answer waited for instead; a renderer descheduled in the middle of a task can still record a long task and is then busy; the check runs at most once per action, adding at most 250 ms, so an endless script is reported busy about 500 ms after the action) }; absent when the page looked settled (exit code stays 0). A result a timer renders later, with no DOM change, request or loading indicator before it, is not detected. Cost: nothing extra, except 250 ms plus one read when the DOM looked busy. Not checked with --no-wait';
|
|
16
16
|
/** What hover and pressKey report about elements they showed */
|
|
17
17
|
const SHOWN_BEHAVIOR = 'Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target\'s form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count';
|
|
18
18
|
/** What `--no-wait` does to a DOM action's triggered requests */
|
|
@@ -101,9 +101,9 @@ const OPTION_BEHAVIORS = {
|
|
|
101
101
|
automaticBehavior: 'The value is matched as: a 0-based index (bdg dom frames order: document order of the <iframe> elements, nested ones depth-first, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again). An index that names another frame than in the last bdg dom frames listing (iframes added, removed or moved, or the page navigated) fails with 87 STALE_CACHE: re-run bdg dom frames or pick the frame by name.',
|
|
102
102
|
},
|
|
103
103
|
'eval:--full': {
|
|
104
|
-
default: 'Human output prints the first 20000 characters of the value followed by "… N more chars (use --full)"
|
|
105
|
-
whenEnabled: 'Prints the whole value, byte for byte',
|
|
106
|
-
tokenImpact: 'dom eval document.documentElement.outerHTML on Wikipedia is about 3.6 MB with --full; select what you need in the expression instead',
|
|
104
|
+
default: 'Human output prints the first 20000 characters of the value followed by "… N more chars (use --full)". In JSON a string result is cut to 20000 characters with truncatedFrom (the original length); an array result lists its first 100 elements with count (all of them) and omitted; an object or array whose JSON is still over 20000 characters becomes the first 20000 characters of its JSON text (a string; arrays keep count) with truncatedFrom (the length of the JSON text of the copy, which holds at most 1000 entries per list or object): a string result with truncatedFrom while type is object is such a JSON-text start',
|
|
105
|
+
whenEnabled: 'Prints the whole value, byte for byte; objects and arrays are copied with every entry (without --full at most 1000 per list or object)',
|
|
106
|
+
tokenImpact: 'dom eval document.documentElement.outerHTML on Wikipedia is about 3.6 MB with --full; on a page with 20000 elements, [...document.querySelectorAll("*")].map(e => e.outerHTML) --json is 3 MB with --full and 23 KB without; select what you need in the expression instead',
|
|
107
107
|
},
|
|
108
108
|
'console:--full': {
|
|
109
109
|
default: 'Message texts are cut: human output (summary, --list, --follow) at 200 characters followed by "… N more chars (use --full)"; JSON text at 10000 characters with truncatedFrom (the original length)',
|
|
@@ -330,6 +330,11 @@ const OPTION_BEHAVIORS = {
|
|
|
330
330
|
whenEnabled: 'Streams requests as they finish',
|
|
331
331
|
automaticBehavior: FOLLOW_BEHAVIOR,
|
|
332
332
|
},
|
|
333
|
+
'har:--include-sensitive': {
|
|
334
|
+
default: 'The HAR is sanitized: values become "[redacted]" for Authorization, Proxy-Authorization, Authentication, Cookie and Set-Cookie headers, X-*key/token/secret/auth headers and headers with an api-key/apikey/token/secret/jwt/subscription-key/session(-id) segment (www-authenticate is kept); every cookie value; query and fragment parameters named like credentials (plus code, sig, key) in the request URL, queryString, redirectURL and Location/Referer headers ("%5Bredacted%5D" in URLs); and password/token/secret/key/session/signature/bearer/cookie/csrf/refresh/auth/sid/pin/ssn/card-number fields of JSON (primitives at any depth under such a name), form-urlencoded (also sniffed when the Content-Type says otherwise, user[password] names too) and multipart request and response bodies (an access_token/refresh_token/id_token login response too) and of WebSocket messages, also in truncated JSON, JSON encoded in string values (3 levels), socket.io and SockJS packets, server-sent events, NDJSON, base64 bodies with no, a generic, JSON, form or event-stream type and binary WebSocket messages that are UTF-8 text; any whole JWT (eyJ…) in a JSON string, form or URL value, multipart part or text body (only the JWT is replaced). camelCase names count as words (userPin). JSON is edited in place, not re-serialized: everything but the replaced values (64-bit numbers, formatting, duplicate keys, a BOM or )]}\' prefix) stays byte for byte. Header, cookie and parameter names, cookie attributes, headersSize, bodySize and content.size stay; log.comment and JSON sanitized: true say so',
|
|
335
|
+
whenEnabled: 'Writes every captured value (JSON sanitized: false); human output warns that the file holds credentials',
|
|
336
|
+
automaticBehavior: 'Matching is by name, so it over-redacts: harmless values under credential-looking names (tokenCount: 5, sessionLength, refreshInterval, cookieConsent) are replaced too, and credentials under other names are kept. Only JSON syntax is understood: single-quoted strings, unquoted keys, JSONP, STOMP passcode: header lines and bare values with spaces ({"token":abc def}, a non-JWT token after Bearer in text) are not (fully) redacted. A body the sanitizer fails on is replaced whole by [redacted]. Unlike Chrome DevTools, which drops these headers and empties cookies, names are kept so the HAR still shows a request was authenticated. Kept as captured: other binary (base64) bodies, binary WebSocket messages that are not UTF-8, and text that is not JSON or a form (apart from JWTs); a WebSocket message cut at 100 KB has _truncatedFrom. HAR files are written readable by their owner only (0600). network headers and network getCookies always show real values',
|
|
337
|
+
},
|
|
333
338
|
'peek:--verbose': {
|
|
334
339
|
default: 'Compact output (truncated URLs, no resource types)',
|
|
335
340
|
whenEnabled: 'Verbose output with full URLs and resource types',
|
|
@@ -360,8 +365,8 @@ const OPTION_BEHAVIORS = {
|
|
|
360
365
|
whenEnabled: 'Alias for --force, kept for compatibility',
|
|
361
366
|
},
|
|
362
367
|
'cleanup:--purge': {
|
|
363
|
-
default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt) is kept for its next start",
|
|
364
|
-
whenEnabled: 'After cleaning up, deletes the directory of the session named by --session (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited',
|
|
368
|
+
default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt; downloads/) is kept for its next start",
|
|
369
|
+
whenEnabled: 'After cleaning up, deletes the directory of the session named by --session, downloaded files included (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited',
|
|
365
370
|
},
|
|
366
371
|
'bdg:--viewport': {
|
|
367
372
|
default: 'A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window',
|
|
@@ -381,12 +386,22 @@ const OPTION_BEHAVIORS = {
|
|
|
381
386
|
'bdg:--chrome-ws-url': {
|
|
382
387
|
default: 'bdg launches its own Chrome (closed on stop)',
|
|
383
388
|
whenEnabled: 'Attaches to a running Chrome instead; it keeps running after stop. --port, -u and --[no-]headless cannot be combined with it (exit 81)',
|
|
384
|
-
automaticBehavior: 'A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port)',
|
|
389
|
+
automaticBehavior: 'A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port). Downloads are not redirected: they go to the download folder of that Chrome (usually ~/Downloads), and only downloads of the session page are reported (not those of tabs or popups it opens)',
|
|
385
390
|
},
|
|
386
391
|
'stop:--kill-chrome': {
|
|
387
392
|
default: 'Chrome launched by bdg is always closed on stop; an attached Chrome (--chrome-ws-url) is left running',
|
|
388
393
|
whenEnabled: 'No additional effect; kept for compatibility',
|
|
389
394
|
},
|
|
395
|
+
'cdp:--send-anyway': {
|
|
396
|
+
default: 'A Domain.method 1 edit from a bundled method or domain (2 for names over 5 letters, case ignored) is taken for a typo: exit 81 with Did you mean',
|
|
397
|
+
whenEnabled: 'Sends such a name to Chrome as typed (for a method newer than the bundled protocol, e.g. next to a bundled sibling like getWindowBounds/setWindowBounds), with the not-in-bundled-protocol warning',
|
|
398
|
+
automaticBehavior: 'Blocked methods (Page.captureScreenshot, Page.close, Browser.close), type names and names that are not Domain.method are still refused; a well-formed name far from every bundled one is sent without the flag',
|
|
399
|
+
},
|
|
400
|
+
'cdp:--describe': {
|
|
401
|
+
default: 'Without --describe, a Domain.method is called (one missing from the bundled protocol is sent as typed, with a warning: only bundled methods are matched case-insensitively)',
|
|
402
|
+
whenEnabled: 'Describes a domain, a method (parameters with ? for optional, returns, example) or a protocol type (Domain.Type: enum values or object properties)',
|
|
403
|
+
automaticBehavior: 'Parameters referring to an enum type list its values inline (JSON enum, ref, refType); a redirected method (DOM.highlightNode) also shows the method implementing it and its parameters, which Chrome checks (JSON redirect, resolved: true); a redirect to a method the protocol lacks (Page.deleteCookie → Network.deleteCookie) is shown as unresolved (resolved: false). Example values work as typed: width 1280, height 800, x/y 100, deviceScaleFactor/scale 1, timeout 5000, url https://example.com, other numbers 1 (never 0, which often means off)',
|
|
404
|
+
},
|
|
390
405
|
'status:--verbose': {
|
|
391
406
|
default: 'Basic session status (daemon running, session active, URL)',
|
|
392
407
|
whenEnabled: 'Includes Chrome diagnostics and CDP connection details',
|
|
@@ -44,6 +44,11 @@ export interface CommandResult<T = unknown> {
|
|
|
44
44
|
errorContext?: Record<string, unknown>;
|
|
45
45
|
/** Optional hint message to display on stderr (for successful commands with guidance) */
|
|
46
46
|
hint?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Warning about how the command ran (success or failure): top-level
|
|
49
|
+
* `warning` in the JSON envelope, `Warning: …` on stderr otherwise
|
|
50
|
+
*/
|
|
51
|
+
warning?: string;
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* Handler function type.
|
|
@@ -80,6 +80,15 @@ function sessionEndedError() {
|
|
|
80
80
|
const err = sessionEndedDuringCommandError();
|
|
81
81
|
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Print a command's warning on stderr (text output; `--json` has it in the envelope).
|
|
85
|
+
*
|
|
86
|
+
* @param warning - Warning, if any
|
|
87
|
+
*/
|
|
88
|
+
function printWarning(warning) {
|
|
89
|
+
if (warning)
|
|
90
|
+
console.error(escapeControlChars(`Warning: ${warning}`));
|
|
91
|
+
}
|
|
83
92
|
/**
|
|
84
93
|
* Run a command with consistent error handling, output formatting, and exit codes.
|
|
85
94
|
* Eliminates boilerplate try-catch and JSON output logic from command handlers.
|
|
@@ -114,6 +123,7 @@ export async function runCommand(handler, options, formatter) {
|
|
|
114
123
|
if (options.json) {
|
|
115
124
|
console.log(stringifyEnvelope(OutputBuilder.buildJsonError(result.error ?? 'Unknown error', {
|
|
116
125
|
...result.errorContext,
|
|
126
|
+
...(result.warning && { warning: result.warning }),
|
|
117
127
|
exitCode,
|
|
118
128
|
})));
|
|
119
129
|
}
|
|
@@ -126,14 +136,17 @@ export async function runCommand(handler, options, formatter) {
|
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
}
|
|
139
|
+
printWarning(result.warning);
|
|
129
140
|
}
|
|
130
141
|
process.exit(exitCode);
|
|
131
142
|
}
|
|
143
|
+
if (!options.json)
|
|
144
|
+
printWarning(result.warning);
|
|
132
145
|
if (result.hint && !options.quiet) {
|
|
133
146
|
console.error(escapeControlChars(result.hint));
|
|
134
147
|
}
|
|
135
148
|
if (options.json) {
|
|
136
|
-
console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
|
|
149
|
+
console.log(stringifyEnvelope(buildSuccessResponse(result.data, result.warning)));
|
|
137
150
|
}
|
|
138
151
|
else if (formatter) {
|
|
139
152
|
const formattedOutput = formatter(result.data);
|
|
@@ -158,10 +171,12 @@ export async function runCommand(handler, options, formatter) {
|
|
|
158
171
|
})));
|
|
159
172
|
}
|
|
160
173
|
else {
|
|
174
|
+
const { warning, ...metadata } = error.metadata;
|
|
161
175
|
console.error(genericError(error.message));
|
|
162
|
-
for (const value of Object.values(
|
|
163
|
-
console.error(escapeControlChars(
|
|
176
|
+
for (const value of Object.values(metadata)) {
|
|
177
|
+
console.error(escapeControlChars(typeof value === 'string' ? value : JSON.stringify(value)));
|
|
164
178
|
}
|
|
179
|
+
printWarning(warning);
|
|
165
180
|
}
|
|
166
181
|
process.exit(error.exitCode);
|
|
167
182
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ctrl-C and SIGTERM for a command that cancels its daemon request instead
|
|
3
|
+
* of dying mid-request, so it can still report (the `--json` envelope) and
|
|
4
|
+
* exit with the shell's code.
|
|
5
|
+
*/
|
|
6
|
+
/** Signals that interrupt a command */
|
|
7
|
+
export type InterruptSignal = 'SIGINT' | 'SIGTERM';
|
|
8
|
+
/**
|
|
9
|
+
* The exit code of an interrupted command, as shells expect.
|
|
10
|
+
*
|
|
11
|
+
* @param signal - The signal
|
|
12
|
+
* @returns 130 for SIGINT, 143 for SIGTERM
|
|
13
|
+
*/
|
|
14
|
+
export declare function interruptExitCode(signal: InterruptSignal): number;
|
|
15
|
+
/**
|
|
16
|
+
* The signal that aborted an interrupt, from its reason.
|
|
17
|
+
*
|
|
18
|
+
* @param interrupt - Aborted interrupt (reason: the signal name)
|
|
19
|
+
* @returns The signal, SIGINT unless it was SIGTERM
|
|
20
|
+
*/
|
|
21
|
+
export declare function interruptSignal(interrupt: AbortSignal): InterruptSignal;
|
|
22
|
+
/**
|
|
23
|
+
* Abort on Ctrl-C or SIGTERM (reason: the signal name). A second signal
|
|
24
|
+
* exits at once.
|
|
25
|
+
*
|
|
26
|
+
* @returns Aborted on the first signal
|
|
27
|
+
*/
|
|
28
|
+
export declare function abortOnInterrupt(): AbortSignal;
|
|
29
|
+
/**
|
|
30
|
+
* Wait for work unless interrupted first: the first signal ends the wait at
|
|
31
|
+
* once with the interrupted error (the work is left to the exiting process).
|
|
32
|
+
*
|
|
33
|
+
* @param work - Work to wait for
|
|
34
|
+
* @param interrupt - Aborted on Ctrl-C or SIGTERM (reason: the signal)
|
|
35
|
+
* @param interruptedError - The error for an interrupt by a signal
|
|
36
|
+
* @returns The work's value
|
|
37
|
+
* @throws The interrupted error once interrupted, else the work's error
|
|
38
|
+
*/
|
|
39
|
+
export declare function unlessInterrupted<T>(work: Promise<T>, interrupt: AbortSignal, interruptedError: (signal: InterruptSignal) => Error): Promise<T>;
|
|
40
|
+
//# sourceMappingURL=interrupt.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ctrl-C and SIGTERM for a command that cancels its daemon request instead
|
|
3
|
+
* of dying mid-request, so it can still report (the `--json` envelope) and
|
|
4
|
+
* exit with the shell's code.
|
|
5
|
+
*/
|
|
6
|
+
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
7
|
+
/**
|
|
8
|
+
* The exit code of an interrupted command, as shells expect.
|
|
9
|
+
*
|
|
10
|
+
* @param signal - The signal
|
|
11
|
+
* @returns 130 for SIGINT, 143 for SIGTERM
|
|
12
|
+
*/
|
|
13
|
+
export function interruptExitCode(signal) {
|
|
14
|
+
return signal === 'SIGINT' ? EXIT_CODES.INTERRUPTED : EXIT_CODES.TERMINATED;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The signal that aborted an interrupt, from its reason.
|
|
18
|
+
*
|
|
19
|
+
* @param interrupt - Aborted interrupt (reason: the signal name)
|
|
20
|
+
* @returns The signal, SIGINT unless it was SIGTERM
|
|
21
|
+
*/
|
|
22
|
+
export function interruptSignal(interrupt) {
|
|
23
|
+
return interrupt.reason === 'SIGTERM' ? 'SIGTERM' : 'SIGINT';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Abort on Ctrl-C or SIGTERM (reason: the signal name). A second signal
|
|
27
|
+
* exits at once.
|
|
28
|
+
*
|
|
29
|
+
* @returns Aborted on the first signal
|
|
30
|
+
*/
|
|
31
|
+
export function abortOnInterrupt() {
|
|
32
|
+
const interrupt = new AbortController();
|
|
33
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
34
|
+
process.on(signal, () => {
|
|
35
|
+
if (interrupt.signal.aborted)
|
|
36
|
+
process.exit(interruptExitCode(signal));
|
|
37
|
+
interrupt.abort(signal);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return interrupt.signal;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wait for work unless interrupted first: the first signal ends the wait at
|
|
44
|
+
* once with the interrupted error (the work is left to the exiting process).
|
|
45
|
+
*
|
|
46
|
+
* @param work - Work to wait for
|
|
47
|
+
* @param interrupt - Aborted on Ctrl-C or SIGTERM (reason: the signal)
|
|
48
|
+
* @param interruptedError - The error for an interrupt by a signal
|
|
49
|
+
* @returns The work's value
|
|
50
|
+
* @throws The interrupted error once interrupted, else the work's error
|
|
51
|
+
*/
|
|
52
|
+
export async function unlessInterrupted(work, interrupt, interruptedError) {
|
|
53
|
+
const fail = () => interruptedError(interruptSignal(interrupt));
|
|
54
|
+
if (interrupt.aborted)
|
|
55
|
+
throw fail();
|
|
56
|
+
let onAbort = () => undefined;
|
|
57
|
+
const interrupted = new Promise((_, reject) => {
|
|
58
|
+
onAbort = () => reject(fail());
|
|
59
|
+
interrupt.addEventListener('abort', onAbort, { once: true });
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
return await Promise.race([work, interrupted]);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (interrupt.aborted)
|
|
66
|
+
throw fail();
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
interrupt.removeEventListener('abort', onAbort);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=interrupt.js.map
|
|
@@ -129,6 +129,8 @@ export interface CdpMethodOptions {
|
|
|
129
129
|
describe?: boolean;
|
|
130
130
|
/** Search for methods */
|
|
131
131
|
search?: string;
|
|
132
|
+
/** Send a method that looks like a typo of a bundled one as typed */
|
|
133
|
+
sendAnyway?: boolean;
|
|
132
134
|
}
|
|
133
135
|
/** Options for stop command */
|
|
134
136
|
export type StopCommandOptions = BaseOptions & ChromeOptions;
|
|
@@ -300,6 +302,7 @@ export type NetworkCookiesCommandOptions = BaseOptions & {
|
|
|
300
302
|
/** Options for network HAR command */
|
|
301
303
|
export type NetworkHarCommandOptions = BaseOptions & {
|
|
302
304
|
outputFile?: string;
|
|
305
|
+
includeSensitive?: boolean;
|
|
303
306
|
};
|
|
304
307
|
/** Options for network headers command */
|
|
305
308
|
export type NetworkHeadersCommandOptions = BaseOptions & {
|
|
@@ -26,8 +26,9 @@ export declare function assertFilePath(filePath: string, extension?: string): vo
|
|
|
26
26
|
* @param filePath - Path the user gave
|
|
27
27
|
* @param data - File contents
|
|
28
28
|
* @param extension - Extension of the file kind, for examples in errors
|
|
29
|
+
* @param mode - File permissions of a text file (default: 0666 less the umask)
|
|
29
30
|
* @returns Absolute path written
|
|
30
31
|
* @throws CommandError naming the path when it cannot be written
|
|
31
32
|
*/
|
|
32
|
-
export declare function writeOutputFile(filePath: string, data: string | Buffer, extension?: string): Promise<string>;
|
|
33
|
+
export declare function writeOutputFile(filePath: string, data: string | Buffer, extension?: string, mode?: number): Promise<string>;
|
|
33
34
|
//# sourceMappingURL=outputFile.d.ts.map
|
|
@@ -62,18 +62,21 @@ export function assertFilePath(filePath, extension) {
|
|
|
62
62
|
* @param filePath - Path the user gave
|
|
63
63
|
* @param data - File contents
|
|
64
64
|
* @param extension - Extension of the file kind, for examples in errors
|
|
65
|
+
* @param mode - File permissions of a text file (default: 0666 less the umask)
|
|
65
66
|
* @returns Absolute path written
|
|
66
67
|
* @throws CommandError naming the path when it cannot be written
|
|
67
68
|
*/
|
|
68
|
-
export async function writeOutputFile(filePath, data, extension) {
|
|
69
|
+
export async function writeOutputFile(filePath, data, extension, mode) {
|
|
69
70
|
assertFilePath(filePath, extension);
|
|
70
71
|
const absolutePath = path.resolve(filePath);
|
|
71
72
|
try {
|
|
72
73
|
makeDirectory(path.dirname(absolutePath));
|
|
73
|
-
if (typeof data === 'string')
|
|
74
|
-
await AtomicFileWriter.writeAsync(absolutePath, data);
|
|
75
|
-
|
|
74
|
+
if (typeof data === 'string') {
|
|
75
|
+
await AtomicFileWriter.writeAsync(absolutePath, data, mode === undefined ? {} : { mode });
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
76
78
|
await AtomicFileWriter.writeBufferAsync(absolutePath, data);
|
|
79
|
+
}
|
|
77
80
|
}
|
|
78
81
|
catch (error) {
|
|
79
82
|
throw outputPathError(filePath, error, extension);
|