@crouton-kit/capture 1.3.8 → 1.3.10
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/bin/capture +29841 -8539
- package/package.json +6 -3
- package/src/capture.ts +86 -173
- package/src/cdp/a11y.ts +113 -14
- package/src/cdp/args.ts +300 -205
- package/src/cdp/bridge/protocol.ts +93 -6
- package/src/cdp/bridge/server.ts +118 -53
- package/src/cdp/bridge/spawn.ts +53 -6
- package/src/cdp/client.ts +13 -3
- package/src/cdp/commands/bridge-serve.ts +20 -14
- package/src/cdp/commands/cdp.ts +259 -90
- package/src/cdp/commands/gate-guard.ts +43 -0
- package/src/cdp/commands/lib.ts +339 -213
- package/src/cdp/commands/measure/census.ts +76 -20
- package/src/cdp/commands/measure/check.ts +100 -26
- package/src/cdp/commands/measure/diff.ts +109 -21
- package/src/cdp/commands/measure/explain.ts +121 -19
- package/src/cdp/commands/measure/index.ts +31 -39
- package/src/cdp/commands/measure/map-ax.ts +52 -0
- package/src/cdp/commands/measure/map-focus.ts +41 -13
- package/src/cdp/commands/measure/map-layers.ts +49 -15
- package/src/cdp/commands/measure/map-scroll.ts +85 -15
- package/src/cdp/commands/measure/snap.ts +405 -25
- package/src/cdp/commands/measure/sweep.ts +217 -20
- package/src/cdp/commands/motion/index.ts +18 -17
- package/src/cdp/commands/motion/jank.ts +119 -9
- package/src/cdp/commands/motion/mask.ts +81 -12
- package/src/cdp/commands/motion/rec.ts +520 -19
- package/src/cdp/commands/motion/response.ts +65 -15
- package/src/cdp/commands/motion/timeline.ts +139 -14
- package/src/cdp/commands/page/click.ts +194 -0
- package/src/cdp/commands/page/elements.ts +154 -0
- package/src/cdp/commands/page/exec.ts +317 -0
- package/src/cdp/commands/page/index.ts +72 -0
- package/src/cdp/commands/page/navigate.ts +330 -0
- package/src/cdp/commands/page/scroll.ts +107 -0
- package/src/cdp/commands/page/shot.ts +208 -0
- package/src/cdp/commands/page/type.ts +104 -0
- package/src/cdp/commands/tab/index.ts +59 -0
- package/src/cdp/commands/tab/list.ts +167 -0
- package/src/cdp/commands/tab/network.ts +162 -0
- package/src/cdp/commands/tab/open.ts +90 -0
- package/src/cdp/commands/tab/reset.ts +160 -0
- package/src/cdp/connection.ts +368 -37
- package/src/cdp/detect.ts +42 -13
- package/src/cdp/dispatch.ts +74 -28
- package/src/cdp/har-recorder.ts +648 -115
- package/src/cdp/leaf-grammar.ts +99 -0
- package/src/cdp/measure/census.ts +205 -0
- package/src/cdp/measure/check.ts +318 -0
- package/src/cdp/measure/collectors/animation.ts +385 -0
- package/src/cdp/measure/collectors/ax.ts +278 -0
- package/src/cdp/measure/collectors/focus.ts +1041 -0
- package/src/cdp/measure/collectors/forms.ts +905 -0
- package/src/cdp/measure/collectors/geometry.ts +1038 -0
- package/src/cdp/measure/collectors/hittest.ts +835 -0
- package/src/cdp/measure/collectors/layers.ts +697 -0
- package/src/cdp/measure/collectors/media.ts +522 -0
- package/src/cdp/measure/collectors/pixels.ts +1584 -0
- package/src/cdp/measure/collectors/queries.ts +464 -0
- package/src/cdp/measure/collectors/scroll.ts +599 -0
- package/src/cdp/measure/collectors/states.ts +992 -0
- package/src/cdp/measure/collectors/style-provenance.ts +643 -0
- package/src/cdp/measure/collectors/styles.ts +355 -0
- package/src/cdp/measure/collectors/text.ts +713 -0
- package/src/cdp/measure/diff.ts +470 -0
- package/src/cdp/measure/explain.ts +498 -0
- package/src/cdp/measure/map-ax.ts +633 -0
- package/src/cdp/measure/map-focus.ts +188 -0
- package/src/cdp/measure/map-layers.ts +177 -0
- package/src/cdp/measure/map-scroll.ts +317 -0
- package/src/cdp/measure/redaction.ts +51 -0
- package/src/cdp/measure/settle.ts +1051 -0
- package/src/cdp/measure/snapshot.ts +390 -0
- package/src/cdp/measure/sweep.ts +330 -0
- package/src/cdp/measure/types.ts +220 -0
- package/src/cdp/measure/v2/collectors.ts +19 -0
- package/src/cdp/measure/v2/orchestrator.ts +46 -0
- package/src/cdp/measure/v2/snap-selection.ts +33 -0
- package/src/cdp/measure/v2/target-lock.ts +15 -0
- package/src/cdp/measure/v2/target.ts +16 -0
- package/src/cdp/motion/jank.ts +433 -0
- package/src/cdp/motion/mask.ts +421 -0
- package/src/cdp/motion/recorder.ts +764 -0
- package/src/cdp/motion/response.ts +356 -0
- package/src/cdp/motion/timeline.ts +215 -0
- package/src/cdp/record.ts +5 -212
- package/src/cdp/recorder-bridge.ts +1478 -105
- package/src/cdp/recorder-client.ts +326 -0
- package/src/cdp/scope-lock.ts +73 -0
- package/src/cdp/screenshot.ts +199 -66
- package/src/cdp/targets.ts +21 -1
- package/src/cdp/timing.ts +15 -10
- package/src/cdp/types.ts +22 -7
- package/src/cdp/viewport.ts +21 -0
- package/src/cdp.ts +12 -58
- package/src/command-help.ts +29 -0
- package/src/command-resolution.ts +22 -0
- package/src/command-walker.ts +35 -0
- package/src/contracts/a11y.ts +33 -0
- package/src/contracts/cursor.ts +35 -0
- package/src/contracts/index.ts +15 -0
- package/src/contracts/motion.ts +19 -0
- package/src/contracts/neutral.ts +56 -0
- package/src/contracts/primitives.ts +205 -0
- package/src/contracts/release.ts +17 -0
- package/src/contracts/results.ts +208 -0
- package/src/contracts/routes.ts +140 -0
- package/src/contracts/snapshot.ts +92 -0
- package/src/cursor/index.ts +13 -0
- package/src/cursor/service.ts +310 -0
- package/src/errors.ts +71 -0
- package/src/har-manager.ts +992 -41
- package/src/interact.ts +410 -98
- package/src/output/artifact-lifecycle.ts +371 -0
- package/src/output/artifact.ts +6 -0
- package/src/output/exact-raw.ts +114 -0
- package/src/output/render.ts +396 -2
- package/src/output/structured.ts +211 -0
- package/src/registry.ts +208 -0
- package/src/session/artifacts.ts +452 -200
- package/src/session/commands.ts +1162 -397
- package/src/session/coordinator.ts +251 -0
- package/src/session/har-filter.ts +57 -0
- package/src/session/log-tailer.ts +653 -0
- package/src/session-context.ts +279 -51
- package/src/vault/bundle.ts +32 -17
- package/src/cdp/commands/exec.ts +0 -105
- package/src/cdp/commands/har.ts +0 -128
- package/src/cdp/commands/tabs.ts +0 -183
- package/src/cdp/commands/traffic.ts +0 -121
- package/src/cdp/commands/ui.ts +0 -172
- package/src/cdp/execute.ts +0 -113
- package/src/cdp/har-output.ts +0 -47
- package/src/cdp/usage.ts +0 -79
- package/src/tab-session.ts +0 -91
package/src/cdp/args.ts
CHANGED
|
@@ -1,25 +1,208 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { invalidInput } from '../errors.js';
|
|
2
2
|
import { getActiveSession } from '../session-context.js';
|
|
3
|
+
import { type ParsedArgs } from './types.js';
|
|
4
|
+
import { assertParseableUrl, assertScrollDestination, assertSweepBounds, parsePositiveNumber, parseDoAction, type SweepAxisName } from './leaf-grammar.js';
|
|
3
5
|
|
|
4
|
-
// Every flag below is matched as `--flag` followed by a separate `next`
|
|
5
|
-
// token (space-separated). To accept the equally-common `--flag=value` form
|
|
6
|
-
// without duplicating every branch, split any `--flag=value` token into two
|
|
7
|
-
// tokens up front so the rest of the parser only ever has to handle one
|
|
8
|
-
// convention.
|
|
9
6
|
export function expandEqualsFlags(argv: string[]): string[] {
|
|
10
7
|
const out: string[] = [];
|
|
11
8
|
for (const arg of argv) {
|
|
12
9
|
if (arg.startsWith('--') && arg.includes('=')) {
|
|
13
10
|
const eq = arg.indexOf('=');
|
|
14
11
|
out.push(arg.slice(0, eq), arg.slice(eq + 1));
|
|
15
|
-
} else
|
|
16
|
-
out.push(arg);
|
|
17
|
-
}
|
|
12
|
+
} else out.push(arg);
|
|
18
13
|
}
|
|
19
14
|
return out;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
const VALUE_FLAGS = new Set([
|
|
18
|
+
'--port', '--out', '--duration', '--settle', '--file', '--target', '--url', '--into', '--viewport', '--session',
|
|
19
|
+
'--filter', '--name', '--filter-url', '--filter-status', '--filter-method', '--limit', '--params', '--wait-event',
|
|
20
|
+
'--timeout', '--socket', '--settle-timeout', '--state', '--for', '--before', '--after', '--snap', '--set-file',
|
|
21
|
+
'--axis', '--from', '--to', '--viewport-height', '--rec-id', '--selector', '--do', '--element', '--prop', '--action', '--occurrence',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function valueFor(flag: string, next: string | undefined): string {
|
|
25
|
+
if (next === undefined || next.startsWith('--')) throw invalidInput(`${flag} requires a value.`);
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function integer(token: string, flag: string, min: number, max: number, safe = false): number {
|
|
30
|
+
// Full unsigned decimal integer grammar (leading zeros allowed); no sign,
|
|
31
|
+
// exponent, decimal point, partial token, or non-finite value.
|
|
32
|
+
if (!/^\d+$/.test(token)) throw invalidInput(`Invalid ${flag}: ${token}.`);
|
|
33
|
+
const value = Number(token);
|
|
34
|
+
if (!Number.isInteger(value) || value < min || value > max || (safe && !Number.isSafeInteger(value))) {
|
|
35
|
+
throw invalidInput(`Invalid ${flag}: ${token}.`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function durationMs(token: string): number {
|
|
41
|
+
// Full unsigned decimal-seconds grammar (`1`, `1.`, `.5`, `00.5`); no sign,
|
|
42
|
+
// exponent, partial token, or non-finite value. Compare the decimal token
|
|
43
|
+
// before Number conversion so precision rounding cannot admit an overflow.
|
|
44
|
+
if (!/^(?:\d+\.?\d*|\.\d+)$/.test(token)) throw invalidInput(`Invalid --duration: ${token}.`);
|
|
45
|
+
const [whole = '', fraction = ''] = token.split('.');
|
|
46
|
+
const normalizedWhole = whole.replace(/^0+/, '') || '0';
|
|
47
|
+
const maxWhole = '2147483';
|
|
48
|
+
const wholeComparison = normalizedWhole.length - maxWhole.length || normalizedWhole.localeCompare(maxWhole);
|
|
49
|
+
const maxFraction = '647';
|
|
50
|
+
const fractionComparison = fraction.slice(0, maxFraction.length).padEnd(maxFraction.length, '0').localeCompare(maxFraction);
|
|
51
|
+
if (wholeComparison > 0 || (wholeComparison === 0 && (fractionComparison > 0 || (fractionComparison === 0 && /[1-9]/.test(fraction.slice(maxFraction.length)))))) {
|
|
52
|
+
throw invalidInput(`Invalid --duration: ${token}.`);
|
|
53
|
+
}
|
|
54
|
+
return Number(token) * 1000;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateKnownLeaf(command: string, positional: readonly string[]): void {
|
|
58
|
+
const leaf = positional[0];
|
|
59
|
+
if (leaf === undefined) return;
|
|
60
|
+
const branches: Record<string, readonly string[]> = {
|
|
61
|
+
session: ['start', 'stop', 'list', 'view', 'har', 'log'],
|
|
62
|
+
page: ['click', 'type', 'scroll', 'navigate', 'exec', 'shot', 'elements'],
|
|
63
|
+
tab: ['list', 'open', 'reset', 'network'],
|
|
64
|
+
measure: ['snap', 'check', 'diff', 'census', 'explain', 'sweep', 'map'],
|
|
65
|
+
motion: ['rec', 'mask', 'timeline', 'jank', 'response'],
|
|
66
|
+
lib: ['list', 'search', 'show', 'read'],
|
|
67
|
+
};
|
|
68
|
+
const known = branches[command];
|
|
69
|
+
if (known && !known.includes(leaf)) throw invalidInput(`Unknown ${command} leaf: ${leaf}.`, 'unknown_command');
|
|
70
|
+
if (command === 'measure' && leaf === 'map' && positional[1] !== undefined) {
|
|
71
|
+
const mapLeaf = positional[1];
|
|
72
|
+
if (!['focus', 'scroll', 'layers', 'ax'].includes(mapLeaf)) throw invalidInput(`Unknown measure map leaf: ${mapLeaf}.`, 'unknown_command');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requireCount(values: readonly string[], min: number, max: number, command: string): void {
|
|
77
|
+
if (values.length < min || values.length > max) {
|
|
78
|
+
const expected = min === max ? `exactly ${min}` : `${min}..${max}`;
|
|
79
|
+
throw invalidInput(`${command} received ${values.length} positional argument(s); expected ${expected}.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Pure branch/leaf validation. It must stay before session and environment resolution. */
|
|
84
|
+
export function validateCliInvocation(parsed: ParsedArgs): void {
|
|
85
|
+
validateKnownLeaf(parsed.command, parsed.positional);
|
|
86
|
+
if (parsed.help) return;
|
|
87
|
+
if (parsed.command === 'cdp') {
|
|
88
|
+
requireCount(parsed.positional, 0, 1, 'cdp');
|
|
89
|
+
if (parsed.positional.length === 0 && !parsed.waitEvent) throw invalidInput('cdp requires a method or --wait-event.');
|
|
90
|
+
if (parsed.params !== undefined) {
|
|
91
|
+
try {
|
|
92
|
+
const value = JSON.parse(parsed.params) as unknown;
|
|
93
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('not an object');
|
|
94
|
+
} catch {
|
|
95
|
+
throw invalidInput('--params must be a JSON object.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (parsed.positional[0] === undefined) return;
|
|
101
|
+
|
|
102
|
+
const leaf = parsed.positional[0];
|
|
103
|
+
const values = parsed.positional.slice(1);
|
|
104
|
+
if (parsed.command === 'session') {
|
|
105
|
+
const ranges: Record<string, readonly [number, number]> = {
|
|
106
|
+
start: [0, 0], stop: [1, 1], list: [0, 0], view: [1, 1], har: [0, 1], log: [1, 1],
|
|
107
|
+
};
|
|
108
|
+
const [min, max] = ranges[leaf];
|
|
109
|
+
requireCount(values, min, max, `session ${leaf}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (parsed.command === 'page') {
|
|
114
|
+
if (leaf === 'click' || leaf === 'type' || leaf === 'navigate') requireCount(values, 1, 1, `page ${leaf}`);
|
|
115
|
+
if (leaf === 'navigate') assertParseableUrl(values[0], 'URL');
|
|
116
|
+
if (leaf === 'scroll') {
|
|
117
|
+
requireCount(values, 1, 1, 'page scroll');
|
|
118
|
+
if (parsed.to === undefined) throw invalidInput('page scroll requires --to <top|bottom|px>.');
|
|
119
|
+
assertScrollDestination(parsed.to);
|
|
120
|
+
}
|
|
121
|
+
if (leaf === 'exec') requireCount(values, parsed.file === undefined ? 1 : 0, parsed.file === undefined ? 1 : 0, 'page exec');
|
|
122
|
+
if (leaf === 'shot' || leaf === 'elements') requireCount(values, 0, 0, `page ${leaf}`);
|
|
123
|
+
if (leaf === 'shot' && parsed.viewport !== undefined) {
|
|
124
|
+
const match = /^([1-9]\d*)x([1-9]\d*)$/.exec(parsed.viewport);
|
|
125
|
+
if (!match || !Number.isSafeInteger(Number(match[1])) || !Number.isSafeInteger(Number(match[2]))) {
|
|
126
|
+
throw invalidInput(`Invalid --viewport: ${parsed.viewport}.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parsed.command === 'tab') {
|
|
133
|
+
requireCount(values, leaf === 'list' ? 0 : 1, leaf === 'list' ? 0 : 1, `tab ${leaf}`);
|
|
134
|
+
if (leaf === 'network' && values[0] !== 'offline' && values[0] !== 'online') {
|
|
135
|
+
throw invalidInput('tab network requires offline or online.');
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (parsed.command === 'measure') {
|
|
141
|
+
if (leaf === 'snap' || leaf === 'check' || leaf === 'sweep') requireCount(values, 0, 1, `measure ${leaf}`);
|
|
142
|
+
if (leaf === 'diff' || leaf === 'census') requireCount(values, 0, 0, `measure ${leaf}`);
|
|
143
|
+
if (leaf === 'diff' && (!parsed.before || !parsed.after)) throw invalidInput('measure diff requires --before and --after.');
|
|
144
|
+
if (leaf === 'census' && !['color', 'font', 'spacing', 'radius', 'shadow', 'animation', 'geometry', 'media', 'queries'].includes(parsed.axis ?? '')) {
|
|
145
|
+
throw invalidInput('measure census requires a documented --axis value.');
|
|
146
|
+
}
|
|
147
|
+
if (leaf === 'explain') {
|
|
148
|
+
requireCount(values, 1, 1, 'measure explain');
|
|
149
|
+
if (!parsed.selector?.trim()) throw invalidInput('measure explain requires --selector.');
|
|
150
|
+
}
|
|
151
|
+
if (leaf === 'sweep') {
|
|
152
|
+
if (!['width', 'dpr', 'zoom', 'color-scheme', 'reduced-motion'].includes(parsed.axis ?? '')) {
|
|
153
|
+
throw invalidInput('measure sweep requires a documented --axis value.');
|
|
154
|
+
}
|
|
155
|
+
assertSweepBounds(parsed.axis as SweepAxisName, parsed.from, parsed.to);
|
|
156
|
+
if (parsed.viewportHeight !== undefined) parsePositiveNumber(parsed.viewportHeight, 1, '--viewport-height');
|
|
157
|
+
}
|
|
158
|
+
if (leaf === 'map') {
|
|
159
|
+
const mapLeaf = values[0];
|
|
160
|
+
if (mapLeaf === undefined) return;
|
|
161
|
+
const targets = values.slice(1);
|
|
162
|
+
requireCount(targets, mapLeaf === 'focus' || mapLeaf === 'ax' ? 1 : 0, 1, `measure map ${mapLeaf}`);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (parsed.command === 'motion') {
|
|
168
|
+
if (leaf === 'mask' || leaf === 'jank' || leaf === 'response') requireCount(values, 1, 1, `motion ${leaf}`);
|
|
169
|
+
if (leaf === 'timeline') {
|
|
170
|
+
requireCount(values, 1, 1, 'motion timeline');
|
|
171
|
+
if (!parsed.element?.trim()) throw invalidInput('motion timeline requires --element.');
|
|
172
|
+
}
|
|
173
|
+
if (leaf === 'rec') {
|
|
174
|
+
if (parsed.start && parsed.stop) throw invalidInput('motion rec cannot combine --start and --stop.');
|
|
175
|
+
if (!parsed.stop && parsed.recId) throw invalidInput('motion rec accepts --rec-id only with --stop.');
|
|
176
|
+
if (parsed.start || parsed.stop) {
|
|
177
|
+
requireCount(values, 0, 0, 'motion rec lifecycle');
|
|
178
|
+
if (parsed.do || parsed.duration !== undefined || (parsed.stop && parsed.viewport !== undefined)) {
|
|
179
|
+
throw invalidInput('motion rec lifecycle flags cannot be combined with one-shot flags.');
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
requireCount(values, 1, 1, 'motion rec');
|
|
183
|
+
if (!parsed.do) throw invalidInput('motion rec one-shot requires --do.');
|
|
184
|
+
assertParseableUrl(values[0], 'recording URL');
|
|
185
|
+
parseDoAction(parsed.do);
|
|
186
|
+
}
|
|
187
|
+
if (parsed.viewport !== undefined) {
|
|
188
|
+
const match = /^([1-9]\d*)x([1-9]\d*)$/.exec(parsed.viewport);
|
|
189
|
+
if (!match || !Number.isSafeInteger(Number(match[1])) || !Number.isSafeInteger(Number(match[2]))) {
|
|
190
|
+
throw invalidInput(`Invalid --viewport: ${parsed.viewport}.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (parsed.command === 'lib') {
|
|
198
|
+
if (leaf === 'list') requireCount(values, 0, 0, 'lib list');
|
|
199
|
+
if (leaf === 'search' || leaf === 'show') requireCount(values, 1, 1, `lib ${leaf}`);
|
|
200
|
+
if (leaf === 'read') requireCount(values, 1, Number.MAX_SAFE_INTEGER, 'lib read');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Pure syntax/provenance parsing: no session or environment reads. */
|
|
205
|
+
export function parseCliSyntax(argv: string[]): ParsedArgs {
|
|
23
206
|
const command = argv[0] ?? '';
|
|
24
207
|
const rest = expandEqualsFlags(argv.slice(1));
|
|
25
208
|
const positional: string[] = [];
|
|
@@ -28,209 +211,121 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
|
|
|
28
211
|
for (let i = 0; i < rest.length; i++) {
|
|
29
212
|
const arg = rest[i];
|
|
30
213
|
const next = rest[i + 1];
|
|
214
|
+
if (VALUE_FLAGS.has(arg)) valueFor(arg, next);
|
|
31
215
|
|
|
32
|
-
if (arg === '--port'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
parsed.fullPage = true;
|
|
91
|
-
} else if (arg === '--height' && next) {
|
|
92
|
-
parsed.height = parseInt(next, 10);
|
|
93
|
-
i++;
|
|
94
|
-
} else if (arg === '--filter-url' && next) {
|
|
95
|
-
parsed.filterUrl = next;
|
|
96
|
-
i++;
|
|
97
|
-
} else if (arg === '--filter-status' && next) {
|
|
98
|
-
parsed.filterStatus = next;
|
|
99
|
-
i++;
|
|
100
|
-
} else if (arg === '--filter-method' && next) {
|
|
101
|
-
parsed.filterMethod = next.toUpperCase();
|
|
102
|
-
i++;
|
|
103
|
-
} else if (arg === '--limit' && next) {
|
|
104
|
-
parsed.limit = parseInt(next, 10);
|
|
105
|
-
i++;
|
|
106
|
-
} else if (arg === '--browser') {
|
|
107
|
-
parsed.browser = true;
|
|
108
|
-
} else if (arg === '--params' && next) {
|
|
109
|
-
parsed.params = next;
|
|
110
|
-
i++;
|
|
111
|
-
} else if (arg === '--wait-event' && next) {
|
|
112
|
-
parsed.waitEvent = next;
|
|
113
|
-
i++;
|
|
114
|
-
} else if (arg === '--timeout' && next) {
|
|
115
|
-
parsed.timeoutMs = parseInt(next, 10);
|
|
116
|
-
i++;
|
|
117
|
-
} else if (arg === '--socket' && next) {
|
|
118
|
-
parsed.socket = next;
|
|
119
|
-
i++;
|
|
120
|
-
} else if (arg === '--freeze-animations') {
|
|
121
|
-
parsed.freezeAnimations = true;
|
|
122
|
-
} else if (arg === '--settle-timeout' && next) {
|
|
123
|
-
parsed.settleTimeout = parseInt(next, 10);
|
|
124
|
-
i++;
|
|
125
|
-
} else if (arg === '--capture-unsettled') {
|
|
126
|
-
parsed.captureUnsettled = true;
|
|
127
|
-
} else if (arg === '--pixels') {
|
|
128
|
-
parsed.pixels = true;
|
|
129
|
-
} else if (arg === '--state' && next) {
|
|
130
|
-
(parsed.state ??= []).push(next);
|
|
131
|
-
i++;
|
|
132
|
-
} else if (arg === '--for' && next) {
|
|
133
|
-
parsed.for = next;
|
|
134
|
-
i++;
|
|
135
|
-
} else if (arg === '--before' && next) {
|
|
136
|
-
parsed.before = next;
|
|
137
|
-
i++;
|
|
138
|
-
} else if (arg === '--after' && next) {
|
|
139
|
-
parsed.after = next;
|
|
140
|
-
i++;
|
|
141
|
-
} else if (arg === '--full') {
|
|
142
|
-
parsed.full = true;
|
|
143
|
-
} else if (arg === '--gate') {
|
|
144
|
-
parsed.gate = true;
|
|
145
|
-
} else if (arg === '--snap' && next) {
|
|
146
|
-
(parsed.snap ??= []).push(next);
|
|
147
|
-
i++;
|
|
148
|
-
} else if (arg === '--set-file' && next) {
|
|
149
|
-
parsed.setFile = next;
|
|
150
|
-
i++;
|
|
151
|
-
} else if (arg === '--axis' && next) {
|
|
152
|
-
parsed.axis = next;
|
|
153
|
-
i++;
|
|
154
|
-
} else if (arg === '--from' && next) {
|
|
155
|
-
// Raw string — axis units vary (width px / dpr float / zoom); parseInt
|
|
156
|
-
// would lossily truncate a non-integer axis value.
|
|
157
|
-
parsed.from = next;
|
|
158
|
-
i++;
|
|
159
|
-
} else if (arg === '--to' && next) {
|
|
160
|
-
parsed.to = next;
|
|
161
|
-
i++;
|
|
162
|
-
} else if (arg === '--viewport-height' && next) {
|
|
163
|
-
parsed.viewportHeight = next;
|
|
164
|
-
i++;
|
|
165
|
-
} else if (arg === '--rec-id' && next) {
|
|
166
|
-
parsed.recId = next;
|
|
167
|
-
i++;
|
|
168
|
-
} else if (arg === '--selector' && next) {
|
|
169
|
-
parsed.selector = next;
|
|
170
|
-
i++;
|
|
171
|
-
} else if (arg === '--size') {
|
|
172
|
-
parsed.size = true;
|
|
173
|
-
} else if (arg === '--text') {
|
|
174
|
-
parsed.text = true;
|
|
175
|
-
} else if (arg === '--form') {
|
|
176
|
-
parsed.form = true;
|
|
177
|
-
} else if (arg === '--start') {
|
|
178
|
-
parsed.start = true;
|
|
179
|
-
} else if (arg === '--stop') {
|
|
180
|
-
parsed.stop = true;
|
|
181
|
-
} else if (arg === '--do' && next) {
|
|
182
|
-
parsed.do = next;
|
|
183
|
-
i++;
|
|
184
|
-
} else if (arg === '--element' && next) {
|
|
185
|
-
parsed.element = next;
|
|
186
|
-
i++;
|
|
187
|
-
} else if (arg === '--prop' && next) {
|
|
188
|
-
parsed.prop = next;
|
|
189
|
-
i++;
|
|
190
|
-
} else if (arg === '--action' && next) {
|
|
191
|
-
parsed.action = next;
|
|
192
|
-
i++;
|
|
193
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
194
|
-
parsed.help = true;
|
|
195
|
-
} else if (arg.startsWith('--')) {
|
|
196
|
-
console.error(`Unknown flag: ${arg}`);
|
|
197
|
-
process.exit(1);
|
|
198
|
-
} else {
|
|
199
|
-
positional.push(arg);
|
|
200
|
-
}
|
|
216
|
+
if (arg === '--port') { parsed.port = integer(valueFor(arg, next), '--port', 1, 65535); i++; }
|
|
217
|
+
else if (arg === '--out') { parsed.out = valueFor(arg, next); i++; }
|
|
218
|
+
else if (arg === '--json') parsed.json = true;
|
|
219
|
+
else if (arg === '--duration') { parsed.duration = durationMs(valueFor(arg, next)); i++; }
|
|
220
|
+
else if (arg === '--settle') { parsed.settle = integer(valueFor(arg, next), '--settle', 0, 2_147_483_647); i++; }
|
|
221
|
+
else if (arg === '--file') { parsed.file = valueFor(arg, next); i++; }
|
|
222
|
+
else if (arg === '--new') parsed.new = true;
|
|
223
|
+
else if (arg === '--target') { parsed.target = valueFor(arg, next); parsed.targetSource = 'flag'; i++; }
|
|
224
|
+
else if (arg === '--url') { const value = valueFor(arg, next); parsed.url = value; (parsed.urls ??= []).push(value); i++; }
|
|
225
|
+
else if (arg === '--into') { parsed.into = valueFor(arg, next); i++; }
|
|
226
|
+
else if (arg === '--no-screenshot') parsed.noScreenshot = true;
|
|
227
|
+
else if (arg === '--viewport') { const value = valueFor(arg, next); parsed.viewport = value; (parsed.viewports ??= []).push(value); i++; }
|
|
228
|
+
else if (arg === '--full-page') parsed.fullPage = true;
|
|
229
|
+
else if (arg === '--all') parsed.all = true;
|
|
230
|
+
else if (arg === '--session') { parsed.session = valueFor(arg, next); i++; }
|
|
231
|
+
else if (arg === '--hold') parsed.hold = true;
|
|
232
|
+
else if (arg === '--filter') { parsed.filter = valueFor(arg, next); i++; }
|
|
233
|
+
else if (arg === '--name') { parsed.name = valueFor(arg, next); i++; }
|
|
234
|
+
else if (arg === '--filter-url') { parsed.filterUrl = valueFor(arg, next); i++; }
|
|
235
|
+
else if (arg === '--filter-status') { parsed.filterStatus = valueFor(arg, next); i++; }
|
|
236
|
+
else if (arg === '--filter-method') { parsed.filterMethod = valueFor(arg, next).toUpperCase(); i++; }
|
|
237
|
+
else if (arg === '--limit') { parsed.limit = integer(valueFor(arg, next), '--limit', 1, Number.MAX_SAFE_INTEGER, true); i++; }
|
|
238
|
+
else if (arg === '--browser') parsed.browser = true;
|
|
239
|
+
else if (arg === '--params') { parsed.params = valueFor(arg, next); i++; }
|
|
240
|
+
else if (arg === '--wait-event') { parsed.waitEvent = valueFor(arg, next); i++; }
|
|
241
|
+
else if (arg === '--timeout') { parsed.timeoutMs = integer(valueFor(arg, next), '--timeout', 1, 2_147_483_647); i++; }
|
|
242
|
+
else if (arg === '--socket') { parsed.socket = valueFor(arg, next); i++; }
|
|
243
|
+
else if (arg === '--freeze-animations') parsed.freezeAnimations = true;
|
|
244
|
+
else if (arg === '--settle-timeout') { parsed.settleTimeout = integer(valueFor(arg, next), '--settle-timeout', 1, 2_147_483_647); i++; }
|
|
245
|
+
else if (arg === '--capture-unsettled') parsed.captureUnsettled = true;
|
|
246
|
+
else if (arg === '--pixels') parsed.pixels = true;
|
|
247
|
+
else if (arg === '--state') { (parsed.state ??= []).push(valueFor(arg, next)); i++; }
|
|
248
|
+
else if (arg === '--for') { parsed.for = valueFor(arg, next); i++; }
|
|
249
|
+
else if (arg === '--before') { parsed.before = valueFor(arg, next); i++; }
|
|
250
|
+
else if (arg === '--after') { parsed.after = valueFor(arg, next); i++; }
|
|
251
|
+
else if (arg === '--full') parsed.full = true;
|
|
252
|
+
else if (arg === '--gate') parsed.gate = true;
|
|
253
|
+
else if (arg === '--snap') { (parsed.snap ??= []).push(valueFor(arg, next)); i++; }
|
|
254
|
+
else if (arg === '--set-file') { parsed.setFile = valueFor(arg, next); i++; }
|
|
255
|
+
else if (arg === '--axis') { parsed.axis = valueFor(arg, next); i++; }
|
|
256
|
+
else if (arg === '--from') { parsed.from = valueFor(arg, next); i++; }
|
|
257
|
+
else if (arg === '--to') { parsed.to = valueFor(arg, next); i++; }
|
|
258
|
+
else if (arg === '--viewport-height') { parsed.viewportHeight = valueFor(arg, next); i++; }
|
|
259
|
+
else if (arg === '--rec-id') { parsed.recId = valueFor(arg, next); i++; }
|
|
260
|
+
else if (arg === '--selector') { parsed.selector = valueFor(arg, next); i++; }
|
|
261
|
+
else if (arg === '--size') parsed.size = true;
|
|
262
|
+
else if (arg === '--text') parsed.text = true;
|
|
263
|
+
else if (arg === '--form') parsed.form = true;
|
|
264
|
+
else if (arg === '--start') parsed.start = true;
|
|
265
|
+
else if (arg === '--stop') parsed.stop = true;
|
|
266
|
+
else if (arg === '--do') { parsed.do = valueFor(arg, next); i++; }
|
|
267
|
+
else if (arg === '--element') { parsed.element = valueFor(arg, next); i++; }
|
|
268
|
+
else if (arg === '--prop') { parsed.prop = valueFor(arg, next); i++; }
|
|
269
|
+
else if (arg === '--action') { parsed.action = valueFor(arg, next); i++; }
|
|
270
|
+
else if (arg === '--occurrence') { parsed.occurrence = integer(valueFor(arg, next), '--occurrence', 1, Number.MAX_SAFE_INTEGER, true); i++; }
|
|
271
|
+
else if (arg === '-h') parsed.help = true;
|
|
272
|
+
else if (arg.startsWith('--')) throw invalidInput(`Unknown flag: ${arg}.`, 'unknown_flag');
|
|
273
|
+
else positional.push(arg);
|
|
201
274
|
}
|
|
202
275
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
276
|
+
return parsed as ParsedArgs;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Decides whether an invocation needs CDP endpoint (port/target) resolution.
|
|
281
|
+
* Bare branches (`session`, `page`, ... with no leaf) and `measure map` with no
|
|
282
|
+
* sub-leaf only render help. `lib` never touches a browser. Of the `session`
|
|
283
|
+
* lifecycle leaves, only `start` connects to CDP — `stop`, `list`, `view`,
|
|
284
|
+
* `har`, and `log` finalize/read session state and must NOT parse `CDP_PORT`
|
|
285
|
+
* or read/clean the active-session index (a malformed ambient `CDP_PORT` must
|
|
286
|
+
* never block `session stop <id>` from finalizing).
|
|
287
|
+
*/
|
|
288
|
+
function needsEndpointResolution(parsed: ParsedArgs): boolean {
|
|
289
|
+
if (parsed.help || parsed.command === 'lib') return false;
|
|
290
|
+
const branchOnly = ['session', 'page', 'tab', 'measure', 'motion'].includes(parsed.command)
|
|
291
|
+
&& (parsed.positional.length === 0 || (parsed.command === 'measure' && parsed.positional.length === 1 && parsed.positional[0] === 'map'));
|
|
292
|
+
if (branchOnly) return false;
|
|
293
|
+
if (parsed.command === 'session' && parsed.positional[0] !== 'start') return false;
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Hydrates endpoint/session defaults only after pure invocation validation
|
|
299
|
+
* succeeds. Endpoint precedence is `explicit flag > session endpoint > env`:
|
|
300
|
+
* the active session is hydrated FIRST, and the ambient `CDP_PORT` token is
|
|
301
|
+
* parsed only when neither an explicit `--port` nor a session port supplies
|
|
302
|
+
* one — so a malformed `CDP_PORT` can never reject a command whose session
|
|
303
|
+
* already holds the authoritative port.
|
|
304
|
+
*/
|
|
305
|
+
export function resolveCliContext(parsed: ParsedArgs): ParsedArgs {
|
|
306
|
+
if (!needsEndpointResolution(parsed)) return parsed;
|
|
307
|
+
const session = getActiveSession({ cleanStale: false });
|
|
211
308
|
if (session) {
|
|
212
309
|
if (!parsed.har && session.harId) parsed.har = session.harId;
|
|
213
310
|
// An explicit --url picks a different (parallel) tab than the session's
|
|
214
311
|
// own — don't let the session's targetId clobber that choice.
|
|
215
|
-
if (!parsed.target && !parsed.url && session.targetId) parsed.target = session.targetId;
|
|
312
|
+
if (!parsed.target && !parsed.url && session.targetId) { parsed.target = session.targetId; parsed.targetSource = 'session'; }
|
|
313
|
+
// A session target is inseparable from the endpoint that created it.
|
|
314
|
+
// Prefer that endpoint over ambient CDP_PORT, but retain an explicit
|
|
315
|
+
// --port override for deliberate multi-browser work.
|
|
316
|
+
if (parsed.port === undefined && session.port !== undefined && session.port !== null) parsed.port = session.port;
|
|
216
317
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// orchestrators that aren't using the session concept).
|
|
220
|
-
if (!parsed.port && process.env.CDP_PORT) {
|
|
221
|
-
const envPort = Number.parseInt(process.env.CDP_PORT, 10);
|
|
222
|
-
if (Number.isNaN(envPort)) {
|
|
223
|
-
console.error(`Invalid CDP_PORT: ${process.env.CDP_PORT}`);
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
parsed.port = envPort;
|
|
227
|
-
}
|
|
228
|
-
if (!parsed.target && !parsed.url && process.env.CDP_TARGET) {
|
|
229
|
-
parsed.target = process.env.CDP_TARGET;
|
|
230
|
-
}
|
|
231
|
-
if (!parsed.har && process.env.CDP_HAR_ID) {
|
|
232
|
-
parsed.har = process.env.CDP_HAR_ID;
|
|
318
|
+
if (parsed.port === undefined && process.env.CDP_PORT !== undefined) {
|
|
319
|
+
parsed.port = integer(process.env.CDP_PORT, 'CDP_PORT', 1, 65535);
|
|
233
320
|
}
|
|
321
|
+
// Preserve historical stale-index cleanup once endpoint validation has
|
|
322
|
+
// succeeded; only the malformed-env throw path deliberately leaves it alone.
|
|
323
|
+
if (!session) getActiveSession();
|
|
324
|
+
if (!parsed.target && !parsed.url && process.env.CDP_TARGET) { parsed.target = process.env.CDP_TARGET; parsed.targetSource = 'env'; }
|
|
325
|
+
return parsed;
|
|
326
|
+
}
|
|
234
327
|
|
|
235
|
-
|
|
328
|
+
/** Public pure parser used by command-level tests and programmatic branch callers. */
|
|
329
|
+
export function parseCliArgs(argv: string[]): ParsedArgs {
|
|
330
|
+
return parseCliSyntax(argv);
|
|
236
331
|
}
|