@gravitylabsllc/porthole 0.1.0 → 0.2.1
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 +123 -0
- package/dist/adb.js +430 -21
- package/dist/adb.js.map +1 -1
- package/dist/args.js +144 -0
- package/dist/args.js.map +1 -0
- package/dist/capture.js +139 -30
- package/dist/capture.js.map +1 -1
- package/dist/cli.js +221 -62
- package/dist/cli.js.map +1 -1
- package/dist/device.js +337 -4
- package/dist/device.js.map +1 -1
- package/dist/index.js +2030 -377
- package/dist/index.js.map +1 -1
- package/dist/moment.js +240 -0
- package/dist/moment.js.map +1 -0
- package/dist/perfetto.js +826 -0
- package/dist/perfetto.js.map +1 -0
- package/dist/report.js +68 -7
- package/dist/report.js.map +1 -1
- package/dist/save.js +252 -0
- package/dist/save.js.map +1 -0
- package/dist/sessions.js +704 -0
- package/dist/sessions.js.map +1 -0
- package/dist/system.js +169 -0
- package/dist/system.js.map +1 -0
- package/dist/systrace.js +198 -0
- package/dist/systrace.js.map +1 -0
- package/dist/timeline.js +731 -29
- package/dist/timeline.js.map +1 -1
- package/dist/trace.js +317 -27
- package/dist/trace.js.map +1 -1
- package/dist/watermark.js +220 -0
- package/dist/watermark.js.map +1 -0
- package/package.json +10 -4
- package/src/adb.ts +583 -0
- package/src/args.ts +177 -0
- package/src/capture.ts +292 -0
- package/src/cli.ts +367 -0
- package/src/device.ts +635 -0
- package/src/index.ts +2545 -0
- package/src/moment.ts +306 -0
- package/src/perfetto.ts +972 -0
- package/src/report.ts +285 -0
- package/src/save.ts +322 -0
- package/src/sessions.ts +894 -0
- package/src/system.ts +221 -0
- package/src/systrace.ts +258 -0
- package/src/timeline.ts +1036 -0
- package/src/trace.ts +769 -0
- package/src/watermark.ts +337 -0
- package/ui/dist/assets/index-DtnyBXCM.css +1 -0
- package/ui/dist/assets/index-h7VNB9Fl.js +70 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index--1mlZuNZ.css +0 -1
- package/ui/dist/assets/index-BeVGHRFm.js +0 -68
package/dist/args.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Copyright 2026 Gravity Labs
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { TRACE_VERSION } from "./trace.js";
|
|
5
|
+
/**
|
|
6
|
+
* A port a device or a browser can plausibly reach. `--port` used to be
|
|
7
|
+
* `Number(argv[++i])` with no check at all: a missing value is `NaN`, and a
|
|
8
|
+
* `NaN` port is not a refusal, it is a listener that never connects to
|
|
9
|
+
* anything while printing nothing to say why. Below 1024 needs privileges
|
|
10
|
+
* this process does not have on most platforms; above 65535 does not exist;
|
|
11
|
+
* a fraction is a typo, not a port.
|
|
12
|
+
*/
|
|
13
|
+
export function parsePort(raw, option) {
|
|
14
|
+
if (raw === undefined || raw === "") {
|
|
15
|
+
return { message: `${option} needs a port number` };
|
|
16
|
+
}
|
|
17
|
+
// A port is a run of decimal digits, nothing else: `Number()` also accepts
|
|
18
|
+
// " 8677 " (trims whitespace), "1e4" (scientific notation) and "0x2000"
|
|
19
|
+
// (hex) as finite integers, none of which anyone typed on purpose.
|
|
20
|
+
if (/^-?\d+$/.test(raw)) {
|
|
21
|
+
const value = Number(raw);
|
|
22
|
+
if (value < 1024 || value > 65535) {
|
|
23
|
+
return { message: `${option} ${raw} is out of range (must be 1024-65535)` };
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
if (/^-?\d+\.\d+$/.test(raw)) {
|
|
28
|
+
return { message: `${option} ${raw} must be a whole number` };
|
|
29
|
+
}
|
|
30
|
+
return { message: `${option} ${JSON.stringify(raw)} is not a number` };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A device-uptime millisecond value — `porthole save`'s `--from`/`--to`.
|
|
34
|
+
* Same shape discipline as `parsePort`: a run of decimal digits only, so
|
|
35
|
+
* `"1e4"` or `" 100 "` (both finite per `Number()`) are refused rather than
|
|
36
|
+
* silently accepted. Unlike a port, 0 is a legitimate value (the very start
|
|
37
|
+
* of the uptime clock) and there is no upper bound — a session can run for
|
|
38
|
+
* days.
|
|
39
|
+
*/
|
|
40
|
+
export function parseMillis(raw, option) {
|
|
41
|
+
if (raw === undefined || raw === "") {
|
|
42
|
+
return { message: `${option} needs a millisecond value` };
|
|
43
|
+
}
|
|
44
|
+
if (/^\d+$/.test(raw))
|
|
45
|
+
return Number(raw);
|
|
46
|
+
if (/^\d+\.\d+$/.test(raw)) {
|
|
47
|
+
return { message: `${option} ${raw} must be a whole number` };
|
|
48
|
+
}
|
|
49
|
+
return { message: `${option} ${JSON.stringify(raw)} is not a number` };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A relative lookback for `porthole save --since`: `10m`, `90s`, `2h`, or a
|
|
53
|
+
* bare millisecond count — the ticket's own examples, all a single unit, so
|
|
54
|
+
* a compound duration like `1h30m` is refused rather than half-parsed.
|
|
55
|
+
* Returns milliseconds.
|
|
56
|
+
*/
|
|
57
|
+
export function parseDuration(raw, option) {
|
|
58
|
+
if (raw === undefined || raw === "") {
|
|
59
|
+
return { message: `${option} needs a duration (e.g. 10m, 90s, 2h, or a millisecond count)` };
|
|
60
|
+
}
|
|
61
|
+
const MULTIPLIER_MS = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000 };
|
|
62
|
+
const bare = /^\d+$/.test(raw) ? { value: Number(raw), unitMs: 1 } : null;
|
|
63
|
+
const suffixed = /^(\d+)(ms|s|m|h)$/.exec(raw);
|
|
64
|
+
const parsed = bare ?? (suffixed ? { value: Number(suffixed[1]), unitMs: MULTIPLIER_MS[suffixed[2]] } : null);
|
|
65
|
+
if (!parsed) {
|
|
66
|
+
return {
|
|
67
|
+
message: `${option} ${JSON.stringify(raw)} is not a duration (use 10m, 90s, 2h, or a millisecond count)`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (parsed.value <= 0) {
|
|
71
|
+
return { message: `${option} ${raw} must be positive` };
|
|
72
|
+
}
|
|
73
|
+
return parsed.value * parsed.unitMs;
|
|
74
|
+
}
|
|
75
|
+
const FAIL_ON_VALUES = ["nothing", "error", "regression"];
|
|
76
|
+
/**
|
|
77
|
+
* `--fail-on` used to be `argv[++i] as CaptureOptions["failOn"]` — a cast, not
|
|
78
|
+
* a check. A typo like `regresion` compiled, ran, and turned the CI gate off
|
|
79
|
+
* without saying a word: the worst failure mode here is a green build. This
|
|
80
|
+
* validates against the real union and names the accepted values so the typo
|
|
81
|
+
* is caught at the command line instead of at the postmortem.
|
|
82
|
+
*/
|
|
83
|
+
export function parseFailOn(raw) {
|
|
84
|
+
if (raw !== undefined && FAIL_ON_VALUES.includes(raw)) {
|
|
85
|
+
return raw;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
message: `--fail-on must be one of: ${FAIL_ON_VALUES.join(", ")} (got ${JSON.stringify(raw ?? null)})`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A required string option's value must actually be there — and must not be
|
|
93
|
+
* the next flag left dangling because this one's value was omitted.
|
|
94
|
+
* `--scenario` at the end of the command line and `--scenario --port 8677`
|
|
95
|
+
* were both silently accepted before: the first became `undefined` with no
|
|
96
|
+
* complaint, the second swallowed `--port` as the scenario name and left
|
|
97
|
+
* `8677` to be rejected later as a nonsense option, blaming the wrong flag.
|
|
98
|
+
*/
|
|
99
|
+
export function requiredValue(raw, option) {
|
|
100
|
+
if (raw === undefined || raw === "--" || raw.startsWith("--")) {
|
|
101
|
+
return { message: `${option} needs a value` };
|
|
102
|
+
}
|
|
103
|
+
return raw;
|
|
104
|
+
}
|
|
105
|
+
/** Thrown by readTrace; the message is written straight to stderr, so it earns its keep alone. */
|
|
106
|
+
export class TraceReadError extends Error {
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reads and validates a trace file, refusing anything that is not one, rather
|
|
110
|
+
* than letting a missing file, truncated JSON, or a trace from a version this
|
|
111
|
+
* build does not understand fall through as an unhandled rejection and a raw
|
|
112
|
+
* stack trace — which is what a CI operator would have gotten instead of the
|
|
113
|
+
* one sentence they need.
|
|
114
|
+
*/
|
|
115
|
+
export async function readTrace(file) {
|
|
116
|
+
let content;
|
|
117
|
+
try {
|
|
118
|
+
content = await readFile(file, "utf8");
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const code = error.code;
|
|
122
|
+
if (code === "ENOENT")
|
|
123
|
+
throw new TraceReadError(`no such file: ${file}`);
|
|
124
|
+
throw new TraceReadError(`could not read ${file}: ${error.message}`);
|
|
125
|
+
}
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = JSON.parse(content);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new TraceReadError(`${file} is not valid JSON`);
|
|
132
|
+
}
|
|
133
|
+
if (typeof parsed !== "object" ||
|
|
134
|
+
parsed === null ||
|
|
135
|
+
typeof parsed.porthole !== "number") {
|
|
136
|
+
throw new TraceReadError(`${file} is not a porthole trace (missing "porthole" version field)`);
|
|
137
|
+
}
|
|
138
|
+
const version = parsed.porthole;
|
|
139
|
+
if (version !== TRACE_VERSION) {
|
|
140
|
+
throw new TraceReadError(`${file} is trace version ${version}, which this build (version ${TRACE_VERSION}) does not understand`);
|
|
141
|
+
}
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/capture.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Copyright 2026 Gravity Labs
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import {
|
|
5
|
-
import { DeviceClient } from "./device.js";
|
|
6
|
-
import { renderComparison, renderReport } from "./report.js";
|
|
7
|
-
import { buildTrace } from "./trace.js";
|
|
4
|
+
import { writeFile } from "node:fs/promises";
|
|
5
|
+
import { DeviceClient, isConnected } from "./device.js";
|
|
6
|
+
import { renderComparison, renderReport, shouldColor } from "./report.js";
|
|
7
|
+
import { buildTrace, resolveProfile } from "./trace.js";
|
|
8
|
+
import { parseFailOn, parsePort, readTrace, requiredValue } from "./args.js";
|
|
8
9
|
export const CAPTURE_USAGE = `
|
|
9
10
|
porthole capture — record a run and write a trace
|
|
10
11
|
|
|
@@ -26,14 +27,33 @@ porthole capture — record a run and write a trace
|
|
|
26
27
|
The command runs to completion with the porthole recording. Its exit code is
|
|
27
28
|
passed through unless --fail-on fires first.
|
|
28
29
|
`;
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Waits for the device to answer, so a capture does not silently record
|
|
32
|
+
* nothing.
|
|
33
|
+
*
|
|
34
|
+
* GRA-157 AC5: this used to resolve the instant the socket connected, before
|
|
35
|
+
* hello had a chance to land — DeviceClient set state = "connected" on
|
|
36
|
+
* socket connect and issued the hello request without awaiting it, so a
|
|
37
|
+
* capture that started recording right here could finish with `hello: null`
|
|
38
|
+
* for a run it had already reported as connected. That is now structurally
|
|
39
|
+
* impossible without any change to this function: DeviceClient's "state"
|
|
40
|
+
* event does not fire "connected" until hello has actually resolved (see
|
|
41
|
+
* device.ts's setState()/connect()), and this only resolves `true` on that
|
|
42
|
+
* exact event, so by the time it does, `device.hello` below is guaranteed
|
|
43
|
+
* non-null. Waiting through the new "handshaking" state in between is free —
|
|
44
|
+
* this function was never told which non-"connected" states exist, and does
|
|
45
|
+
* not need to be now either.
|
|
46
|
+
*/
|
|
30
47
|
async function awaitConnection(device, timeoutMs = 10_000) {
|
|
31
|
-
|
|
48
|
+
// GRA-162: routed through isConnected() rather than `=== "connected"` so
|
|
49
|
+
// that a fifth ConnectionState fails `tsc` here instead of this function
|
|
50
|
+
// just never resolving true for it.
|
|
51
|
+
if (isConnected(device.state))
|
|
32
52
|
return true;
|
|
33
53
|
return new Promise((resolve) => {
|
|
34
54
|
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
35
55
|
device.on("state", (state) => {
|
|
36
|
-
if (state
|
|
56
|
+
if (isConnected(state)) {
|
|
37
57
|
clearTimeout(timer);
|
|
38
58
|
resolve(true);
|
|
39
59
|
}
|
|
@@ -57,8 +77,18 @@ export async function capture(options) {
|
|
|
57
77
|
const durationMs = Date.now() - startedAt;
|
|
58
78
|
// The last events are still in flight when the child exits.
|
|
59
79
|
await new Promise((resolve) => setTimeout(resolve, 750));
|
|
80
|
+
// Non-null here unless the app disconnected again during the run — a real,
|
|
81
|
+
// separate risk (the process under test crashed or was reinstalled mid-run)
|
|
82
|
+
// that this ticket does not attempt to paper over. It is no longer possible
|
|
83
|
+
// for this to be null merely because we asked too early: awaitConnection()
|
|
84
|
+
// above only returns once DeviceClient has actually set hello (GRA-157).
|
|
60
85
|
const hello = device.hello;
|
|
61
86
|
device.stop();
|
|
87
|
+
// GRA-185: `capture` has no window narrower than the whole run, so
|
|
88
|
+
// `windowTo: Infinity` — a profile emitted anywhere in `events` (in
|
|
89
|
+
// practice, `DeviceCollector`'s one startup event) counts, exactly as it
|
|
90
|
+
// always has here.
|
|
91
|
+
const profile = resolveProfile({ liveEvents: events, windowTo: Number.POSITIVE_INFINITY, sessionProfile: null, hello });
|
|
62
92
|
const trace = buildTrace({
|
|
63
93
|
scenario: options.scenario,
|
|
64
94
|
driver: options.driver,
|
|
@@ -66,16 +96,30 @@ export async function capture(options) {
|
|
|
66
96
|
hello,
|
|
67
97
|
durationMs,
|
|
68
98
|
withEvents: options.withEvents,
|
|
99
|
+
profile,
|
|
69
100
|
});
|
|
70
101
|
await writeFile(options.out, JSON.stringify(trace, null, 2));
|
|
71
|
-
|
|
102
|
+
// stderr, not stdout — this is the summary `porthole capture` prints
|
|
103
|
+
// alongside the child command's own output, so it colours against stderr's
|
|
104
|
+
// own TTY-ness, which can differ from stdout's (e.g. `capture ... | tee log`).
|
|
105
|
+
process.stderr.write(`\n${renderReport(trace, { color: shouldColor(process.stderr) })}`);
|
|
72
106
|
process.stderr.write(`\nwrote ${options.out} (${events.length} events)\n`);
|
|
73
107
|
let regressed = false;
|
|
74
108
|
if (options.baseline) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
109
|
+
try {
|
|
110
|
+
const before = await readTrace(options.baseline);
|
|
111
|
+
const comparison = renderComparison(before, trace);
|
|
112
|
+
process.stderr.write(`\n${comparison.text}`);
|
|
113
|
+
regressed = comparison.regressed;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// Same hazard as `porthole compare` reading its two files, just reached
|
|
117
|
+
// from a capture that asked to be checked against a baseline inline. A
|
|
118
|
+
// baseline we could not read is not evidence either way, so it is
|
|
119
|
+
// reported and the comparison is skipped rather than crashing the run
|
|
120
|
+
// that was otherwise recorded successfully.
|
|
121
|
+
process.stderr.write(`${error.message}\n`);
|
|
122
|
+
}
|
|
79
123
|
}
|
|
80
124
|
const hasError = trace.findings.some((finding) => finding.severity === "error");
|
|
81
125
|
if (options.failOn === "error" && hasError)
|
|
@@ -102,13 +146,28 @@ function run(command) {
|
|
|
102
146
|
});
|
|
103
147
|
}
|
|
104
148
|
export async function report(file) {
|
|
105
|
-
|
|
106
|
-
|
|
149
|
+
let trace;
|
|
150
|
+
try {
|
|
151
|
+
trace = await readTrace(file);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
process.stderr.write(`${error.message}\n`);
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
process.stdout.write(renderReport(trace, { color: shouldColor(process.stdout) }));
|
|
107
158
|
return 0;
|
|
108
159
|
}
|
|
109
160
|
export async function compare(baseline, file) {
|
|
110
|
-
|
|
111
|
-
|
|
161
|
+
let before;
|
|
162
|
+
let after;
|
|
163
|
+
try {
|
|
164
|
+
before = await readTrace(baseline);
|
|
165
|
+
after = await readTrace(file);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
process.stderr.write(`${error.message}\n`);
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
112
171
|
const comparison = renderComparison(before, after);
|
|
113
172
|
process.stdout.write(comparison.text);
|
|
114
173
|
// A refusal is not a pass. Exiting 0 would turn a gate that compared nothing
|
|
@@ -133,22 +192,72 @@ export function parseCapture(argv) {
|
|
|
133
192
|
options.command = argv.slice(i + 1);
|
|
134
193
|
break;
|
|
135
194
|
}
|
|
136
|
-
else if (arg === "--scenario")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
195
|
+
else if (arg === "--scenario") {
|
|
196
|
+
const value = requiredValue(argv[++i], "--scenario");
|
|
197
|
+
if (typeof value !== "string") {
|
|
198
|
+
process.stderr.write(`${value.message}\n`);
|
|
199
|
+
process.exit(2);
|
|
200
|
+
}
|
|
201
|
+
options.scenario = value;
|
|
202
|
+
}
|
|
203
|
+
else if (arg === "--out") {
|
|
204
|
+
// Validated here, before `capture()` is ever called: a bad --out used
|
|
205
|
+
// to fail only after the recording had already happened, so the run was
|
|
206
|
+
// lost *and* the operator got a raw stack trace instead of a sentence.
|
|
207
|
+
const value = requiredValue(argv[++i], "--out");
|
|
208
|
+
if (typeof value !== "string") {
|
|
209
|
+
process.stderr.write(`${value.message}\n`);
|
|
210
|
+
process.exit(2);
|
|
211
|
+
}
|
|
212
|
+
options.out = value;
|
|
213
|
+
}
|
|
214
|
+
else if (arg === "--driver") {
|
|
215
|
+
// Previously `argv[++i]` raw: `--driver --serial abc` swallowed
|
|
216
|
+
// "--serial" as the driver name and left "abc" to be rejected next as
|
|
217
|
+
// a nonsense option — blaming the wrong token for the actual mistake.
|
|
218
|
+
const value = requiredValue(argv[++i], "--driver");
|
|
219
|
+
if (typeof value !== "string") {
|
|
220
|
+
process.stderr.write(`${value.message}\n`);
|
|
221
|
+
process.exit(2);
|
|
222
|
+
}
|
|
223
|
+
options.driver = value;
|
|
224
|
+
}
|
|
225
|
+
else if (arg === "--baseline") {
|
|
226
|
+
const value = requiredValue(argv[++i], "--baseline");
|
|
227
|
+
if (typeof value !== "string") {
|
|
228
|
+
process.stderr.write(`${value.message}\n`);
|
|
229
|
+
process.exit(2);
|
|
230
|
+
}
|
|
231
|
+
options.baseline = value;
|
|
232
|
+
}
|
|
144
233
|
else if (arg === "--with-events")
|
|
145
234
|
options.withEvents = true;
|
|
146
|
-
else if (arg === "--fail-on")
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
235
|
+
else if (arg === "--fail-on") {
|
|
236
|
+
const value = parseFailOn(argv[++i]);
|
|
237
|
+
if (typeof value !== "string") {
|
|
238
|
+
process.stderr.write(`${value.message}\n`);
|
|
239
|
+
process.exit(2);
|
|
240
|
+
}
|
|
241
|
+
options.failOn = value;
|
|
242
|
+
}
|
|
243
|
+
else if (arg === "--port") {
|
|
244
|
+
const value = parsePort(argv[++i], "--port");
|
|
245
|
+
if (typeof value !== "number") {
|
|
246
|
+
process.stderr.write(`${value.message}\n`);
|
|
247
|
+
process.exit(2);
|
|
248
|
+
}
|
|
249
|
+
options.port = value;
|
|
250
|
+
}
|
|
251
|
+
else if (arg === "--serial") {
|
|
252
|
+
// Same hazard as --driver above: a raw argv[++i] blames the wrong
|
|
253
|
+
// token when the value is missing or is actually the next flag.
|
|
254
|
+
const value = requiredValue(argv[++i], "--serial");
|
|
255
|
+
if (typeof value !== "string") {
|
|
256
|
+
process.stderr.write(`${value.message}\n`);
|
|
257
|
+
process.exit(2);
|
|
258
|
+
}
|
|
259
|
+
options.serial = value;
|
|
260
|
+
}
|
|
152
261
|
else if (arg === "--no-forward")
|
|
153
262
|
options.forward = false;
|
|
154
263
|
else if (arg === "--help" || arg === "-h") {
|
package/dist/capture.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,sCAAsC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
|