@react-native-harness/jest 1.5.0 → 1.7.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/dist/cli-args.d.ts +3 -4
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +3 -8
- package/dist/diagnostics/index.d.ts +4 -1
- package/dist/diagnostics/index.d.ts.map +1 -1
- package/dist/diagnostics/index.js +2 -1
- package/dist/diagnostics/report.d.ts +81 -1
- package/dist/diagnostics/report.d.ts.map +1 -1
- package/dist/diagnostics/report.js +225 -3
- package/dist/diagnostics/telemetry.d.ts +42 -0
- package/dist/diagnostics/telemetry.d.ts.map +1 -0
- package/dist/diagnostics/telemetry.js +120 -0
- package/dist/errors.d.ts +8 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +29 -3
- package/dist/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +73 -21
- package/dist/format-harness-error.d.ts.map +1 -1
- package/dist/format-harness-error.js +3 -0
- package/dist/harness-session.d.ts +8 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +110 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/jest-preset.d.ts +3 -0
- package/dist/jest-preset.d.ts.map +1 -1
- package/dist/jest-preset.js +18 -0
- package/dist/relative-path.d.ts +8 -0
- package/dist/relative-path.d.ts.map +1 -0
- package/dist/relative-path.js +8 -0
- package/dist/resource-lock.d.ts.map +1 -1
- package/dist/resource-lock.js +47 -27
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +4 -4
- package/dist/watch-session.d.ts +6 -0
- package/dist/watch-session.d.ts.map +1 -0
- package/dist/watch-session.js +37 -0
- package/package.json +8 -8
- package/src/cli-args.ts +5 -13
- package/src/diagnostics/index.ts +12 -1
- package/src/diagnostics/report.ts +326 -5
- package/src/diagnostics/telemetry.ts +186 -0
- package/src/errors.ts +42 -5
- package/src/execute-run.ts +93 -23
- package/src/format-harness-error.ts +5 -0
- package/src/harness-session.ts +143 -22
- package/src/index.ts +8 -7
- package/src/jest-preset.ts +18 -0
- package/src/relative-path.ts +10 -0
- package/src/resource-lock.ts +64 -33
- package/src/run.ts +4 -4
- package/src/watch-session.ts +65 -0
package/dist/cli-args.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
1
|
+
import { harnessCliOptions } from '@react-native-harness/config';
|
|
2
|
+
import { type InferredOptionTypes } from 'yargs';
|
|
3
|
+
export type HarnessCliArgs = InferredOptionTypes<typeof harnessCliOptions>;
|
|
5
4
|
export declare const getAdditionalCliArgs: () => HarnessCliArgs;
|
|
6
5
|
//# sourceMappingURL=cli-args.d.ts.map
|
package/dist/cli-args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAGxD,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE3E,eAAO,MAAM,oBAAoB,QAAO,cAcvC,CAAC"}
|
package/dist/cli-args.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
+
import { harnessCliOptions } from '@react-native-harness/config';
|
|
1
2
|
import yargs from 'yargs';
|
|
2
3
|
import { hideBin } from 'yargs/helpers';
|
|
3
4
|
export const getAdditionalCliArgs = () => {
|
|
4
5
|
const argv = yargs(hideBin(process.argv))
|
|
5
|
-
.
|
|
6
|
-
type: 'string',
|
|
7
|
-
description: 'Specify which Harness runner to use',
|
|
8
|
-
})
|
|
9
|
-
.option('metroPort', {
|
|
10
|
-
type: 'number',
|
|
11
|
-
description: 'Override the Metro bundler port',
|
|
12
|
-
})
|
|
6
|
+
.options(harnessCliOptions)
|
|
13
7
|
.strict(false)
|
|
14
8
|
.help(false)
|
|
15
9
|
.version(false)
|
|
16
10
|
.exitProcess(false)
|
|
11
|
+
.fail(false)
|
|
17
12
|
.parseSync();
|
|
18
13
|
return {
|
|
19
14
|
harnessRunner: argv.harnessRunner,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { observeMetroEvents, observeBridgeEvents } from './derivers.js';
|
|
2
|
-
export { writeTraceFile, printSummary } from './report.js';
|
|
2
|
+
export { writeTraceFile, appendToTraceFile, printSummary, createDiagnosticsAppender, summarizeRunKpis, writeSummaryFile, appendStepSummary, } from './report.js';
|
|
3
|
+
export type { DiagnosticsAppender, RunKpis } from './report.js';
|
|
4
|
+
export { startSessionTelemetry } from './telemetry.js';
|
|
5
|
+
export type { SessionTelemetry } from './telemetry.js';
|
|
3
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,yBAAyB,EACzB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { observeMetroEvents, observeBridgeEvents } from './derivers.js';
|
|
2
|
-
export { writeTraceFile, printSummary } from './report.js';
|
|
2
|
+
export { writeTraceFile, appendToTraceFile, printSummary, createDiagnosticsAppender, summarizeRunKpis, writeSummaryFile, appendStepSummary, } from './report.js';
|
|
3
|
+
export { startSessionTelemetry } from './telemetry.js';
|
|
@@ -1,7 +1,87 @@
|
|
|
1
|
-
import type { DiagnosticEntry } from '@react-native-harness/tools';
|
|
1
|
+
import type { DiagnosticEntry, Diagnostics } from '@react-native-harness/tools';
|
|
2
|
+
type TraceEvent = {
|
|
3
|
+
name: string;
|
|
4
|
+
ph: 'X' | 'i' | 'C';
|
|
5
|
+
ts: number;
|
|
6
|
+
dur?: number;
|
|
7
|
+
s?: 'g';
|
|
8
|
+
pid: number;
|
|
9
|
+
tid: number;
|
|
10
|
+
args?: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
export declare const toTraceEvent: (entry: DiagnosticEntry) => TraceEvent;
|
|
2
13
|
export declare const writeTraceFile: (entries: DiagnosticEntry[], options: {
|
|
3
14
|
projectRoot: string;
|
|
4
15
|
runId: string;
|
|
5
16
|
}) => Promise<string>;
|
|
17
|
+
export type DiagnosticsAppender = {
|
|
18
|
+
readonly filePath: string;
|
|
19
|
+
/** Synchronously writes everything not yet on disk. For `exit` handlers. */
|
|
20
|
+
flush(): void;
|
|
21
|
+
/** Waits for in-flight writes, then writes the rest and detaches. */
|
|
22
|
+
dispose(): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Persists diagnostics entries as they are recorded, one JSON object per line
|
|
26
|
+
* in `.harness/diagnostics/<id>.jsonl`. The final trace is only written when a
|
|
27
|
+
* run finishes, so a job that is cancelled or killed mid-run (the case where
|
|
28
|
+
* the data matters most) would otherwise leave nothing behind.
|
|
29
|
+
*
|
|
30
|
+
* Entries are batched (every `flushIntervalMs` or `maxBufferedEntries`) and
|
|
31
|
+
* appended asynchronously, one write at a time, so recording an entry never
|
|
32
|
+
* blocks on disk, which matters most when the machine is overloaded. Only the
|
|
33
|
+
* `exit` handler writes synchronously: a process that is going away cannot
|
|
34
|
+
* wait for the event loop. The session already owns SIGINT/SIGTERM and leaves
|
|
35
|
+
* through `process.exit`, which emits `exit`; installing signal handlers here
|
|
36
|
+
* would depend on listener order and fight other handlers about who re-raises.
|
|
37
|
+
*
|
|
38
|
+
* Entries recorded before the appender was created are replayed, so the log
|
|
39
|
+
* starts at the beginning of the session.
|
|
40
|
+
*/
|
|
41
|
+
export declare const createDiagnosticsAppender: (diagnostics: Diagnostics, options: {
|
|
42
|
+
projectRoot: string;
|
|
43
|
+
id: string;
|
|
44
|
+
flushIntervalMs?: number;
|
|
45
|
+
maxBufferedEntries?: number;
|
|
46
|
+
}) => DiagnosticsAppender;
|
|
47
|
+
export type RunKpis = {
|
|
48
|
+
bootWaitMs: number | null;
|
|
49
|
+
timeToReadyMs: number | null;
|
|
50
|
+
peakHostCompressedMb: number | null;
|
|
51
|
+
peakLoad1: number | null;
|
|
52
|
+
minHostFreeMb: number | null;
|
|
53
|
+
/** Memory swapped out during the run, in MB. */
|
|
54
|
+
swapoutsDelta: number | null;
|
|
55
|
+
maxEventLoopLagMs: number | null;
|
|
56
|
+
testFileCount: number;
|
|
57
|
+
result: 'passed' | 'failed' | null;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Reduces a run's entries to the handful of numbers worth comparing between
|
|
61
|
+
* runs. Anything that cannot be derived from the entries is null, never a
|
|
62
|
+
* guess, so a missing probe shows up as "n/a" rather than as a misleading 0.
|
|
63
|
+
*/
|
|
64
|
+
export declare const summarizeRunKpis: (entries: DiagnosticEntry[]) => RunKpis;
|
|
65
|
+
export declare const writeSummaryFile: (kpis: RunKpis, options: {
|
|
66
|
+
projectRoot: string;
|
|
67
|
+
runId: string;
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
}) => Promise<string>;
|
|
70
|
+
/** The KPIs as label/value pairs, in display order. */
|
|
71
|
+
export declare const formatRunKpis: (kpis: RunKpis) => Array<[string, string]>;
|
|
72
|
+
/**
|
|
73
|
+
* Appends the KPIs as a markdown table to the GitHub Actions job summary when
|
|
74
|
+
* running there. Best effort: a failure here must never fail the run.
|
|
75
|
+
*/
|
|
76
|
+
export declare const appendStepSummary: (kpis: RunKpis, env?: NodeJS.ProcessEnv) => Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Adds entries recorded after a run's trace was written (session teardown) to
|
|
79
|
+
* that trace, or writes a new trace if there is none yet.
|
|
80
|
+
*/
|
|
81
|
+
export declare const appendToTraceFile: (entries: DiagnosticEntry[], options: {
|
|
82
|
+
projectRoot: string;
|
|
83
|
+
runId: string;
|
|
84
|
+
}) => Promise<string>;
|
|
6
85
|
export declare const printSummary: (entries: DiagnosticEntry[], log: (line: string) => void) => void;
|
|
86
|
+
export {};
|
|
7
87
|
//# sourceMappingURL=report.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/diagnostics/report.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/diagnostics/report.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAKhF,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,GAAG,CAAC;IACR,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAKF,eAAO,MAAM,YAAY,GAAI,OAAO,eAAe,KAAG,UAmCrD,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,SAAS,eAAe,EAAE,EAC1B,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAC9C,OAAO,CAAC,MAAM,CAUhB,CAAC;AAKF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,KAAK,IAAI,IAAI,CAAC;IACd,qEAAqE;IACrE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,yBAAyB,GACpC,aAAa,WAAW,EACxB,SAAS;IACP,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,KACA,mBAyFF,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gDAAgD;IAChD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;CACpC,CAAC;AAQF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,eAAe,EAAE,KAAG,OA2C7D,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,MAAM,OAAO,EACb,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAClE,OAAO,CAAC,MAAM,CAahB,CAAC;AAQF,uDAAuD;AACvD,eAAO,MAAM,aAAa,GAAI,MAAM,OAAO,KAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAUnE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,OAAO,EACb,MAAK,MAAM,CAAC,UAAwB,KACnC,OAAO,CAAC,IAAI,CAsBd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,eAAe,EAAE,EAC1B,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAC9C,OAAO,CAAC,MAAM,CAwBhB,CAAC;AAkBF,eAAO,MAAM,YAAY,GACvB,SAAS,eAAe,EAAE,EAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC1B,IAsDF,CAAC"}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
import fsSync from 'node:fs';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import { getHarnessRootPath } from '@react-native-harness/tools';
|
|
4
|
+
import { getHarnessRootPath, logger, unrefTimer } from '@react-native-harness/tools';
|
|
5
|
+
const reportLogger = logger.child('diagnostics');
|
|
4
6
|
const TRACE_PID = 1;
|
|
5
7
|
const TRACE_TID = 1;
|
|
6
|
-
const toTraceEvent = (entry) => {
|
|
8
|
+
export const toTraceEvent = (entry) => {
|
|
9
|
+
if (entry.kind === 'gauge') {
|
|
10
|
+
// Counter events become graphed tracks in Perfetto / chrome://tracing,
|
|
11
|
+
// one per distinct name, so each gauge label is its own series.
|
|
12
|
+
return {
|
|
13
|
+
name: entry.label,
|
|
14
|
+
ph: 'C',
|
|
15
|
+
ts: entry.startTime * 1000,
|
|
16
|
+
pid: TRACE_PID,
|
|
17
|
+
tid: TRACE_TID,
|
|
18
|
+
args: { [entry.label]: entry.value ?? 0 },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
7
21
|
if (entry.kind === 'mark') {
|
|
8
22
|
return {
|
|
9
23
|
name: entry.label,
|
|
@@ -26,13 +40,213 @@ const toTraceEvent = (entry) => {
|
|
|
26
40
|
};
|
|
27
41
|
};
|
|
28
42
|
export const writeTraceFile = async (entries, options) => {
|
|
29
|
-
const dir =
|
|
43
|
+
const dir = getDiagnosticsDir(options.projectRoot);
|
|
30
44
|
await fs.mkdir(dir, { recursive: true });
|
|
31
45
|
const filePath = path.join(dir, `${options.runId}.json`);
|
|
32
46
|
const trace = { traceEvents: entries.map(toTraceEvent) };
|
|
33
47
|
await fs.writeFile(filePath, JSON.stringify(trace), 'utf-8');
|
|
34
48
|
return filePath;
|
|
35
49
|
};
|
|
50
|
+
const getDiagnosticsDir = (projectRoot) => path.join(getHarnessRootPath(projectRoot), 'diagnostics');
|
|
51
|
+
/**
|
|
52
|
+
* Persists diagnostics entries as they are recorded, one JSON object per line
|
|
53
|
+
* in `.harness/diagnostics/<id>.jsonl`. The final trace is only written when a
|
|
54
|
+
* run finishes, so a job that is cancelled or killed mid-run (the case where
|
|
55
|
+
* the data matters most) would otherwise leave nothing behind.
|
|
56
|
+
*
|
|
57
|
+
* Entries are batched (every `flushIntervalMs` or `maxBufferedEntries`) and
|
|
58
|
+
* appended asynchronously, one write at a time, so recording an entry never
|
|
59
|
+
* blocks on disk, which matters most when the machine is overloaded. Only the
|
|
60
|
+
* `exit` handler writes synchronously: a process that is going away cannot
|
|
61
|
+
* wait for the event loop. The session already owns SIGINT/SIGTERM and leaves
|
|
62
|
+
* through `process.exit`, which emits `exit`; installing signal handlers here
|
|
63
|
+
* would depend on listener order and fight other handlers about who re-raises.
|
|
64
|
+
*
|
|
65
|
+
* Entries recorded before the appender was created are replayed, so the log
|
|
66
|
+
* starts at the beginning of the session.
|
|
67
|
+
*/
|
|
68
|
+
export const createDiagnosticsAppender = (diagnostics, options) => {
|
|
69
|
+
const filePath = path.join(getDiagnosticsDir(options.projectRoot), `${options.id}.jsonl`);
|
|
70
|
+
if (!diagnostics.enabled) {
|
|
71
|
+
return {
|
|
72
|
+
filePath,
|
|
73
|
+
flush: () => undefined,
|
|
74
|
+
dispose: async () => undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const maxBufferedEntries = options.maxBufferedEntries ?? 100;
|
|
78
|
+
let buffer = [];
|
|
79
|
+
// Chunks handed to the async writer and not yet confirmed on disk, kept so
|
|
80
|
+
// the `exit` handler can write them if the process leaves first. In the
|
|
81
|
+
// rare race where one was also completed by the writer, a chunk ends up in
|
|
82
|
+
// the file twice; losing it would be worse for a log whose purpose is
|
|
83
|
+
// surviving abrupt termination.
|
|
84
|
+
let pending = [];
|
|
85
|
+
let writeChain = Promise.resolve();
|
|
86
|
+
let directoryReady = false;
|
|
87
|
+
const render = (lines) => `${lines.join('\n')}\n`;
|
|
88
|
+
const flushAsync = () => {
|
|
89
|
+
if (buffer.length === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const lines = buffer;
|
|
93
|
+
buffer = [];
|
|
94
|
+
pending.push(lines);
|
|
95
|
+
writeChain = writeChain.then(async () => {
|
|
96
|
+
try {
|
|
97
|
+
if (!directoryReady) {
|
|
98
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
99
|
+
directoryReady = true;
|
|
100
|
+
}
|
|
101
|
+
await fs.appendFile(filePath, render(lines), 'utf-8');
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
reportLogger.debug('failed to append diagnostics: %s', error);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
pending = pending.filter((chunk) => chunk !== lines);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const flushSync = () => {
|
|
112
|
+
const lines = [...pending.flat(), ...buffer];
|
|
113
|
+
pending = [];
|
|
114
|
+
buffer = [];
|
|
115
|
+
if (lines.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
120
|
+
fsSync.appendFileSync(filePath, render(lines), 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
reportLogger.debug('failed to append diagnostics: %s', error);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const unsubscribe = diagnostics.subscribe((entry) => {
|
|
127
|
+
buffer.push(JSON.stringify(entry));
|
|
128
|
+
if (buffer.length >= maxBufferedEntries) {
|
|
129
|
+
flushAsync();
|
|
130
|
+
}
|
|
131
|
+
}, { replay: true });
|
|
132
|
+
const timer = setInterval(flushAsync, options.flushIntervalMs ?? 2000);
|
|
133
|
+
unrefTimer(timer);
|
|
134
|
+
process.on('exit', flushSync);
|
|
135
|
+
const dispose = async () => {
|
|
136
|
+
unsubscribe();
|
|
137
|
+
clearInterval(timer);
|
|
138
|
+
process.off('exit', flushSync);
|
|
139
|
+
flushAsync();
|
|
140
|
+
await writeChain;
|
|
141
|
+
};
|
|
142
|
+
return { filePath, flush: flushSync, dispose };
|
|
143
|
+
};
|
|
144
|
+
const maxOf = (values) => values.length > 0 ? Math.max(...values) : null;
|
|
145
|
+
const minOf = (values) => values.length > 0 ? Math.min(...values) : null;
|
|
146
|
+
/**
|
|
147
|
+
* Reduces a run's entries to the handful of numbers worth comparing between
|
|
148
|
+
* runs. Anything that cannot be derived from the entries is null, never a
|
|
149
|
+
* guess, so a missing probe shows up as "n/a" rather than as a misleading 0.
|
|
150
|
+
*/
|
|
151
|
+
export const summarizeRunKpis = (entries) => {
|
|
152
|
+
const gaugeValues = (label) => entries
|
|
153
|
+
.filter((entry) => entry.kind === 'gauge' && entry.label === label)
|
|
154
|
+
.map((entry) => entry.value)
|
|
155
|
+
.filter((value) => typeof value === 'number');
|
|
156
|
+
const spans = (label) => entries.filter((entry) => entry.kind === 'span' && entry.label === label);
|
|
157
|
+
const bootSpans = entries.filter((entry) => entry.kind === 'span' && entry.label.endsWith('.waitForBoot'));
|
|
158
|
+
const sessionStart = spans('session.config.load')[0]?.startTime;
|
|
159
|
+
const firstReady = spans('app.ready.total').find((entry) => entry.status === 'ok');
|
|
160
|
+
const swapouts = gaugeValues('host.mem.swapouts');
|
|
161
|
+
const runStatus = spans('run.total')[0]?.attrs?.status;
|
|
162
|
+
return {
|
|
163
|
+
bootWaitMs: bootSpans.length > 0
|
|
164
|
+
? bootSpans.reduce((total, entry) => total + entry.duration, 0)
|
|
165
|
+
: null,
|
|
166
|
+
timeToReadyMs: sessionStart !== undefined && firstReady
|
|
167
|
+
? firstReady.startTime + firstReady.duration - sessionStart
|
|
168
|
+
: null,
|
|
169
|
+
peakHostCompressedMb: maxOf(gaugeValues('host.mem.compressed')),
|
|
170
|
+
peakLoad1: maxOf(gaugeValues('host.load1')),
|
|
171
|
+
minHostFreeMb: minOf(gaugeValues('host.mem.free')),
|
|
172
|
+
swapoutsDelta: swapouts.length >= 2
|
|
173
|
+
? swapouts[swapouts.length - 1] - swapouts[0]
|
|
174
|
+
: null,
|
|
175
|
+
maxEventLoopLagMs: maxOf(gaugeValues('node.event_loop.max_ms')),
|
|
176
|
+
testFileCount: spans('run.file').length,
|
|
177
|
+
result: runStatus === 'passed' || runStatus === 'failed' ? runStatus : null,
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
export const writeSummaryFile = async (kpis, options) => {
|
|
181
|
+
const dir = getDiagnosticsDir(options.projectRoot);
|
|
182
|
+
await fs.mkdir(dir, { recursive: true });
|
|
183
|
+
const filePath = path.join(dir, `${options.runId}.summary.json`);
|
|
184
|
+
const summary = {
|
|
185
|
+
runId: options.runId,
|
|
186
|
+
...(options.sessionId ? { sessionId: options.sessionId } : {}),
|
|
187
|
+
...kpis,
|
|
188
|
+
};
|
|
189
|
+
await fs.writeFile(filePath, JSON.stringify(summary, null, 2), 'utf-8');
|
|
190
|
+
return filePath;
|
|
191
|
+
};
|
|
192
|
+
const formatNumber = (value, unit, digits = 0) => value === null ? 'n/a' : `${value.toFixed(digits)}${unit}`;
|
|
193
|
+
const formatKpiDuration = (ms) => ms === null ? 'n/a' : formatDuration(ms);
|
|
194
|
+
/** The KPIs as label/value pairs, in display order. */
|
|
195
|
+
export const formatRunKpis = (kpis) => [
|
|
196
|
+
['Result', kpis.result ?? 'n/a'],
|
|
197
|
+
['Test files', String(kpis.testFileCount)],
|
|
198
|
+
['Boot wait', formatKpiDuration(kpis.bootWaitMs)],
|
|
199
|
+
['Time to ready', formatKpiDuration(kpis.timeToReadyMs)],
|
|
200
|
+
['Peak host load (1m)', formatNumber(kpis.peakLoad1, '', 1)],
|
|
201
|
+
['Peak compressed memory', formatNumber(kpis.peakHostCompressedMb, ' MB')],
|
|
202
|
+
['Min free memory', formatNumber(kpis.minHostFreeMb, ' MB')],
|
|
203
|
+
['Swapped out during run', formatNumber(kpis.swapoutsDelta, ' MB')],
|
|
204
|
+
['Max event loop lag', formatNumber(kpis.maxEventLoopLagMs, ' ms')],
|
|
205
|
+
];
|
|
206
|
+
/**
|
|
207
|
+
* Appends the KPIs as a markdown table to the GitHub Actions job summary when
|
|
208
|
+
* running there. Best effort: a failure here must never fail the run.
|
|
209
|
+
*/
|
|
210
|
+
export const appendStepSummary = async (kpis, env = process.env) => {
|
|
211
|
+
const summaryPath = env.GITHUB_STEP_SUMMARY;
|
|
212
|
+
if (!summaryPath) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const rows = formatRunKpis(kpis).map(([label, value]) => `| ${label} | ${value} |`);
|
|
216
|
+
const markdown = [
|
|
217
|
+
'### Harness run KPIs',
|
|
218
|
+
'',
|
|
219
|
+
'| KPI | Value |',
|
|
220
|
+
'| --- | --- |',
|
|
221
|
+
...rows,
|
|
222
|
+
'',
|
|
223
|
+
'',
|
|
224
|
+
].join('\n');
|
|
225
|
+
try {
|
|
226
|
+
await fs.appendFile(summaryPath, markdown, 'utf-8');
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
reportLogger.debug('failed to write GitHub step summary: %s', error);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Adds entries recorded after a run's trace was written (session teardown) to
|
|
234
|
+
* that trace, or writes a new trace if there is none yet.
|
|
235
|
+
*/
|
|
236
|
+
export const appendToTraceFile = async (entries, options) => {
|
|
237
|
+
const filePath = path.join(getDiagnosticsDir(options.projectRoot), `${options.runId}.json`);
|
|
238
|
+
let existing = [];
|
|
239
|
+
try {
|
|
240
|
+
const parsed = JSON.parse(await fs.readFile(filePath, 'utf-8'));
|
|
241
|
+
existing = parsed.traceEvents ?? [];
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// No trace yet (or an unreadable one): start a fresh file.
|
|
245
|
+
}
|
|
246
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
247
|
+
await fs.writeFile(filePath, JSON.stringify({ traceEvents: [...existing, ...entries.map(toTraceEvent)] }), 'utf-8');
|
|
248
|
+
return filePath;
|
|
249
|
+
};
|
|
36
250
|
const formatDuration = (ms) => {
|
|
37
251
|
if (ms >= 1000) {
|
|
38
252
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -46,6 +260,10 @@ export const printSummary = (entries, log) => {
|
|
|
46
260
|
}
|
|
47
261
|
const statsByLabel = new Map();
|
|
48
262
|
for (const entry of entries) {
|
|
263
|
+
// Gauges are samples, not timings; they are summarised as KPIs below.
|
|
264
|
+
if (entry.kind === 'gauge') {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
49
267
|
const existing = statsByLabel.get(entry.label);
|
|
50
268
|
if (existing) {
|
|
51
269
|
existing.count += 1;
|
|
@@ -81,4 +299,8 @@ export const printSummary = (entries, log) => {
|
|
|
81
299
|
log(` ${stats.label}: count=${stats.count} total=${formatDuration(stats.totalMs)} max=${formatDuration(stats.maxMs)}`);
|
|
82
300
|
}
|
|
83
301
|
}
|
|
302
|
+
log(' KPIs');
|
|
303
|
+
for (const [label, value] of formatRunKpis(summarizeRunKpis(entries))) {
|
|
304
|
+
log(` ${label}: ${value}`);
|
|
305
|
+
}
|
|
84
306
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createLifecycleEmitter, type Diagnostics, type DiagnosticAttrs, type LifecyclePhase, type HostPressure } from '@react-native-harness/tools';
|
|
2
|
+
import { type Config as HarnessConfig } from '@react-native-harness/config';
|
|
3
|
+
import type { HarnessPlatformRunner } from '@react-native-harness/platforms';
|
|
4
|
+
export type SessionTelemetry = {
|
|
5
|
+
/** Identifies the session; names the incremental `.jsonl` log. */
|
|
6
|
+
readonly sessionId: string;
|
|
7
|
+
/** Session code emits phases here; probes listen. */
|
|
8
|
+
readonly lifecycle: ReturnType<typeof createLifecycleEmitter>;
|
|
9
|
+
/** Emits a lifecycle phase. */
|
|
10
|
+
phase(phase: LifecyclePhase, attrs?: DiagnosticAttrs): void;
|
|
11
|
+
/** Makes the platform runner available to the platform collector probe. */
|
|
12
|
+
attachPlatform(platform: HarnessPlatformRunner): void;
|
|
13
|
+
/** Records the run that is current, so teardown data can join its trace. */
|
|
14
|
+
trackRun(runId: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* Host pressure observed by the host-resources probe so far: peak load,
|
|
17
|
+
* mean idle CPU, lowest free memory and memory swapped out since the first
|
|
18
|
+
* sample. Null
|
|
19
|
+
* when no host sample was taken (diagnostics or the host probe are off).
|
|
20
|
+
*/
|
|
21
|
+
hostPressure(): HostPressure | null;
|
|
22
|
+
/** Resolves once lifecycle driven collection started so far has finished. */
|
|
23
|
+
settled(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Stops probes and the incremental log. Entries recorded since the last
|
|
26
|
+
* run's report was written (the end-of-run snapshot, teardown spans) are
|
|
27
|
+
* added to that run's trace. Safe to call more than once.
|
|
28
|
+
*/
|
|
29
|
+
dispose(): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The single composition point for run telemetry: it wires the incremental
|
|
33
|
+
* log and the probes to the one diagnostics sink. Session code only emits
|
|
34
|
+
* lifecycle phases; it never calls a probe. Everything is a no-op, with no
|
|
35
|
+
* timers, listeners or spawned processes, when diagnostics are disabled.
|
|
36
|
+
*/
|
|
37
|
+
export declare const startSessionTelemetry: ({ diagnostics, config, projectRoot, }: {
|
|
38
|
+
diagnostics: Diagnostics;
|
|
39
|
+
config: Pick<HarnessConfig, "diagnostics">;
|
|
40
|
+
projectRoot: string;
|
|
41
|
+
}) => SessionTelemetry;
|
|
42
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/diagnostics/telemetry.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,sBAAsB,EAGtB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,KAAK,MAAM,IAAI,aAAa,EAC7B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAK7E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;IAC9D,+BAA+B;IAC/B,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC5D,2EAA2E;IAC3E,cAAc,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;;OAKG;IACH,YAAY,IAAI,YAAY,GAAG,IAAI,CAAC;IACpC,6EAA6E;IAC7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,uCAInC;IACD,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAC;CACrB,KAAG,gBAyHH,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { logger, createDefaultProbes, createLifecycleEmitter, DEFAULT_SAMPLE_INTERVAL_MS, startTelemetryProbes, } from '@react-native-harness/tools';
|
|
4
|
+
import { resolveDiagnosticsOptions, } from '@react-native-harness/config';
|
|
5
|
+
import { appendToTraceFile, createDiagnosticsAppender } from './report.js';
|
|
6
|
+
const telemetryLogger = logger.child('diagnostics');
|
|
7
|
+
/**
|
|
8
|
+
* The single composition point for run telemetry: it wires the incremental
|
|
9
|
+
* log and the probes to the one diagnostics sink. Session code only emits
|
|
10
|
+
* lifecycle phases; it never calls a probe. Everything is a no-op, with no
|
|
11
|
+
* timers, listeners or spawned processes, when diagnostics are disabled.
|
|
12
|
+
*/
|
|
13
|
+
export const startSessionTelemetry = ({ diagnostics, config, projectRoot, }) => {
|
|
14
|
+
const sessionId = randomUUID();
|
|
15
|
+
const lifecycle = createLifecycleEmitter();
|
|
16
|
+
const abortController = new AbortController();
|
|
17
|
+
const options = resolveDiagnosticsOptions(config);
|
|
18
|
+
let platform;
|
|
19
|
+
let lastRunId;
|
|
20
|
+
const appender = createDiagnosticsAppender(diagnostics, {
|
|
21
|
+
projectRoot,
|
|
22
|
+
id: sessionId,
|
|
23
|
+
});
|
|
24
|
+
const run = startTelemetryProbes(createDefaultProbes({
|
|
25
|
+
host: options.probes.host,
|
|
26
|
+
node: options.probes.node,
|
|
27
|
+
snapshots: options.probes.snapshots,
|
|
28
|
+
platform: options.probes.platform
|
|
29
|
+
? { getCollector: () => platform?.createTelemetryCollector?.() }
|
|
30
|
+
: undefined,
|
|
31
|
+
}), {
|
|
32
|
+
diagnostics,
|
|
33
|
+
signal: abortController.signal,
|
|
34
|
+
lifecycle,
|
|
35
|
+
sampleIntervalMs: options.sampleIntervalMs ?? DEFAULT_SAMPLE_INTERVAL_MS,
|
|
36
|
+
});
|
|
37
|
+
// Running aggregates rather than stored samples: a watch-mode session can
|
|
38
|
+
// live for hours, and only the extremes are needed.
|
|
39
|
+
let peakLoad1 = null;
|
|
40
|
+
let idlePctSum = 0;
|
|
41
|
+
let idlePctSamples = 0;
|
|
42
|
+
let minHostFreeMb = null;
|
|
43
|
+
let firstSwapoutsMb = null;
|
|
44
|
+
let lastSwapoutsMb = null;
|
|
45
|
+
const unsubscribePressure = diagnostics.subscribe(({ kind, label, value }) => {
|
|
46
|
+
if (kind !== 'gauge' || typeof value !== 'number') {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (label === 'host.load1') {
|
|
50
|
+
peakLoad1 = Math.max(peakLoad1 ?? value, value);
|
|
51
|
+
}
|
|
52
|
+
else if (label === 'host.cpu.idle_pct') {
|
|
53
|
+
idlePctSum += value;
|
|
54
|
+
idlePctSamples += 1;
|
|
55
|
+
}
|
|
56
|
+
else if (label === 'host.mem.free') {
|
|
57
|
+
minHostFreeMb = Math.min(minHostFreeMb ?? value, value);
|
|
58
|
+
}
|
|
59
|
+
else if (label === 'host.mem.swapouts') {
|
|
60
|
+
firstSwapoutsMb ??= value;
|
|
61
|
+
lastSwapoutsMb = value;
|
|
62
|
+
}
|
|
63
|
+
}, { replay: true });
|
|
64
|
+
const hostPressure = () => peakLoad1 === null && minHostFreeMb === null && lastSwapoutsMb === null
|
|
65
|
+
? null
|
|
66
|
+
: {
|
|
67
|
+
peakLoad1,
|
|
68
|
+
meanIdlePct: idlePctSamples > 0 ? idlePctSum / idlePctSamples : null,
|
|
69
|
+
minHostFreeMb,
|
|
70
|
+
swapoutsDeltaMb: firstSwapoutsMb !== null && lastSwapoutsMb !== null
|
|
71
|
+
? lastSwapoutsMb - firstSwapoutsMb
|
|
72
|
+
: null,
|
|
73
|
+
cpuCount: os.availableParallelism(),
|
|
74
|
+
};
|
|
75
|
+
const phase = (name, attrs) => {
|
|
76
|
+
if (!diagnostics.enabled) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
lifecycle.emit({ type: 'phase', phase: name, attrs });
|
|
80
|
+
};
|
|
81
|
+
let disposed = null;
|
|
82
|
+
return {
|
|
83
|
+
sessionId,
|
|
84
|
+
lifecycle,
|
|
85
|
+
phase,
|
|
86
|
+
attachPlatform: (runner) => {
|
|
87
|
+
platform = runner;
|
|
88
|
+
},
|
|
89
|
+
trackRun: (runId) => {
|
|
90
|
+
lastRunId = runId;
|
|
91
|
+
},
|
|
92
|
+
hostPressure,
|
|
93
|
+
settled: run.settled,
|
|
94
|
+
dispose: () => {
|
|
95
|
+
disposed ??= (async () => {
|
|
96
|
+
unsubscribePressure();
|
|
97
|
+
try {
|
|
98
|
+
abortController.abort();
|
|
99
|
+
await run.dispose();
|
|
100
|
+
if (diagnostics.enabled && lastRunId) {
|
|
101
|
+
const leftover = diagnostics.flush();
|
|
102
|
+
if (leftover.length > 0) {
|
|
103
|
+
await appendToTraceFile(leftover, {
|
|
104
|
+
projectRoot,
|
|
105
|
+
runId: lastRunId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
telemetryLogger.debug('failed to finalize diagnostics trace: %s', error);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await appender.dispose();
|
|
115
|
+
}
|
|
116
|
+
})();
|
|
117
|
+
return disposed;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
};
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HarnessError } from '@react-native-harness/tools';
|
|
1
|
+
import { HarnessError, type HostPressure } from '@react-native-harness/tools';
|
|
2
2
|
import type { AppCrashDetails } from '@react-native-harness/platforms';
|
|
3
3
|
export { StartupStallError, type StartupStallCode, type StartupStallDetails, } from '@react-native-harness/bundler-metro';
|
|
4
4
|
export declare class NoRunnerSpecifiedError extends HarnessError {
|
|
@@ -13,7 +13,9 @@ export declare class InitializationTimeoutError extends HarnessError {
|
|
|
13
13
|
}
|
|
14
14
|
export declare class PlatformReadyTimeoutError extends HarnessError {
|
|
15
15
|
readonly timeout: number;
|
|
16
|
-
|
|
16
|
+
readonly hostPressure: HostPressure | null;
|
|
17
|
+
constructor(timeout: number, hostPressure?: HostPressure | null);
|
|
18
|
+
get hostStarved(): boolean;
|
|
17
19
|
}
|
|
18
20
|
export declare class MetroPortRangeExhaustedError extends HarnessError {
|
|
19
21
|
readonly initialPort: number;
|
|
@@ -40,5 +42,9 @@ export declare class RuntimeDisconnectError extends HarnessError {
|
|
|
40
42
|
constructor(testFilePath: string, details: RuntimeDisconnectDetails);
|
|
41
43
|
get phase(): NativeCrashPhase;
|
|
42
44
|
}
|
|
45
|
+
export declare class HostSurfaceReplacedError extends HarnessError {
|
|
46
|
+
readonly testFilePath: string;
|
|
47
|
+
constructor(testFilePath: string, cause: Error);
|
|
48
|
+
}
|
|
43
49
|
export type HarnessRuntimeFailure = NativeCrashError | RuntimeDisconnectError;
|
|
44
50
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EAEZ,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,qCAAqC,CAAC;AAE7C,qBAAa,sBAAuB,SAAQ,YAAY;;CAKvD;AAED,qBAAa,mBAAoB,SAAQ,YAAY;aACvB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,0BAA2B,SAAQ,YAAY;;CAK3D;AAkBD,qBAAa,yBAA0B,SAAQ,YAAY;aAEvC,OAAO,EAAE,MAAM;aACf,YAAY,EAAE,YAAY,GAAG,IAAI;gBADjC,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,YAAY,GAAG,IAAW;IAM1D,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF;AAED,qBAAa,4BAA6B,SAAQ,YAAY;aAE1C,WAAW,EAAE,MAAM;aACnB,QAAQ,EAAE,MAAM;gBADhB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM;CAQnC;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG;IACjD,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,eAAe,GAAG;IACvD,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAmEF,qBAAa,gBAAiB,SAAQ,YAAY;aAE9B,YAAY,EAAE,MAAM;aACpB,OAAO,EAAE,kBAAkB;aAC3B,aAAa,CAAC,EAAE,MAAM;gBAFtB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,kBAAkB,EAC3B,aAAa,CAAC,EAAE,MAAM,YAAA;IAOxC,IAAI,KAAK,qBAER;CACF;AA0BD,qBAAa,sBAAuB,SAAQ,YAAY;aAEpC,YAAY,EAAE,MAAM;aACpB,OAAO,EAAE,wBAAwB;gBADjC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,wBAAwB;IAOnD,IAAI,KAAK,qBAER;CACF;AAID,qBAAa,wBAAyB,SAAQ,YAAY;aAC5B,YAAY,EAAE,MAAM;gBAApB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;CAK/D;AAED,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,sBAAsB,CAAC"}
|