@crouton-kit/capture 1.4.22 → 1.4.24
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/README.md +2 -0
- package/bin/capture +611 -577
- package/package.json +1 -1
- package/src/capture.ts +1 -1
- package/src/cdp/args.ts +47 -1
- package/src/cdp/commands/measure/check.ts +8 -3
- package/src/cdp/measure/check.ts +3 -1
package/package.json
CHANGED
package/src/capture.ts
CHANGED
|
@@ -65,7 +65,7 @@ Selecting a noun: session, page, and tab address the live browser; measure, moti
|
|
|
65
65
|
|
|
66
66
|
I/O contract: flags and positionals on input; one rendered prose block on stdout. --json mirrors the same result as JSON, but the rendered block is the contract. stderr carries in-flight diagnostics only. --artifact-dir <path> selects the root for a session bundle (\`session start\`) or a sessionless artifact-producing leaf's one-shot bundle; leaf help states when it writes one. --out always names one caller-owned file, never a bundle directory.
|
|
67
67
|
|
|
68
|
-
A CDP-enabled browser must be running; \`capture tab list\` is the probe, and \`capture tab launch\` starts one capture owns and reaps when nothing is (never hand-roll a detached browser — nothing reaps that). Capture never signals a browser it did not start, so attaching to your own with --port is unchanged. CDP_PORT / CDP_TARGET pin the browser + tab for target-selecting commands — precedence: explicit flag > active session > env. \`session start\` adopts only an explicit --target.`;
|
|
68
|
+
A CDP-enabled browser must be running; \`capture tab list\` is the probe, and \`capture tab launch\` starts one capture owns and reaps when nothing is (never hand-roll a detached browser — nothing reaps that). Capture never signals a browser it did not start, so attaching to your own with --port is unchanged. CDP_PORT / CDP_TARGET pin the browser + tab for target-selecting commands — precedence: explicit flag > active session > env. \`session start\` adopts only an explicit --target. When CRTR_NODE_ID is set (a crtr agent node), a browser command with no --port, no --target and no active session of that node is refused instead of falling back to CDP_PORT, CDP_TARGET or localhost discovery, because the browser found could belong to another node: run \`capture session start --url <url>\`, or pass --port or --target. \`capture tab list\` and commands that only read a recorded artifact are not refused.`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function printVersion(): void {
|
package/src/cdp/args.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { invalidInput } from '../errors.js';
|
|
1
|
+
import { captureError, invalidInput } from '../errors.js';
|
|
2
2
|
import { getActiveSession } from '../session-context.js';
|
|
3
|
+
import { isUrlRef } from '../output/artifact.js';
|
|
3
4
|
import { type ParsedArgs } from './types.js';
|
|
4
5
|
import { assertParseableUrl, assertScrollDestination, assertSweepBounds, parsePositiveNumber, parseDoAction, type SweepAxisName } from './leaf-grammar.js';
|
|
5
6
|
|
|
@@ -557,6 +558,50 @@ function needsEndpointResolution(parsed: ParsedArgs): boolean {
|
|
|
557
558
|
return true;
|
|
558
559
|
}
|
|
559
560
|
|
|
561
|
+
/**
|
|
562
|
+
* False only for leaves that never open a CDP connection: `tab list` (the probe
|
|
563
|
+
* that finds a port to pass), and the leaves that read a recorded artifact by
|
|
564
|
+
* id or path. A leaf that also takes a URL (`measure check`, `measure census`,
|
|
565
|
+
* `measure map ax|focus|layers|scroll`) reaches a browser only when it is given
|
|
566
|
+
* one. Everything not named here counts as reaching a browser, so a new leaf is
|
|
567
|
+
* guarded until it is listed.
|
|
568
|
+
*/
|
|
569
|
+
function mayReachBrowser(parsed: ParsedArgs): boolean {
|
|
570
|
+
const [leaf, sub] = parsed.positional;
|
|
571
|
+
switch (parsed.command) {
|
|
572
|
+
case 'tab': return leaf !== 'list';
|
|
573
|
+
case 'motion': return leaf === 'rec';
|
|
574
|
+
case 'perf': return leaf === 'trace';
|
|
575
|
+
case 'heap': return leaf === 'snapshot';
|
|
576
|
+
case 'measure': {
|
|
577
|
+
if (leaf === 'diff' || leaf === 'explain' || leaf === 'text' || (leaf === 'map' && sub === 'paint')) return false;
|
|
578
|
+
if (leaf === 'check' || leaf === 'census' || leaf === 'map') return parsed.url !== undefined || parsed.positional.some(isUrlRef);
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
default: return true;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* A node (`CRTR_NODE_ID` set) may only drive a browser it chose: an explicit
|
|
587
|
+
* `--port` or `--target`, or the port/tab recorded by its own active session.
|
|
588
|
+
* Otherwise `CDP_PORT`, `CDP_TARGET` and localhost auto-discovery would accept
|
|
589
|
+
* whatever browser is there, which can be another node's signed-in browser.
|
|
590
|
+
* `session start` is the way to choose one, and `tab list` only lists, so
|
|
591
|
+
* neither is refused. With no `CRTR_NODE_ID` (a person at a shell) nothing here
|
|
592
|
+
* applies.
|
|
593
|
+
*/
|
|
594
|
+
function refuseUnownedEndpoint(parsed: ParsedArgs, sessionStart: boolean): void {
|
|
595
|
+
if (!process.env.CRTR_NODE_ID?.trim() || sessionStart || !mayReachBrowser(parsed)) return;
|
|
596
|
+
if (parsed.port !== undefined || parsed.targetSource === 'flag' || parsed.targetSource === 'session') return;
|
|
597
|
+
const command = leafCommand(parsed.command, parsed.positional);
|
|
598
|
+
throw captureError(
|
|
599
|
+
'precondition',
|
|
600
|
+
'no_owned_browser',
|
|
601
|
+
`\`capture ${command}\` was not run: this node (CRTR_NODE_ID) has no browser of its own. With no --port, no --target and no active session holding a browser, capture will not use CDP_PORT, CDP_TARGET or auto-discovery, because the browser it found could belong to another node. Do one of: (1) \`capture session start --url <url>\` to open and hold your own browser tab, or \`capture tab launch\` then \`capture session start --port <port>\`; (2) pass \`--port <port>\` (see \`capture tab list\`); (3) pass \`--target <tabId>\`.`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
560
605
|
/**
|
|
561
606
|
* Hydrates endpoint/session defaults only after pure invocation validation
|
|
562
607
|
* succeeds. Endpoint precedence is `explicit flag > session endpoint > env`:
|
|
@@ -582,6 +627,7 @@ export function resolveCliContext(parsed: ParsedArgs): ParsedArgs {
|
|
|
582
627
|
// --port override for deliberate multi-browser work.
|
|
583
628
|
if (parsed.port === undefined && session.port !== undefined && session.port !== null) { parsed.port = session.port; parsed.portSource = 'session'; }
|
|
584
629
|
}
|
|
630
|
+
refuseUnownedEndpoint(parsed, sessionStart);
|
|
585
631
|
if (parsed.port === undefined && process.env.CDP_PORT !== undefined) {
|
|
586
632
|
parsed.port = schemaInteger(leafCommand(parsed.command, parsed.positional), process.env.CDP_PORT, 'CDP_PORT', 1, 65535);
|
|
587
633
|
parsed.portSource = 'env';
|
|
@@ -152,11 +152,16 @@ export async function cmdMeasureCheck(parsed: ParsedArgs, _args: string[]): Prom
|
|
|
152
152
|
} catch (err) {
|
|
153
153
|
const resolution = err as Partial<ArtifactResolutionError>;
|
|
154
154
|
const detail = err instanceof Error ? err.message : 'unknown artifact read failure';
|
|
155
|
+
const unresolved = resolution.name === 'ArtifactResolutionError';
|
|
155
156
|
emitResult({
|
|
156
157
|
tag: 'error',
|
|
157
|
-
attrs: { command: 'measure check', status:
|
|
158
|
-
summary:
|
|
159
|
-
|
|
158
|
+
attrs: { command: 'measure check', status: unresolved ? 'artifact_unavailable' : 'check_failed' },
|
|
159
|
+
summary: unresolved
|
|
160
|
+
? fact`Measure check could not read the requested artifact: ${detail}`
|
|
161
|
+
: fact`Measure check failed while measuring the snapshot: ${detail}`,
|
|
162
|
+
followUp: unresolved
|
|
163
|
+
? text`Create a settled snapshot with capture measure snap <url>, then pass its id or absolute path.`
|
|
164
|
+
: text`The snapshot resolved; this is an internal capture check error, not a missing snapshot. Narrow --for to the checks you need, and report the error with the snapshot path.`,
|
|
160
165
|
}, { json: parsed.json });
|
|
161
166
|
process.exitCode = 1;
|
|
162
167
|
}
|
package/src/cdp/measure/check.ts
CHANGED
|
@@ -448,7 +448,9 @@ export function checkSnapshot(ref: SnapRef, requested: readonly CheckName[]): {
|
|
|
448
448
|
if (!e) continue;
|
|
449
449
|
const point = sample.points?.find((p) => {
|
|
450
450
|
const receiver = p.result?.topReceiver;
|
|
451
|
-
|
|
451
|
+
// The collector records `topReceiver: null` for a point with an empty hit stack (e.g. a sample outside the viewport); no receiver means nothing occludes it.
|
|
452
|
+
if (!receiver) return false;
|
|
453
|
+
const receiverElement = elements.find((x) => receiver.backendNodeId != null && x.backendNodeId === receiver.backendNodeId);
|
|
452
454
|
return receiverElement !== undefined && !isSelfOrDescendant(e, receiverElement);
|
|
453
455
|
})?.result;
|
|
454
456
|
if (!point?.topReceiver) continue;
|