@flakiness/sdk 0.151.0 → 0.153.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/README.md CHANGED
@@ -40,8 +40,8 @@ const report: FlakinessReport.Report = {
40
40
  location: { file: 'test.spec.ts', line: 10, column: 1 },
41
41
  attempts: [{
42
42
  environmentIdx: 0,
43
+ status: 'passed',
43
44
  expectedStatus: 'passed',
44
- actualStatus: 'passed',
45
45
  duration: 100 as FlakinessReport.DurationMS,
46
46
  }],
47
47
  }],
@@ -87,11 +87,12 @@ Use this entry point when you need to process or manipulate reports in browser-b
87
87
  - **`ReportUtils`** - Namespace with utilities for report creation and manipulation:
88
88
  - `createEnvironment()` - Create environment objects with system information
89
89
  - `normalizeReport()` - Deduplicate environments, suites, and tests
90
- - `createTestStepSnippetsInplace()` - Generate code snippets for test steps
90
+ - `collectSources()` - Extract source code snippets for locations in the report
91
91
  - `stripAnsi()` - Remove ANSI escape codes from strings
92
92
  - `visitTests()` - Recursively visit all tests in a report
93
93
  - `createFileAttachment()` / `createDataAttachment()` - Create report attachments
94
- - **`SystemUtilizationSampler`** - Monitor and record CPU/memory utilization during test runs
94
+ - **`CPUUtilization`** - Track CPU utilization over time via periodic sampling
95
+ - **`RAMUtilization`** - Track RAM utilization over time via periodic sampling
95
96
 
96
97
  ### Working with Reports
97
98
  - **`showReport()`** - Start a local server and open the report in your browser
package/lib/browser.js CHANGED
@@ -5,13 +5,14 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/browser.ts
8
- import { FlakinessReport } from "@flakiness/flakiness-report";
8
+ import { FlakinessReport as FlakinessReport2 } from "@flakiness/flakiness-report";
9
9
 
10
10
  // src/reportUtilsBrowser.ts
11
11
  var reportUtilsBrowser_exports = {};
12
12
  __export(reportUtilsBrowser_exports, {
13
13
  normalizeReport: () => normalizeReport,
14
14
  stripAnsi: () => stripAnsi,
15
+ validateReport: () => validateReport,
15
16
  visitTests: () => visitTests
16
17
  });
17
18
 
@@ -29,6 +30,47 @@ var Multimap = class {
29
30
  }
30
31
  };
31
32
  function normalizeReport(report) {
33
+ report = deduplicateTestsSuitesEnvironments(report);
34
+ function cleanupTestStep(step) {
35
+ return {
36
+ ...step,
37
+ duration: step.duration === 0 ? void 0 : step.duration,
38
+ steps: step.steps && step.steps.length ? step.steps.map(cleanupTestStep) : void 0
39
+ };
40
+ }
41
+ function cleanupAttempt(attempt) {
42
+ return {
43
+ ...attempt,
44
+ status: attempt.status === "passed" ? void 0 : attempt.status,
45
+ expectedStatus: attempt.expectedStatus === "passed" ? void 0 : attempt.expectedStatus,
46
+ environmentIdx: attempt.environmentIdx === 0 ? void 0 : attempt.environmentIdx,
47
+ duration: attempt.duration === 0 ? void 0 : attempt.duration,
48
+ stdout: attempt.stdout && attempt.stdout.length ? attempt.stdout : void 0,
49
+ stderr: attempt.stderr && attempt.stderr.length ? attempt.stderr : void 0,
50
+ attachments: attempt.attachments && attempt.attachments.length ? attempt.attachments : void 0,
51
+ steps: attempt.steps && attempt.steps.length ? attempt.steps.map(cleanupTestStep) : void 0
52
+ };
53
+ }
54
+ function cleanupTest(test) {
55
+ return {
56
+ ...test,
57
+ attempts: test.attempts.map(cleanupAttempt)
58
+ };
59
+ }
60
+ function cleanupSuite(suite) {
61
+ return {
62
+ ...suite,
63
+ tests: suite.tests && suite.tests.length ? suite.tests.map(cleanupTest) : void 0,
64
+ suites: suite.suites && suite.suites.length ? suite.suites.map(cleanupSuite) : void 0
65
+ };
66
+ }
67
+ return {
68
+ ...report,
69
+ tests: report.tests && report.tests.length ? report.tests.map(cleanupTest) : void 0,
70
+ suites: report.suites && report.suites.length ? report.suites.map(cleanupSuite) : void 0
71
+ };
72
+ }
73
+ function deduplicateTestsSuitesEnvironments(report) {
32
74
  const gEnvs = /* @__PURE__ */ new Map();
33
75
  const gSuites = /* @__PURE__ */ new Map();
34
76
  const gTests = new Multimap();
@@ -50,9 +92,15 @@ function normalizeReport(report) {
50
92
  gTestIds.set(test, testId);
51
93
  gSuiteTests.set(suiteId, test);
52
94
  for (const attempt of test.attempts) {
53
- const env = report.environments[attempt.environmentIdx];
95
+ const env = report.environments[attempt.environmentIdx ?? 0];
54
96
  const envId = gEnvIds.get(env);
55
97
  usedEnvIds.add(envId);
98
+ if (attempt.annotations && !attempt.annotations.length)
99
+ delete attempt.annotations;
100
+ if (attempt.stdout && !attempt.stdout.length)
101
+ delete attempt.stdout;
102
+ if (attempt.stderr && !attempt.stderr.length)
103
+ delete attempt.stderr;
56
104
  }
57
105
  }
58
106
  }
@@ -77,7 +125,7 @@ function normalizeReport(report) {
77
125
  tags: tags.length ? tags : void 0,
78
126
  attempts: tests2.map((t) => t.attempts).flat().map((attempt) => ({
79
127
  ...attempt,
80
- environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx]))
128
+ environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx ?? 0]))
81
129
  }))
82
130
  };
83
131
  });
@@ -96,14 +144,14 @@ function normalizeReport(report) {
96
144
  });
97
145
  }
98
146
  visitTests2(report.tests ?? [], "suiteless");
99
- for (const suite of report.suites)
147
+ for (const suite of report.suites ?? [])
100
148
  visitSuite(suite);
101
149
  const newEnvironments = [...usedEnvIds];
102
150
  const envIdToIndex = new Map(newEnvironments.map((envId, index) => [envId, index]));
103
151
  return {
104
152
  ...report,
105
153
  environments: newEnvironments.map((envId) => gEnvs.get(envId)),
106
- suites: transformSuites(report.suites),
154
+ suites: transformSuites(report.suites ?? []),
107
155
  tests: transformTests(report.tests ?? [])
108
156
  };
109
157
  }
@@ -126,6 +174,24 @@ function computeTestId(test, suiteId) {
126
174
  });
127
175
  }
128
176
 
177
+ // src/validateReport.ts
178
+ import { Schema } from "@flakiness/flakiness-report";
179
+ import z from "zod";
180
+ function validateReport(report) {
181
+ const validation = Schema.Report.safeParse(report);
182
+ if (!validation.success) {
183
+ const MAX_ISSUES = 5;
184
+ const allIssues = validation.error.issues;
185
+ const shownIssues = allIssues.slice(0, MAX_ISSUES);
186
+ const remaining = allIssues.length - shownIssues.length;
187
+ const base = [z.prettifyError(new z.ZodError(shownIssues))];
188
+ if (remaining > 0)
189
+ base.push(`... and ${remaining} more issue${remaining === 1 ? "" : "s"} ...`);
190
+ return base.join("\n");
191
+ }
192
+ return void 0;
193
+ }
194
+
129
195
  // src/stripAnsi.ts
130
196
  var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
131
197
  function stripAnsi(str) {
@@ -144,11 +210,11 @@ function visitTests(report, testVisitor) {
144
210
  }
145
211
  for (const test of report.tests ?? [])
146
212
  testVisitor(test, []);
147
- for (const suite of report.suites)
213
+ for (const suite of report.suites ?? [])
148
214
  visitSuite(suite, []);
149
215
  }
150
216
  export {
151
- FlakinessReport,
217
+ FlakinessReport2 as FlakinessReport,
152
218
  reportUtilsBrowser_exports as ReportUtils
153
219
  };
154
220
  //# sourceMappingURL=browser.js.map
package/lib/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/index.ts
8
- import { FlakinessReport } from "@flakiness/flakiness-report";
8
+ import { FlakinessReport as FlakinessReport2, Schema as Schema2 } from "@flakiness/flakiness-report";
9
9
 
10
10
  // src/ciUtils.ts
11
11
  var CIUtils;
@@ -46,6 +46,86 @@ function azure() {
46
46
  }
47
47
  }
48
48
 
49
+ // src/cpuUtilization.ts
50
+ import os from "os";
51
+
52
+ // src/_telemetry.ts
53
+ function addTelemetryPoint(collection, newPoint, precision) {
54
+ const lastPoint = collection.at(-1);
55
+ const preLastPoint = collection.at(-2);
56
+ if (lastPoint && preLastPoint && Math.abs(lastPoint.value - preLastPoint.value) < precision && Math.abs(lastPoint.value - newPoint.value) < precision) {
57
+ lastPoint.timestamp = newPoint.timestamp;
58
+ } else {
59
+ collection.push(newPoint);
60
+ }
61
+ }
62
+ function toProtocolTelemetry(collection) {
63
+ if (!collection.length)
64
+ return [];
65
+ let lastTimestamp = collection[0].timestamp;
66
+ return collection.map((x, idx) => {
67
+ const dts = idx === 0 ? x.timestamp : x.timestamp - lastTimestamp;
68
+ lastTimestamp = x.timestamp;
69
+ return [dts, Math.round(x.value * 100) / 100];
70
+ });
71
+ }
72
+
73
+ // src/cpuUtilization.ts
74
+ function sampleCpus() {
75
+ return os.cpus().map((cpu) => {
76
+ const totalTicks = cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.idle;
77
+ const idleTicks = cpu.times.idle;
78
+ const busyTicks = totalTicks - idleTicks;
79
+ return { totalTicks, busyTicks };
80
+ });
81
+ }
82
+ var CPUUtilization = class {
83
+ _lastSample = sampleCpus();
84
+ _precision;
85
+ _cpuAvg = [];
86
+ _cpuMax = [];
87
+ /**
88
+ * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 7.
89
+ */
90
+ constructor(options) {
91
+ this._precision = options?.precision ?? 7;
92
+ }
93
+ /**
94
+ * Records the current CPU utilization since the last sample.
95
+ * The first call primes the baseline; subsequent calls record deltas.
96
+ */
97
+ sample() {
98
+ const newSample = sampleCpus();
99
+ if (newSample.length === this._lastSample.length) {
100
+ const utilization = newSample.map(
101
+ (cpu, idx) => (
102
+ // If the CPU did no work since the last sample, then it's
103
+ // utilization is effectively 0.
104
+ cpu.totalTicks === this._lastSample[idx].totalTicks ? 0 : (cpu.busyTicks - this._lastSample[idx].busyTicks) / (cpu.totalTicks - this._lastSample[idx].totalTicks) * 100
105
+ )
106
+ );
107
+ const timestamp = Date.now();
108
+ addTelemetryPoint(this._cpuAvg, {
109
+ timestamp,
110
+ value: utilization.reduce((acc, x) => acc + x) / utilization.length
111
+ }, this._precision);
112
+ addTelemetryPoint(this._cpuMax, {
113
+ timestamp,
114
+ value: Math.max(...utilization)
115
+ }, this._precision);
116
+ }
117
+ this._lastSample = newSample;
118
+ }
119
+ /**
120
+ * Adds collected CPU telemetry to the report.
121
+ */
122
+ enrich(report) {
123
+ report.cpuCount = os.cpus().length;
124
+ report.cpuMax = toProtocolTelemetry(this._cpuMax);
125
+ report.cpuAvg = toProtocolTelemetry(this._cpuAvg);
126
+ }
127
+ };
128
+
49
129
  // src/gitWorktree.ts
50
130
  import assert from "assert";
51
131
  import { exec } from "child_process";
@@ -335,23 +415,155 @@ async function listCommits(gitRoot, head, count) {
335
415
  }
336
416
  }
337
417
 
418
+ // src/ramUtilization.ts
419
+ import { spawnSync as spawnSync2 } from "child_process";
420
+ import os2 from "os";
421
+ function getAvailableMemMacOS() {
422
+ const lines = spawnSync2("vm_stat", { encoding: "utf8" }).stdout.trim().split("\n");
423
+ const pageSize = parseInt(lines[0].match(/page size of (\d+) bytes/)[1], 10);
424
+ if (isNaN(pageSize)) {
425
+ console.warn("[flakiness.io] Error detecting macos page size");
426
+ return 0;
427
+ }
428
+ let totalFree = 0;
429
+ for (const line of lines) {
430
+ if (/Pages (free|inactive|speculative):/.test(line)) {
431
+ const match = line.match(/\d+/);
432
+ if (match)
433
+ totalFree += parseInt(match[0], 10);
434
+ }
435
+ }
436
+ return totalFree * pageSize;
437
+ }
438
+ var RAMUtilization = class {
439
+ _precision;
440
+ _totalBytes = os2.totalmem();
441
+ _ram = [];
442
+ /**
443
+ * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 1.
444
+ */
445
+ constructor(options) {
446
+ this._precision = options?.precision ?? 1;
447
+ }
448
+ /**
449
+ * Records the current RAM utilization.
450
+ */
451
+ sample() {
452
+ const freeBytes = os2.platform() === "darwin" ? getAvailableMemMacOS() : os2.freemem();
453
+ addTelemetryPoint(this._ram, {
454
+ timestamp: Date.now(),
455
+ value: (this._totalBytes - freeBytes) / this._totalBytes * 100
456
+ }, this._precision);
457
+ }
458
+ /**
459
+ * Adds collected RAM telemetry to the report.
460
+ */
461
+ enrich(report) {
462
+ report.ramBytes = this._totalBytes;
463
+ report.ram = toProtocolTelemetry(this._ram);
464
+ }
465
+ };
466
+
338
467
  // src/reportUtils.ts
339
468
  var reportUtils_exports = {};
340
469
  __export(reportUtils_exports, {
470
+ collectSources: () => collectSources,
341
471
  createDataAttachment: () => createDataAttachment,
342
472
  createEnvironment: () => createEnvironment,
343
473
  createFileAttachment: () => createFileAttachment,
344
- createTestStepSnippetsInplace: () => createTestStepSnippetsInplace,
345
474
  normalizeReport: () => normalizeReport,
346
475
  stripAnsi: () => stripAnsi,
476
+ validateReport: () => validateReport,
347
477
  visitTests: () => visitTests
348
478
  });
349
479
 
350
- // src/createEnvironment.ts
480
+ // src/collectSources.ts
351
481
  import fs2 from "fs";
352
- import os from "os";
482
+ function collectLocationsFromTestStep(testStep, onLocation) {
483
+ onLocation(testStep.location);
484
+ for (const step of testStep.steps ?? [])
485
+ collectLocationsFromTestStep(step, onLocation);
486
+ }
487
+ function collectLocationsFromTest(test, onLocation) {
488
+ onLocation(test.location);
489
+ for (const attempt of test.attempts) {
490
+ for (const annotation of attempt.annotations ?? [])
491
+ onLocation(annotation.location);
492
+ for (const err of attempt.errors ?? [])
493
+ onLocation(err.location);
494
+ for (const step of attempt.steps ?? [])
495
+ collectLocationsFromTestStep(step, onLocation);
496
+ }
497
+ }
498
+ function collectLocationsFromSuite(suite, onLocation) {
499
+ onLocation(suite.location);
500
+ for (const child of suite.suites ?? [])
501
+ collectLocationsFromSuite(child, onLocation);
502
+ for (const test of suite.tests ?? [])
503
+ collectLocationsFromTest(test, onLocation);
504
+ }
505
+ function collectLocationsFromReport(report, onLocation) {
506
+ for (const e of report.unattributedErrors ?? [])
507
+ onLocation(e.location);
508
+ for (const test of report.tests ?? [])
509
+ collectLocationsFromTest(test, onLocation);
510
+ for (const suite of report.suites ?? [])
511
+ collectLocationsFromSuite(suite, onLocation);
512
+ }
513
+ function lineNumbersToChunks(lineNumbers, options) {
514
+ const context = options.context;
515
+ const result = [];
516
+ let current;
517
+ for (const ln of Array.from(lineNumbers).sort((a, b) => a - b)) {
518
+ const span = [ln - context, ln + context];
519
+ if (!current || current[1] + 1 < span[0]) {
520
+ result.push(span);
521
+ current = span;
522
+ } else {
523
+ current[1] = span[1];
524
+ }
525
+ }
526
+ return result;
527
+ }
528
+ function collectSources(worktree, report) {
529
+ const filesToLines = /* @__PURE__ */ new Map();
530
+ collectLocationsFromReport(report, (location) => {
531
+ if (!location)
532
+ return;
533
+ let lineNumbers = filesToLines.get(location.file);
534
+ if (!lineNumbers) {
535
+ lineNumbers = /* @__PURE__ */ new Set();
536
+ filesToLines.set(location.file, lineNumbers);
537
+ }
538
+ lineNumbers.add(location.line);
539
+ });
540
+ const sources = [];
541
+ for (const [gitFilePath, lineNumbers] of filesToLines) {
542
+ let source;
543
+ try {
544
+ source = fs2.readFileSync(worktree.absolutePath(gitFilePath), "utf-8");
545
+ } catch (e) {
546
+ continue;
547
+ }
548
+ const sourceLines = source.split("\n");
549
+ for (const chunk of lineNumbersToChunks(lineNumbers, { context: 5 })) {
550
+ const from = Math.max(chunk[0] - 1, 0);
551
+ const to = Math.min(chunk[1], sourceLines.length);
552
+ sources.push({
553
+ filePath: gitFilePath,
554
+ lineOffset: from !== 0 ? from + 1 : void 0,
555
+ text: sourceLines.slice(from, to).join("\n")
556
+ });
557
+ }
558
+ }
559
+ report.sources = sources;
560
+ }
561
+
562
+ // src/createEnvironment.ts
563
+ import fs3 from "fs";
564
+ import os3 from "os";
353
565
  function readLinuxOSRelease() {
354
- const osReleaseText = fs2.readFileSync("/etc/os-release", "utf-8");
566
+ const osReleaseText = fs3.readFileSync("/etc/os-release", "utf-8");
355
567
  return new Map(osReleaseText.toLowerCase().split("\n").filter((line) => line.includes("=")).map((line) => {
356
568
  line = line.trim();
357
569
  let [key, value] = line.split("=");
@@ -376,7 +588,7 @@ function osDarwinInfo() {
376
588
  function osWinInfo() {
377
589
  const name = "win";
378
590
  const arch = process.arch;
379
- const version = os.release();
591
+ const version = os3.release();
380
592
  return { name, arch, version };
381
593
  }
382
594
  function getOSInfo() {
@@ -401,74 +613,11 @@ function createEnvironment(options) {
401
613
  osName: osInfo.name,
402
614
  osVersion: osInfo.version
403
615
  },
404
- userSuppliedData: {
616
+ metadata: {
405
617
  ...extractEnvConfiguration(),
406
- ...options.userSuppliedData ?? {}
407
- },
408
- opaqueData: options.opaqueData
409
- };
410
- }
411
-
412
- // src/createTestStepSnippets.ts
413
- import { codeFrameColumns } from "@babel/code-frame";
414
- import fs3 from "fs";
415
-
416
- // src/visitTests.ts
417
- function visitTests(report, testVisitor) {
418
- function visitSuite(suite, parents) {
419
- parents.push(suite);
420
- for (const test of suite.tests ?? [])
421
- testVisitor(test, parents);
422
- for (const childSuite of suite.suites ?? [])
423
- visitSuite(childSuite, parents);
424
- parents.pop();
425
- }
426
- for (const test of report.tests ?? [])
427
- testVisitor(test, []);
428
- for (const suite of report.suites)
429
- visitSuite(suite, []);
430
- }
431
-
432
- // src/createTestStepSnippets.ts
433
- function createTestStepSnippetsInplace(worktree, report) {
434
- const allSteps = /* @__PURE__ */ new Map();
435
- visitTests(report, (test) => {
436
- for (const attempt of test.attempts) {
437
- for (const step of attempt.steps ?? []) {
438
- if (!step.location)
439
- continue;
440
- let fileSteps = allSteps.get(step.location.file);
441
- if (!fileSteps) {
442
- fileSteps = /* @__PURE__ */ new Set();
443
- allSteps.set(step.location.file, fileSteps);
444
- }
445
- fileSteps.add(step);
446
- }
618
+ ...options.metadata ?? {}
447
619
  }
448
- });
449
- for (const [gitFilePath, steps] of allSteps) {
450
- let source;
451
- try {
452
- source = fs3.readFileSync(worktree.absolutePath(gitFilePath), "utf-8");
453
- } catch (e) {
454
- continue;
455
- }
456
- const lines = source.split("\n").length;
457
- const highlighted = codeFrameColumns(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 });
458
- const highlightedLines = highlighted.split("\n");
459
- const lineWithArrow = highlightedLines[highlightedLines.length - 1];
460
- for (const step of steps) {
461
- if (!step.location)
462
- continue;
463
- if (step.location.line < 2 || step.location.line >= lines)
464
- continue;
465
- const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1);
466
- const index = lineWithArrow.indexOf("^");
467
- const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index);
468
- snippetLines.splice(2, 0, shiftedArrow);
469
- step.snippet = snippetLines.join("\n");
470
- }
471
- }
620
+ };
472
621
  }
473
622
 
474
623
  // src/normalizeReport.ts
@@ -485,6 +634,47 @@ var Multimap = class {
485
634
  }
486
635
  };
487
636
  function normalizeReport(report) {
637
+ report = deduplicateTestsSuitesEnvironments(report);
638
+ function cleanupTestStep(step) {
639
+ return {
640
+ ...step,
641
+ duration: step.duration === 0 ? void 0 : step.duration,
642
+ steps: step.steps && step.steps.length ? step.steps.map(cleanupTestStep) : void 0
643
+ };
644
+ }
645
+ function cleanupAttempt(attempt) {
646
+ return {
647
+ ...attempt,
648
+ status: attempt.status === "passed" ? void 0 : attempt.status,
649
+ expectedStatus: attempt.expectedStatus === "passed" ? void 0 : attempt.expectedStatus,
650
+ environmentIdx: attempt.environmentIdx === 0 ? void 0 : attempt.environmentIdx,
651
+ duration: attempt.duration === 0 ? void 0 : attempt.duration,
652
+ stdout: attempt.stdout && attempt.stdout.length ? attempt.stdout : void 0,
653
+ stderr: attempt.stderr && attempt.stderr.length ? attempt.stderr : void 0,
654
+ attachments: attempt.attachments && attempt.attachments.length ? attempt.attachments : void 0,
655
+ steps: attempt.steps && attempt.steps.length ? attempt.steps.map(cleanupTestStep) : void 0
656
+ };
657
+ }
658
+ function cleanupTest(test) {
659
+ return {
660
+ ...test,
661
+ attempts: test.attempts.map(cleanupAttempt)
662
+ };
663
+ }
664
+ function cleanupSuite(suite) {
665
+ return {
666
+ ...suite,
667
+ tests: suite.tests && suite.tests.length ? suite.tests.map(cleanupTest) : void 0,
668
+ suites: suite.suites && suite.suites.length ? suite.suites.map(cleanupSuite) : void 0
669
+ };
670
+ }
671
+ return {
672
+ ...report,
673
+ tests: report.tests && report.tests.length ? report.tests.map(cleanupTest) : void 0,
674
+ suites: report.suites && report.suites.length ? report.suites.map(cleanupSuite) : void 0
675
+ };
676
+ }
677
+ function deduplicateTestsSuitesEnvironments(report) {
488
678
  const gEnvs = /* @__PURE__ */ new Map();
489
679
  const gSuites = /* @__PURE__ */ new Map();
490
680
  const gTests = new Multimap();
@@ -506,9 +696,15 @@ function normalizeReport(report) {
506
696
  gTestIds.set(test, testId);
507
697
  gSuiteTests.set(suiteId, test);
508
698
  for (const attempt of test.attempts) {
509
- const env = report.environments[attempt.environmentIdx];
699
+ const env = report.environments[attempt.environmentIdx ?? 0];
510
700
  const envId = gEnvIds.get(env);
511
701
  usedEnvIds.add(envId);
702
+ if (attempt.annotations && !attempt.annotations.length)
703
+ delete attempt.annotations;
704
+ if (attempt.stdout && !attempt.stdout.length)
705
+ delete attempt.stdout;
706
+ if (attempt.stderr && !attempt.stderr.length)
707
+ delete attempt.stderr;
512
708
  }
513
709
  }
514
710
  }
@@ -533,7 +729,7 @@ function normalizeReport(report) {
533
729
  tags: tags.length ? tags : void 0,
534
730
  attempts: tests2.map((t) => t.attempts).flat().map((attempt) => ({
535
731
  ...attempt,
536
- environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx]))
732
+ environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx ?? 0]))
537
733
  }))
538
734
  };
539
735
  });
@@ -552,14 +748,14 @@ function normalizeReport(report) {
552
748
  });
553
749
  }
554
750
  visitTests2(report.tests ?? [], "suiteless");
555
- for (const suite of report.suites)
751
+ for (const suite of report.suites ?? [])
556
752
  visitSuite(suite);
557
753
  const newEnvironments = [...usedEnvIds];
558
754
  const envIdToIndex = new Map(newEnvironments.map((envId, index) => [envId, index]));
559
755
  return {
560
756
  ...report,
561
757
  environments: newEnvironments.map((envId) => gEnvs.get(envId)),
562
- suites: transformSuites(report.suites),
758
+ suites: transformSuites(report.suites ?? []),
563
759
  tests: transformTests(report.tests ?? [])
564
760
  };
565
761
  }
@@ -582,6 +778,24 @@ function computeTestId(test, suiteId) {
582
778
  });
583
779
  }
584
780
 
781
+ // src/validateReport.ts
782
+ import { Schema } from "@flakiness/flakiness-report";
783
+ import z from "zod";
784
+ function validateReport(report) {
785
+ const validation = Schema.Report.safeParse(report);
786
+ if (!validation.success) {
787
+ const MAX_ISSUES = 5;
788
+ const allIssues = validation.error.issues;
789
+ const shownIssues = allIssues.slice(0, MAX_ISSUES);
790
+ const remaining = allIssues.length - shownIssues.length;
791
+ const base = [z.prettifyError(new z.ZodError(shownIssues))];
792
+ if (remaining > 0)
793
+ base.push(`... and ${remaining} more issue${remaining === 1 ? "" : "s"} ...`);
794
+ return base.join("\n");
795
+ }
796
+ return void 0;
797
+ }
798
+
585
799
  // src/stripAnsi.ts
586
800
  var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
587
801
  function stripAnsi(str) {
@@ -758,94 +972,21 @@ var ReportUpload = class {
758
972
  }
759
973
  };
760
974
 
761
- // src/systemUtilizationSampler.ts
762
- import { spawnSync as spawnSync2 } from "child_process";
763
- import os2 from "os";
764
- function getAvailableMemMacOS() {
765
- const lines = spawnSync2("vm_stat", { encoding: "utf8" }).stdout.trim().split("\n");
766
- const pageSize = parseInt(lines[0].match(/page size of (\d+) bytes/)[1], 10);
767
- if (isNaN(pageSize)) {
768
- console.warn("[flakiness.io] Error detecting macos page size");
769
- return 0;
770
- }
771
- let totalFree = 0;
772
- for (const line of lines) {
773
- if (/Pages (free|inactive|speculative):/.test(line)) {
774
- const match = line.match(/\d+/);
775
- if (match)
776
- totalFree += parseInt(match[0], 10);
777
- }
778
- }
779
- return totalFree * pageSize;
780
- }
781
- function getSystemUtilization() {
782
- let idleTicks = 0;
783
- let totalTicks = 0;
784
- for (const cpu of os2.cpus()) {
785
- totalTicks += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.idle;
786
- idleTicks += cpu.times.idle;
975
+ // src/visitTests.ts
976
+ function visitTests(report, testVisitor) {
977
+ function visitSuite(suite, parents) {
978
+ parents.push(suite);
979
+ for (const test of suite.tests ?? [])
980
+ testVisitor(test, parents);
981
+ for (const childSuite of suite.suites ?? [])
982
+ visitSuite(childSuite, parents);
983
+ parents.pop();
787
984
  }
788
- return {
789
- idleTicks,
790
- totalTicks,
791
- timestamp: Date.now(),
792
- freeBytes: os2.platform() === "darwin" ? getAvailableMemMacOS() : os2.freemem()
793
- };
794
- }
795
- function toFKUtilization(sample, previous) {
796
- const idleTicks = sample.idleTicks - previous.idleTicks;
797
- const totalTicks = sample.totalTicks - previous.totalTicks;
798
- const cpuUtilization = Math.floor((1 - idleTicks / totalTicks) * 1e4) / 100;
799
- const memoryUtilization = Math.floor((1 - sample.freeBytes / os2.totalmem()) * 1e4) / 100;
800
- return {
801
- cpuUtilization,
802
- memoryUtilization,
803
- dts: sample.timestamp - previous.timestamp
804
- };
985
+ for (const test of report.tests ?? [])
986
+ testVisitor(test, []);
987
+ for (const suite of report.suites ?? [])
988
+ visitSuite(suite, []);
805
989
  }
806
- var SystemUtilizationSampler = class {
807
- /**
808
- * The accumulated system utilization data.
809
- *
810
- * This object is populated as samples are collected and can be directly included in
811
- * Flakiness reports. It contains:
812
- * - `samples` - Array of utilization samples with CPU/memory percentages and durations
813
- * - `startTimestamp` - Timestamp when sampling began
814
- * - `totalMemoryBytes` - Total system memory in bytes
815
- */
816
- result;
817
- _lastSample = getSystemUtilization();
818
- _timer;
819
- /**
820
- * Creates a new SystemUtilizationSampler and starts sampling immediately.
821
- *
822
- * The first sample is collected after 50ms, and subsequent samples are collected
823
- * every 1000ms. Call `dispose()` to stop sampling and clean up resources.
824
- */
825
- constructor() {
826
- this.result = {
827
- samples: [],
828
- startTimestamp: this._lastSample.timestamp,
829
- totalMemoryBytes: os2.totalmem()
830
- };
831
- this._timer = setTimeout(this._addSample.bind(this), 50);
832
- }
833
- _addSample() {
834
- const sample = getSystemUtilization();
835
- this.result.samples.push(toFKUtilization(sample, this._lastSample));
836
- this._lastSample = sample;
837
- this._timer = setTimeout(this._addSample.bind(this), 1e3);
838
- }
839
- /**
840
- * Stops sampling and cleans up resources.
841
- *
842
- * Call this method when you're done collecting utilization data to stop the sampling
843
- * timer and prevent memory leaks. The `result` object remains accessible after disposal.
844
- */
845
- dispose() {
846
- clearTimeout(this._timer);
847
- }
848
- };
849
990
 
850
991
  // src/showReport.ts
851
992
  import chalk from "chalk";
@@ -1187,11 +1328,13 @@ async function writeReport(report, attachments, outputFolder) {
1187
1328
  }
1188
1329
  export {
1189
1330
  CIUtils,
1331
+ CPUUtilization,
1190
1332
  FlakinessProjectConfig,
1191
- FlakinessReport,
1333
+ FlakinessReport2 as FlakinessReport,
1192
1334
  GitWorktree,
1335
+ RAMUtilization,
1193
1336
  reportUtils_exports as ReportUtils,
1194
- SystemUtilizationSampler,
1337
+ Schema2 as Schema,
1195
1338
  showReport,
1196
1339
  uploadReport,
1197
1340
  writeReport
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flakiness/sdk",
3
- "version": "0.151.0",
3
+ "version": "0.153.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  }
20
20
  },
21
21
  "type": "module",
22
- "description": "",
22
+ "description": "Comprehensive SDK for creating and managing Flakiness JSON Reports in Node.js",
23
23
  "types": "./types/index.d.ts",
24
24
  "scripts": {
25
25
  "minor": "./version.mjs minor",
@@ -29,7 +29,6 @@
29
29
  "author": "Degu Labs, Inc",
30
30
  "license": "MIT",
31
31
  "devDependencies": {
32
- "@types/babel__code-frame": "^7.0.6",
33
32
  "@types/debug": "^4.1.12",
34
33
  "@types/node": "^25.0.3",
35
34
  "esbuild": "^0.27.0",
@@ -38,11 +37,11 @@
38
37
  "typescript": "^5.6.2"
39
38
  },
40
39
  "dependencies": {
41
- "@babel/code-frame": "^7.26.2",
42
- "@flakiness/flakiness-report": "^0.16.0",
40
+ "@flakiness/flakiness-report": "^0.20.0",
43
41
  "chalk": "^5.6.2",
44
42
  "debug": "^4.4.3",
45
43
  "open": "^10.2.0",
46
- "stable-hash": "^0.0.6"
44
+ "stable-hash": "^0.0.6",
45
+ "zod": "^4.3.5"
47
46
  }
48
47
  }
@@ -0,0 +1,7 @@
1
+ export type TelemetryPoint = {
2
+ timestamp: number;
3
+ value: number;
4
+ };
5
+ export declare function addTelemetryPoint(collection: TelemetryPoint[], newPoint: TelemetryPoint, precision: number): void;
6
+ export declare function toProtocolTelemetry(collection: TelemetryPoint[]): [number, number][];
7
+ //# sourceMappingURL=_telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_telemetry.d.ts","sourceRoot":"","sources":["../../src/_telemetry.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAY1G;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CASpF"}
@@ -0,0 +1,14 @@
1
+ import { FlakinessReport as FK } from '@flakiness/flakiness-report';
2
+ import { GitWorktree } from './gitWorktree.js';
3
+ /**
4
+ * Extracts source code snippets referenced by locations in the report.
5
+ *
6
+ * Scans all locations in the report (tests, steps, errors, annotations) and collects
7
+ * the relevant source code chunks with surrounding context (±5 lines). The collected
8
+ * sources are stored directly in `report.sources`.
9
+ *
10
+ * @param worktree - Git worktree for resolving file paths.
11
+ * @param report - Report to scan and enrich with source snippets.
12
+ */
13
+ export declare function collectSources(worktree: GitWorktree, report: FK.Report): void;
14
+ //# sourceMappingURL=collectSources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collectSources.d.ts","sourceRoot":"","sources":["../../src/collectSources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAqD/C;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,QAiCtE"}
@@ -0,0 +1,30 @@
1
+ import { FlakinessReport as FK } from "@flakiness/flakiness-report";
2
+ /**
3
+ * Tracks CPU utilization over time by recording periodic samples.
4
+ *
5
+ * Call `sample()` at desired intervals (e.g., test start/end, every second) to record
6
+ * CPU usage. The class tracks both average and max utilization across all CPU cores.
7
+ * Use `enrich()` to add the collected telemetry to a report.
8
+ */
9
+ export declare class CPUUtilization {
10
+ private _lastSample;
11
+ private _precision;
12
+ private _cpuAvg;
13
+ private _cpuMax;
14
+ /**
15
+ * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 7.
16
+ */
17
+ constructor(options?: {
18
+ precision?: number;
19
+ });
20
+ /**
21
+ * Records the current CPU utilization since the last sample.
22
+ * The first call primes the baseline; subsequent calls record deltas.
23
+ */
24
+ sample(): void;
25
+ /**
26
+ * Adds collected CPU telemetry to the report.
27
+ */
28
+ enrich(report: FK.Report): void;
29
+ }
30
+ //# sourceMappingURL=cpuUtilization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpuUtilization.d.ts","sourceRoot":"","sources":["../../src/cpuUtilization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAkBpE;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAgB;IAEnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAAwB;IAEvC;;OAEG;gBACS,OAAO,CAAC,EAAE;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAID;;;OAGG;IACH,MAAM;IAwBN;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAKzB"}
@@ -9,16 +9,13 @@ import { FlakinessReport } from '@flakiness/flakiness-report';
9
9
  *
10
10
  * @param {Object} options - Configuration object for the environment.
11
11
  * @param {string} options.name - Human-readable name for the environment (e.g., 'CI', 'Local Dev', 'Staging').
12
- * @param {Record<string, string>} [options.userSuppliedData] - Additional key-value pairs to include
12
+ * @param {Record<string, string>} [options.metadata] - Additional key-value pairs to include
13
13
  * in the environment data. These are merged with `FK_ENV_*` environment variables.
14
- * @param {any} [options.opaqueData] - Optional opaque data object that will be stored with the
15
- * environment but not used for environment deduplication.
16
14
  *
17
15
  * @returns {FlakinessReport.Environment} Environment object containing:
18
16
  * - `name` - The provided environment name
19
17
  * - `systemData` - Automatically detected OS information (arch, name, version)
20
- * - `userSuppliedData` - Merged data from `FK_ENV_*` variables and `userSuppliedData` option
21
- * - `opaqueData` - The provided opaque data, if any
18
+ * - `metadata` - Merged data from `FK_ENV_*` variables and `userSuppliedData` option
22
19
  *
23
20
  * @example
24
21
  * ```typescript
@@ -28,13 +25,12 @@ import { FlakinessReport } from '@flakiness/flakiness-report';
28
25
  * // With custom data
29
26
  * const env = createEnvironment({
30
27
  * name: 'Staging',
31
- * userSuppliedData: { region: 'us-east-1', instance: 'large' }
28
+ * metadata: { region: 'us-east-1', instance: 'large' }
32
29
  * });
33
30
  * ```
34
31
  */
35
32
  export declare function createEnvironment(options: {
36
33
  name: string;
37
- userSuppliedData?: Record<string, string>;
38
- opaqueData?: any;
34
+ metadata?: Record<string, string>;
39
35
  }): FlakinessReport.Environment;
40
36
  //# sourceMappingURL=createEnvironment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createEnvironment.d.ts","sourceRoot":"","sources":["../../src/createEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAwD9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB,GAAG,eAAe,CAAC,WAAW,CAe9B"}
1
+ {"version":3,"file":"createEnvironment.d.ts","sourceRoot":"","sources":["../../src/createEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAwD9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,GAAG,eAAe,CAAC,WAAW,CAc9B"}
@@ -1,8 +1,9 @@
1
- export { FlakinessReport } from '@flakiness/flakiness-report';
1
+ export { FlakinessReport, Schema } from '@flakiness/flakiness-report';
2
2
  export { CIUtils } from './ciUtils.js';
3
+ export { CPUUtilization } from './cpuUtilization.js';
3
4
  export { GitWorktree } from './gitWorktree.js';
5
+ export { RAMUtilization } from './ramUtilization.js';
4
6
  export * as ReportUtils from './reportUtils.js';
5
- export { SystemUtilizationSampler } from './systemUtilizationSampler.js';
6
7
  export { showReport } from './showReport.js';
7
8
  export { uploadReport } from './uploadReport.js';
8
9
  export { writeReport } from './writeReport.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAGzE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAGtE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { FlakinessReport } from "@flakiness/flakiness-report";
2
2
  /**
3
3
  * Normalizes a Flakiness report by deduplicating environments, suites, and tests.
4
+ * It also drops the fields from JSON that are equal to their default values.
4
5
  *
5
6
  * This function processes a report to:
6
7
  * - Deduplicate environments based on their content (using stable hashing)
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeReport.d.ts","sourceRoot":"","sources":["../../src/normalizeReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyB9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM,CA2FtF"}
1
+ {"version":3,"file":"normalizeReport.d.ts","sourceRoot":"","sources":["../../src/normalizeReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyB9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM,CA+CtF"}
@@ -0,0 +1,28 @@
1
+ import { FlakinessReport as FK } from '@flakiness/flakiness-report';
2
+ /**
3
+ * Tracks RAM utilization over time by recording periodic samples.
4
+ *
5
+ * Call `sample()` at desired intervals (e.g., test start/end, every second) to record
6
+ * memory usage as a percentage of total system RAM. Use `enrich()` to add the collected
7
+ * telemetry to a report.
8
+ */
9
+ export declare class RAMUtilization {
10
+ private _precision;
11
+ private _totalBytes;
12
+ private _ram;
13
+ /**
14
+ * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 1.
15
+ */
16
+ constructor(options?: {
17
+ precision?: number;
18
+ });
19
+ /**
20
+ * Records the current RAM utilization.
21
+ */
22
+ sample(): void;
23
+ /**
24
+ * Adds collected RAM telemetry to the report.
25
+ */
26
+ enrich(report: FK.Report): void;
27
+ }
28
+ //# sourceMappingURL=ramUtilization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ramUtilization.d.ts","sourceRoot":"","sources":["../../src/ramUtilization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AA4BpE;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAiB;IAEpC,OAAO,CAAC,IAAI,CAAwB;IAEpC;;OAEG;gBACS,OAAO,CAAC,EAAE;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAID;;OAEG;IACH,MAAM;IAQN;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAIzB"}
@@ -1,6 +1,7 @@
1
+ export { collectSources } from './collectSources.js';
1
2
  export { createEnvironment } from './createEnvironment.js';
2
- export { createTestStepSnippetsInplace } from './createTestStepSnippets.js';
3
3
  export { normalizeReport } from './normalizeReport.js';
4
+ export { validateReport } from './validateReport.js';
4
5
  export { stripAnsi } from './stripAnsi.js';
5
6
  export { createDataAttachment, createFileAttachment } from './uploadReport.js';
6
7
  export type { Attachment, DataAttachment, FileAttachment } from './uploadReport.js';
@@ -1 +1 @@
1
- {"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { normalizeReport } from './normalizeReport.js';
2
+ export { validateReport } from './validateReport.js';
2
3
  export { stripAnsi } from './stripAnsi.js';
3
4
  export { visitTests } from './visitTests.js';
4
5
  //# sourceMappingURL=reportUtilsBrowser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reportUtilsBrowser.d.ts","sourceRoot":"","sources":["../../src/reportUtilsBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"reportUtilsBrowser.d.ts","sourceRoot":"","sources":["../../src/reportUtilsBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { FlakinessReport } from '@flakiness/flakiness-report';
2
+ /**
3
+ * Validates a report object against the Flakiness Report schema.
4
+ *
5
+ * @param report - The report object to validate
6
+ * @returns A formatted error string if validation fails, or `undefined` if the report is valid
7
+ */
8
+ export declare function validateReport(report: FlakinessReport.Report): string | undefined;
9
+ //# sourceMappingURL=validateReport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateReport.d.ts","sourceRoot":"","sources":["../../src/validateReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAU,MAAM,6BAA6B,CAAC;AAGtE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,MAAM,GAAC,SAAS,CAe/E"}
@@ -1,30 +0,0 @@
1
- import { FlakinessReport } from '@flakiness/flakiness-report';
2
- import { GitWorktree } from './gitWorktree.js';
3
- /**
4
- * Generates code snippets for test steps and attaches them to the report in-place.
5
- *
6
- * This function reads source files from the git worktree and creates highlighted code snippets
7
- * for each test step that has a location. The snippets include 3 lines of context (1 before,
8
- * the line itself, 1 after) with syntax highlighting and a visual indicator pointing to the
9
- * exact column position.
10
- *
11
- * The snippets are attached directly to the `step.snippet` property of each test step in the
12
- * report object. Steps without locations or with invalid file paths are silently skipped.
13
- *
14
- * @param {GitWorktree} worktree - Git worktree instance used to resolve file paths from
15
- * git-relative paths to absolute paths for reading source files.
16
- *
17
- * @param {FlakinessReport.Report} report - Flakiness report to process. The report is modified
18
- * in-place by adding `snippet` properties to test steps.
19
- *
20
- * @returns {void} This function modifies the report in-place and does not return a value.
21
- *
22
- * @example
23
- * ```typescript
24
- * const worktree = GitWorktree.create(process.cwd());
25
- * createTestStepSnippetsInplace(worktree, report);
26
- * // Report steps now have .snippet properties with highlighted code
27
- * ```
28
- */
29
- export declare function createTestStepSnippetsInplace(worktree: GitWorktree, report: FlakinessReport.Report): void;
30
- //# sourceMappingURL=createTestStepSnippets.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createTestStepSnippets.d.ts","sourceRoot":"","sources":["../../src/createTestStepSnippets.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,QA4ClG"}
@@ -1,43 +0,0 @@
1
- import { FlakinessReport } from "@flakiness/flakiness-report";
2
- /**
3
- * Samples and records system CPU and memory utilization over time.
4
- *
5
- * This class continuously monitors system resource usage at regular intervals and stores
6
- * the samples in a format suitable for inclusion in Flakiness reports. Sampling starts
7
- * immediately upon construction and continues until `dispose()` is called.
8
- *
9
- * The first sample is collected after 50ms, and subsequent samples are collected every
10
- * 1000ms (1 second). CPU utilization is calculated as a percentage based on CPU tick
11
- * differences between samples. Memory utilization uses platform-specific methods for
12
- * accurate measurement (especially on macOS).
13
- */
14
- export declare class SystemUtilizationSampler {
15
- /**
16
- * The accumulated system utilization data.
17
- *
18
- * This object is populated as samples are collected and can be directly included in
19
- * Flakiness reports. It contains:
20
- * - `samples` - Array of utilization samples with CPU/memory percentages and durations
21
- * - `startTimestamp` - Timestamp when sampling began
22
- * - `totalMemoryBytes` - Total system memory in bytes
23
- */
24
- readonly result: FlakinessReport.SystemUtilization;
25
- private _lastSample;
26
- private _timer;
27
- /**
28
- * Creates a new SystemUtilizationSampler and starts sampling immediately.
29
- *
30
- * The first sample is collected after 50ms, and subsequent samples are collected
31
- * every 1000ms. Call `dispose()` to stop sampling and clean up resources.
32
- */
33
- constructor();
34
- private _addSample;
35
- /**
36
- * Stops sampling and cleans up resources.
37
- *
38
- * Call this method when you're done collecting utilization data to stop the sampling
39
- * timer and prevent memory leaks. The `result` object remains accessible after disposal.
40
- */
41
- dispose(): void;
42
- }
43
- //# sourceMappingURL=systemUtilizationSampler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"systemUtilizationSampler.d.ts","sourceRoot":"","sources":["../../src/systemUtilizationSampler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA6D9D;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAwB;IACnC;;;;;;;;OAQG;IACH,SAAgB,MAAM,EAAE,eAAe,CAAC,iBAAiB,CAAC;IAE1D,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,MAAM,CAAiB;IAE/B;;;;;OAKG;;IAWH,OAAO,CAAC,UAAU;IAOlB;;;;;OAKG;IACH,OAAO;CAGR"}