flakiness 0.0.0 → 0.146.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/lib/cli/cli.js ADDED
@@ -0,0 +1,1980 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/cli.ts
4
+ import { showReport } from "@flakiness/sdk";
5
+ import { FlakinessProjectConfig as FlakinessProjectConfig4 } from "@flakiness/sdk/flakinessProjectConfig";
6
+
7
+ // ../node_modules/vlq/src/index.js
8
+ var char_to_integer = {};
9
+ var integer_to_char = {};
10
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split("").forEach(function(char, i) {
11
+ char_to_integer[char] = i;
12
+ integer_to_char[i] = char;
13
+ });
14
+ function decode(string) {
15
+ let result = [];
16
+ let shift = 0;
17
+ let value = 0;
18
+ for (let i = 0; i < string.length; i += 1) {
19
+ let integer = char_to_integer[string[i]];
20
+ if (integer === void 0) {
21
+ throw new Error("Invalid character (" + string[i] + ")");
22
+ }
23
+ const has_continuation_bit = integer & 32;
24
+ integer &= 31;
25
+ value += integer << shift;
26
+ if (has_continuation_bit) {
27
+ shift += 5;
28
+ } else {
29
+ const should_negate = value & 1;
30
+ value >>>= 1;
31
+ if (should_negate) {
32
+ result.push(value === 0 ? -2147483648 : -value);
33
+ } else {
34
+ result.push(value);
35
+ }
36
+ value = shift = 0;
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ function encode(value) {
42
+ if (typeof value === "number") {
43
+ return encode_integer(value);
44
+ }
45
+ let result = "";
46
+ for (let i = 0; i < value.length; i += 1) {
47
+ result += encode_integer(value[i]);
48
+ }
49
+ return result;
50
+ }
51
+ function encode_integer(num) {
52
+ let result = "";
53
+ if (num < 0) {
54
+ num = -num << 1 | 1;
55
+ } else {
56
+ num <<= 1;
57
+ }
58
+ do {
59
+ let clamped = num & 31;
60
+ num >>>= 5;
61
+ if (num > 0) {
62
+ clamped |= 32;
63
+ }
64
+ result += integer_to_char[clamped];
65
+ } while (num > 0);
66
+ return result;
67
+ }
68
+
69
+ // ../server/lib/common/heap.js
70
+ var Heap = class _Heap {
71
+ constructor(_cmp, elements = []) {
72
+ this._cmp = _cmp;
73
+ this._heap = elements.map(([element, score]) => ({ element, score }));
74
+ for (let idx = this._heap.length - 1; idx >= 0; --idx)
75
+ this._down(idx);
76
+ }
77
+ static createMin(elements = []) {
78
+ return new _Heap((a, b) => a - b, elements);
79
+ }
80
+ static createMax(elements = []) {
81
+ return new _Heap((a, b) => b - a, elements);
82
+ }
83
+ _heap = [];
84
+ _up(idx) {
85
+ const e = this._heap[idx];
86
+ while (idx > 0) {
87
+ const parentIdx = idx - 1 >>> 1;
88
+ if (this._cmp(this._heap[parentIdx].score, e.score) <= 0)
89
+ break;
90
+ this._heap[idx] = this._heap[parentIdx];
91
+ idx = parentIdx;
92
+ }
93
+ this._heap[idx] = e;
94
+ }
95
+ _down(idx) {
96
+ const N = this._heap.length;
97
+ const e = this._heap[idx];
98
+ while (true) {
99
+ const leftIdx = idx * 2 + 1;
100
+ const rightIdx = idx * 2 + 2;
101
+ let smallestIdx;
102
+ if (leftIdx < N && rightIdx < N) {
103
+ smallestIdx = this._cmp(this._heap[leftIdx].score, this._heap[rightIdx].score) < 0 ? leftIdx : rightIdx;
104
+ } else if (leftIdx < N) {
105
+ smallestIdx = leftIdx;
106
+ } else if (rightIdx < N) {
107
+ smallestIdx = rightIdx;
108
+ } else {
109
+ break;
110
+ }
111
+ if (this._cmp(e.score, this._heap[smallestIdx].score) < 0)
112
+ break;
113
+ this._heap[idx] = this._heap[smallestIdx];
114
+ idx = smallestIdx;
115
+ }
116
+ this._heap[idx] = e;
117
+ }
118
+ push(element, score) {
119
+ this._heap.push({ element, score });
120
+ this._up(this._heap.length - 1);
121
+ }
122
+ get size() {
123
+ return this._heap.length;
124
+ }
125
+ peekEntry() {
126
+ return this._heap.length ? [this._heap[0].element, this._heap[0].score] : void 0;
127
+ }
128
+ popEntry() {
129
+ if (!this._heap.length)
130
+ return void 0;
131
+ const entry = this._heap[0];
132
+ const last = this._heap.pop();
133
+ if (!this._heap.length)
134
+ return [entry.element, entry.score];
135
+ this._heap[0] = last;
136
+ this._down(0);
137
+ return [entry.element, entry.score];
138
+ }
139
+ };
140
+
141
+ // ../server/lib/common/sequence.js
142
+ var Sequence = class _Sequence {
143
+ constructor(_seek, length) {
144
+ this._seek = _seek;
145
+ this.length = length;
146
+ }
147
+ static fromList(a) {
148
+ return new _Sequence(
149
+ function(pos) {
150
+ return {
151
+ next() {
152
+ if (pos >= a.length)
153
+ return { done: true, value: void 0 };
154
+ return { done: false, value: a[pos++] };
155
+ }
156
+ };
157
+ },
158
+ a.length
159
+ );
160
+ }
161
+ static chain(seqs) {
162
+ const leftsums = [];
163
+ let length = 0;
164
+ for (let i = 0; i < seqs.length; ++i) {
165
+ length += seqs[i].length;
166
+ leftsums.push(length);
167
+ }
168
+ return new _Sequence(
169
+ function(fromIdx) {
170
+ fromIdx = Math.max(0, Math.min(length, fromIdx));
171
+ let idx = _Sequence.fromList(leftsums).partitionPoint(((x) => x <= fromIdx));
172
+ if (idx >= seqs.length) {
173
+ return {
174
+ next: () => ({ done: true, value: void 0 })
175
+ };
176
+ }
177
+ let it = seqs[idx].seek(idx > 0 ? fromIdx - leftsums[idx - 1] : fromIdx);
178
+ return {
179
+ next() {
180
+ let result = it.next();
181
+ while (result.done && ++idx < seqs.length) {
182
+ it = seqs[idx].seek(0);
183
+ result = it.next();
184
+ }
185
+ return result.done ? result : { done: false, value: [result.value, idx] };
186
+ }
187
+ };
188
+ },
189
+ length
190
+ );
191
+ }
192
+ static merge(sequences, cmp) {
193
+ const length = sequences.reduce((acc, seq) => acc + seq.length, 0);
194
+ return new _Sequence(
195
+ function(fromIdx) {
196
+ fromIdx = Math.max(0, Math.min(length, fromIdx));
197
+ const offsets = quickAdvance(sequences, cmp, fromIdx);
198
+ const entries = [];
199
+ for (let i = 0; i < sequences.length; ++i) {
200
+ const seq = sequences[i];
201
+ const it = seq.seek(offsets[i]);
202
+ const itval = it.next();
203
+ if (!itval.done)
204
+ entries.push([it, itval.value]);
205
+ }
206
+ const heap = new Heap(cmp, entries);
207
+ return {
208
+ next() {
209
+ if (!heap.size)
210
+ return { done: true, value: void 0 };
211
+ ++fromIdx;
212
+ const [it, e] = heap.popEntry();
213
+ const itval = it.next();
214
+ if (!itval.done)
215
+ heap.push(it, itval.value);
216
+ return { done: false, value: e };
217
+ }
218
+ };
219
+ },
220
+ length
221
+ );
222
+ }
223
+ static EMPTY = new _Sequence(function* () {
224
+ }, 0);
225
+ seek(idx) {
226
+ const it = this._seek(idx);
227
+ it[Symbol.iterator] = () => it;
228
+ return it;
229
+ }
230
+ get(idx) {
231
+ return this.seek(idx).next().value;
232
+ }
233
+ map(mapper) {
234
+ const originalSeek = this._seek;
235
+ return new _Sequence(
236
+ function(idx) {
237
+ const it = originalSeek(idx);
238
+ return {
239
+ next() {
240
+ const next = it.next();
241
+ if (next.done)
242
+ return next;
243
+ return { done: false, value: mapper(next.value) };
244
+ }
245
+ };
246
+ },
247
+ this.length
248
+ );
249
+ }
250
+ /** Number of elements in sequence that are <= comparator. Only works on sorted sequences. */
251
+ partitionPoint(predicate) {
252
+ let lo = 0, hi = this.length;
253
+ while (lo < hi) {
254
+ const mid = lo + hi >>> 1;
255
+ if (predicate(this.get(mid)))
256
+ lo = mid + 1;
257
+ else
258
+ hi = mid;
259
+ }
260
+ return lo;
261
+ }
262
+ };
263
+ function quickAdvance(sequences, cmp, k) {
264
+ const offsets = new Map(sequences.map((s) => [s, 0]));
265
+ while (offsets.size && k > 0) {
266
+ const t = offsets.size;
267
+ const x = Math.max(Math.floor(k / t / 2), 1);
268
+ const entries = [];
269
+ for (const [seq, offset] of offsets) {
270
+ if (offset + x <= seq.length)
271
+ entries.push([seq, seq.get(offset + x - 1)]);
272
+ }
273
+ const heap = new Heap(cmp, entries);
274
+ while (heap.size && k > 0 && (x === 1 || k >= x * t)) {
275
+ k -= x;
276
+ const [seq] = heap.popEntry();
277
+ const offset = offsets.get(seq) + x;
278
+ if (offset === seq.length)
279
+ offsets.delete(seq);
280
+ else
281
+ offsets.set(seq, offset);
282
+ if (offset + x <= seq.length)
283
+ heap.push(seq, seq.get(offset + x - 1));
284
+ }
285
+ }
286
+ return sequences.map((seq) => offsets.get(seq) ?? seq.length);
287
+ }
288
+
289
+ // ../server/lib/common/ranges.js
290
+ var Ranges;
291
+ ((Ranges2) => {
292
+ Ranges2.EMPTY = [];
293
+ Ranges2.FULL = [-Infinity, Infinity];
294
+ function isFull(ranges) {
295
+ return ranges.length === 2 && Object.is(ranges[0], -Infinity) && Object.is(ranges[1], Infinity);
296
+ }
297
+ Ranges2.isFull = isFull;
298
+ function compress(ranges) {
299
+ if (!ranges.length)
300
+ return "";
301
+ if (isInfinite(ranges))
302
+ throw new Error("Compression of infinite ranges is not supported");
303
+ const prepared = [];
304
+ let last = ranges[0] - 1;
305
+ prepared.push(last);
306
+ for (let i = 0; i < ranges.length; i += 2) {
307
+ if (ranges[i] === ranges[i + 1]) {
308
+ prepared.push(-(ranges[i] - last));
309
+ } else {
310
+ prepared.push(ranges[i] - last);
311
+ prepared.push(ranges[i + 1] - ranges[i]);
312
+ }
313
+ last = ranges[i + 1];
314
+ }
315
+ return encode(prepared);
316
+ }
317
+ Ranges2.compress = compress;
318
+ function decompress(compressed) {
319
+ if (!compressed.length)
320
+ return [];
321
+ const prepared = decode(compressed);
322
+ const result = [];
323
+ let last = prepared[0];
324
+ for (let i = 1; i < prepared.length; ++i) {
325
+ if (prepared[i] < 0) {
326
+ result.push(-prepared[i] + last);
327
+ result.push(-prepared[i] + last);
328
+ last -= prepared[i];
329
+ } else {
330
+ result.push(prepared[i] + last);
331
+ last += prepared[i];
332
+ }
333
+ }
334
+ return result;
335
+ }
336
+ Ranges2.decompress = decompress;
337
+ function toString(ranges) {
338
+ const tokens = [];
339
+ for (let i = 0; i < ranges.length - 1; i += 2) {
340
+ if (ranges[i] === ranges[i + 1])
341
+ tokens.push(ranges[i]);
342
+ else
343
+ tokens.push(`${ranges[i]}-${ranges[i + 1]}`);
344
+ }
345
+ if (!tokens.length)
346
+ return `[]`;
347
+ return `[ ` + tokens.join(", ") + ` ]`;
348
+ }
349
+ Ranges2.toString = toString;
350
+ function popInplace(ranges) {
351
+ if (isInfinite(ranges))
352
+ throw new Error("cannot pop from infinite ranges!");
353
+ const last = ranges.at(-1);
354
+ const prelast = ranges.at(-2);
355
+ if (last === void 0 || prelast === void 0)
356
+ return void 0;
357
+ if (last === prelast) {
358
+ ranges.pop();
359
+ ranges.pop();
360
+ } else {
361
+ ranges[ranges.length - 1] = last - 1;
362
+ }
363
+ return last;
364
+ }
365
+ Ranges2.popInplace = popInplace;
366
+ function* iterate(ranges) {
367
+ if (isInfinite(ranges))
368
+ throw new Error("cannot iterate infinite ranges!");
369
+ for (let i = 0; i < ranges.length - 1; i += 2) {
370
+ for (let j = ranges[i]; j <= ranges[i + 1]; ++j)
371
+ yield j;
372
+ }
373
+ }
374
+ Ranges2.iterate = iterate;
375
+ function toSortedList(ranges) {
376
+ if (isInfinite(ranges))
377
+ throw new Error("cannot convert infinite ranges!");
378
+ const list = [];
379
+ for (let i = 0; i < ranges.length - 1; i += 2) {
380
+ for (let j = ranges[i]; j <= ranges[i + 1]; ++j)
381
+ list.push(j);
382
+ }
383
+ return list;
384
+ }
385
+ Ranges2.toSortedList = toSortedList;
386
+ function toInt32Array(ranges) {
387
+ if (isInfinite(ranges))
388
+ throw new Error("cannot convert infinite ranges!");
389
+ const result = new Int32Array(cardinality(ranges));
390
+ let idx = 0;
391
+ for (let i = 0; i < ranges.length - 1; i += 2) {
392
+ for (let j = ranges[i]; j <= ranges[i + 1]; ++j)
393
+ result[idx++] = j;
394
+ }
395
+ return result;
396
+ }
397
+ Ranges2.toInt32Array = toInt32Array;
398
+ function fromList(x) {
399
+ for (let i = 0; i < x.length - 1; ++i) {
400
+ if (x[i] > x[i + 1]) {
401
+ x = x.toSorted((a, b) => a - b);
402
+ break;
403
+ }
404
+ }
405
+ return fromSortedList(x);
406
+ }
407
+ Ranges2.fromList = fromList;
408
+ function from(x) {
409
+ return [x, x];
410
+ }
411
+ Ranges2.from = from;
412
+ function fromSortedList(sorted) {
413
+ const ranges = [];
414
+ let rangeStart = 0;
415
+ for (let i = 1; i <= sorted.length; ++i) {
416
+ if (i < sorted.length && sorted[i] - sorted[i - 1] <= 1)
417
+ continue;
418
+ ranges.push(sorted[rangeStart], sorted[i - 1]);
419
+ rangeStart = i;
420
+ }
421
+ return ranges;
422
+ }
423
+ Ranges2.fromSortedList = fromSortedList;
424
+ function isInfinite(ranges) {
425
+ return ranges.length > 0 && (Object.is(ranges[0], Infinity) || Object.is(ranges[ranges.length - 1], Infinity));
426
+ }
427
+ Ranges2.isInfinite = isInfinite;
428
+ function includes(ranges, e) {
429
+ if (!ranges.length)
430
+ return false;
431
+ if (e < ranges[0] || ranges[ranges.length - 1] < e)
432
+ return false;
433
+ if (ranges.length < 17) {
434
+ for (let i = 0; i < ranges.length - 1; i += 2) {
435
+ if (ranges[i] <= e && e <= ranges[i + 1])
436
+ return true;
437
+ }
438
+ return false;
439
+ }
440
+ let lo = 0, hi = ranges.length;
441
+ while (lo < hi) {
442
+ const mid = lo + hi >>> 1;
443
+ if (ranges[mid] === e)
444
+ return true;
445
+ if (ranges[mid] < e)
446
+ lo = mid + 1;
447
+ else
448
+ hi = mid;
449
+ }
450
+ return (lo & 1) !== 0;
451
+ }
452
+ Ranges2.includes = includes;
453
+ function cardinality(ranges) {
454
+ if (isInfinite(ranges))
455
+ return Infinity;
456
+ let sum = 0;
457
+ for (let i = 0; i < ranges.length - 1; i += 2)
458
+ sum += ranges[i + 1] - ranges[i] + 1;
459
+ return sum;
460
+ }
461
+ Ranges2.cardinality = cardinality;
462
+ function offset(ranges, offset2) {
463
+ return ranges.map((x) => x + offset2);
464
+ }
465
+ Ranges2.offset = offset;
466
+ function intersect(ranges1, ranges2) {
467
+ const ranges = [];
468
+ if (!ranges1.length || !ranges2.length)
469
+ return ranges;
470
+ if (ranges1[ranges1.length - 1] < ranges2[0] || ranges2[ranges2.length - 1] < ranges1[0])
471
+ return ranges;
472
+ let p1 = 0;
473
+ let p2 = 0;
474
+ while (p1 < ranges1.length - 1 && p2 < ranges2.length - 1) {
475
+ if (ranges1[p1 + 1] < ranges2[p2]) {
476
+ p1 += 2;
477
+ let offset2 = 1;
478
+ while (p1 + offset2 * 2 + 1 < ranges1.length && ranges1[p1 + offset2 * 2 + 1] < ranges2[p2])
479
+ offset2 <<= 1;
480
+ p1 += offset2 >> 1 << 1;
481
+ } else if (ranges2[p2 + 1] < ranges1[p1]) {
482
+ p2 += 2;
483
+ let offset2 = 1;
484
+ while (p2 + offset2 * 2 + 1 < ranges2.length && ranges2[p2 + offset2 * 2 + 1] < ranges1[p1])
485
+ offset2 <<= 1;
486
+ p2 += offset2 >> 1 << 1;
487
+ } else {
488
+ const a1 = ranges1[p1], a2 = ranges1[p1 + 1];
489
+ const b1 = ranges2[p2], b2 = ranges2[p2 + 1];
490
+ ranges.push(Math.max(a1, b1), Math.min(a2, b2));
491
+ if (a2 < b2) {
492
+ p1 += 2;
493
+ } else if (a2 > b2) {
494
+ p2 += 2;
495
+ } else {
496
+ p1 += 2;
497
+ p2 += 2;
498
+ }
499
+ }
500
+ }
501
+ return ranges;
502
+ }
503
+ Ranges2.intersect = intersect;
504
+ function capAt(ranges, cap) {
505
+ const result = [];
506
+ for (let i = 0; i < ranges.length; i += 2) {
507
+ const start = ranges[i];
508
+ const end = ranges[i + 1];
509
+ if (start > cap)
510
+ break;
511
+ if (end <= cap) {
512
+ result.push(start, end);
513
+ } else {
514
+ result.push(start, cap);
515
+ break;
516
+ }
517
+ }
518
+ return result;
519
+ }
520
+ Ranges2.capAt = capAt;
521
+ function isIntersecting(ranges1, ranges2) {
522
+ if (!ranges1.length || !ranges2.length)
523
+ return false;
524
+ if (ranges1[ranges1.length - 1] < ranges2[0] || ranges2[ranges2.length - 1] < ranges1[0])
525
+ return false;
526
+ let p1 = 0;
527
+ let p2 = 0;
528
+ while (p1 < ranges1.length - 1 && p2 < ranges2.length - 1) {
529
+ const a1 = ranges1[p1], a2 = ranges1[p1 + 1];
530
+ const b1 = ranges2[p2], b2 = ranges2[p2 + 1];
531
+ if (a2 < b1) {
532
+ p1 += 2;
533
+ let offset2 = 1;
534
+ while (p1 + offset2 * 2 + 1 < ranges1.length && ranges1[p1 + offset2 * 2 + 1] < ranges2[p2])
535
+ offset2 <<= 1;
536
+ p1 += offset2 >> 1 << 1;
537
+ } else if (b2 < a1) {
538
+ p2 += 2;
539
+ let offset2 = 1;
540
+ while (p2 + offset2 * 2 + 1 < ranges2.length && ranges2[p2 + offset2 * 2 + 1] < ranges1[p1])
541
+ offset2 <<= 1;
542
+ p2 += offset2 >> 1 << 1;
543
+ } else {
544
+ return true;
545
+ }
546
+ }
547
+ return false;
548
+ }
549
+ Ranges2.isIntersecting = isIntersecting;
550
+ function complement(r) {
551
+ if (r.length === 0)
552
+ return [-Infinity, Infinity];
553
+ const result = [];
554
+ if (!Object.is(r[0], -Infinity))
555
+ result.push(-Infinity, r[0] - 1);
556
+ for (let i = 1; i < r.length - 2; i += 2)
557
+ result.push(r[i] + 1, r[i + 1] - 1);
558
+ if (!Object.is(r[r.length - 1], Infinity))
559
+ result.push(r[r.length - 1] + 1, Infinity);
560
+ return result;
561
+ }
562
+ Ranges2.complement = complement;
563
+ function subtract(ranges1, ranges2) {
564
+ return intersect(ranges1, complement(ranges2));
565
+ }
566
+ Ranges2.subtract = subtract;
567
+ function singleRange(from2, to) {
568
+ return [from2, to];
569
+ }
570
+ Ranges2.singleRange = singleRange;
571
+ function unionAll(ranges) {
572
+ let result = Ranges2.EMPTY;
573
+ for (const r of ranges)
574
+ result = union(result, r);
575
+ return result;
576
+ }
577
+ Ranges2.unionAll = unionAll;
578
+ function unionAll_2(rangesIterable) {
579
+ const ranges = Array.isArray(rangesIterable) ? rangesIterable : Array.from(rangesIterable);
580
+ if (ranges.length === 0)
581
+ return [];
582
+ if (ranges.length === 1)
583
+ return ranges[0];
584
+ if (ranges.length === 2)
585
+ return union(ranges[0], ranges[1]);
586
+ const seq = Sequence.merge(ranges.map((r) => intervalSequence(r)), (a, b) => a[0] - b[0]);
587
+ const result = [];
588
+ let last;
589
+ for (const interval of seq.seek(0)) {
590
+ if (!last || last[1] + 1 < interval[0]) {
591
+ result.push(interval);
592
+ last = interval;
593
+ continue;
594
+ }
595
+ if (last[1] < interval[1])
596
+ last[1] = interval[1];
597
+ }
598
+ return result.flat();
599
+ }
600
+ Ranges2.unionAll_2 = unionAll_2;
601
+ function intersectAll(ranges) {
602
+ if (!ranges.length)
603
+ return Ranges2.EMPTY;
604
+ let result = Ranges2.FULL;
605
+ for (const range of ranges)
606
+ result = Ranges2.intersect(result, range);
607
+ return result;
608
+ }
609
+ Ranges2.intersectAll = intersectAll;
610
+ function domain(ranges) {
611
+ if (!ranges.length)
612
+ return void 0;
613
+ return { min: ranges[0], max: ranges[ranges.length - 1] };
614
+ }
615
+ Ranges2.domain = domain;
616
+ function union(ranges1, ranges2) {
617
+ if (!ranges1.length)
618
+ return ranges2;
619
+ if (!ranges2.length)
620
+ return ranges1;
621
+ if (ranges2[0] < ranges1[0])
622
+ [ranges1, ranges2] = [ranges2, ranges1];
623
+ const r = [ranges1[0], ranges1[1]];
624
+ let p1 = 2, p2 = 0;
625
+ while (p1 < ranges1.length - 1 && p2 < ranges2.length - 1) {
626
+ if (ranges1[p1] <= ranges2[p2]) {
627
+ if (r[r.length - 1] + 1 < ranges1[p1]) {
628
+ r.push(ranges1[p1], ranges1[p1 + 1]);
629
+ p1 += 2;
630
+ } else if (r[r.length - 1] < ranges1[p1 + 1]) {
631
+ r[r.length - 1] = ranges1[p1 + 1];
632
+ p1 += 2;
633
+ } else {
634
+ p1 += 2;
635
+ let offset2 = 1;
636
+ while (p1 + offset2 * 2 + 1 < ranges1.length && r[r.length - 1] >= ranges1[p1 + offset2 * 2 + 1])
637
+ offset2 <<= 1;
638
+ p1 += offset2 >> 1 << 1;
639
+ }
640
+ } else {
641
+ if (r[r.length - 1] + 1 < ranges2[p2]) {
642
+ r.push(ranges2[p2], ranges2[p2 + 1]);
643
+ p2 += 2;
644
+ } else if (r[r.length - 1] < ranges2[p2 + 1]) {
645
+ r[r.length - 1] = ranges2[p2 + 1];
646
+ p2 += 2;
647
+ } else {
648
+ p2 += 2;
649
+ let offset2 = 1;
650
+ while (p2 + offset2 * 2 + 1 < ranges2.length && r[r.length - 1] >= ranges2[p2 + offset2 * 2 + 1])
651
+ offset2 <<= 1;
652
+ p2 += offset2 >> 1 << 1;
653
+ }
654
+ }
655
+ }
656
+ while (p1 < ranges1.length - 1) {
657
+ if (r[r.length - 1] + 1 < ranges1[p1]) {
658
+ r.push(ranges1[p1], ranges1[p1 + 1]);
659
+ p1 += 2;
660
+ } else if (r[r.length - 1] < ranges1[p1 + 1]) {
661
+ r[r.length - 1] = ranges1[p1 + 1];
662
+ p1 += 2;
663
+ } else {
664
+ p1 += 2;
665
+ let offset2 = 1;
666
+ while (p1 + offset2 * 2 + 1 < ranges1.length && r[r.length - 1] >= ranges1[p1 + offset2 * 2 + 1])
667
+ offset2 <<= 1;
668
+ p1 += offset2 >> 1 << 1;
669
+ }
670
+ }
671
+ while (p2 < ranges2.length - 1) {
672
+ if (r[r.length - 1] + 1 < ranges2[p2]) {
673
+ r.push(ranges2[p2], ranges2[p2 + 1]);
674
+ p2 += 2;
675
+ } else if (r[r.length - 1] < ranges2[p2 + 1]) {
676
+ r[r.length - 1] = ranges2[p2 + 1];
677
+ p2 += 2;
678
+ } else {
679
+ p2 += 2;
680
+ let offset2 = 1;
681
+ while (p2 + offset2 * 2 + 1 < ranges2.length && r[r.length - 1] >= ranges2[p2 + offset2 * 2 + 1])
682
+ offset2 <<= 1;
683
+ p2 += offset2 >> 1 << 1;
684
+ }
685
+ }
686
+ return r;
687
+ }
688
+ Ranges2.union = union;
689
+ function intervalSequence(ranges) {
690
+ return new Sequence(function(idx) {
691
+ return {
692
+ next() {
693
+ if (idx * 2 >= ranges.length)
694
+ return { done: true, value: void 0 };
695
+ const value = [ranges[idx * 2], ranges[idx * 2 + 1]];
696
+ ++idx;
697
+ return { done: false, value };
698
+ }
699
+ };
700
+ }, ranges.length >>> 1);
701
+ }
702
+ Ranges2.intervalSequence = intervalSequence;
703
+ function sequence(ranges) {
704
+ let length = 0;
705
+ const leftsums = [];
706
+ for (let i = 0; i < ranges.length - 1; i += 2) {
707
+ length += ranges[i + 1] - ranges[i] + 1;
708
+ leftsums.push(length);
709
+ }
710
+ return new Sequence(
711
+ function(fromIdx) {
712
+ fromIdx = Math.max(0, Math.min(length, fromIdx));
713
+ const idx = Sequence.fromList(leftsums).partitionPoint((x) => x <= fromIdx);
714
+ const intervals = Ranges2.intervalSequence(ranges);
715
+ const it = intervals.seek(idx);
716
+ const firstInterval = it.next();
717
+ if (firstInterval.done)
718
+ return { next: () => firstInterval };
719
+ let from2 = firstInterval.value[0] + fromIdx - (idx > 0 ? leftsums[idx - 1] : 0);
720
+ let to = firstInterval.value[1];
721
+ return {
722
+ next() {
723
+ if (from2 > to) {
724
+ const interval = it.next();
725
+ if (interval.done)
726
+ return { done: true, value: void 0 };
727
+ from2 = interval.value[0];
728
+ to = interval.value[1];
729
+ }
730
+ return { done: false, value: from2++ };
731
+ }
732
+ };
733
+ },
734
+ length
735
+ );
736
+ }
737
+ Ranges2.sequence = sequence;
738
+ })(Ranges || (Ranges = {}));
739
+
740
+ // src/cli/cli.ts
741
+ import { TypedHTTP as TypedHTTP2 } from "@flakiness/shared/common/typedHttp.js";
742
+ import assert3 from "assert";
743
+ import { Command, Option } from "commander";
744
+ import fs8 from "fs";
745
+ import ora from "ora";
746
+ import path8 from "path";
747
+
748
+ // ../package.json
749
+ var package_default = {
750
+ name: "flakiness",
751
+ version: "0.146.1",
752
+ private: true,
753
+ scripts: {
754
+ minor: "./version.mjs minor",
755
+ patch: "./version.mjs patch",
756
+ dev: "npx kubik --env-file=.env.dev -w $(find . -name build.mts) ./app.mts ./stripe.mts",
757
+ "dev+billing": "npx kubik --env-file=.env.dev+billing -w $(find . -name build.mts) ./app.mts ./stripe.mts",
758
+ prod: "npx kubik --env-file=.env.prodlocal -w ./cli/build.mts ./server.mts ./web/build.mts ./experimental/build.mts ./landing/build.mts",
759
+ build: "npx kubik $(find . -name build.mts)",
760
+ perf: "node --max-old-space-size=10240 --enable-source-maps --env-file=.env.prodlocal experimental/lib/perf_filter.js"
761
+ },
762
+ engines: {
763
+ node: ">=24"
764
+ },
765
+ author: "Degu Labs, Inc",
766
+ license: "Fair Source 100",
767
+ workspaces: [
768
+ "./report",
769
+ "./sdk",
770
+ "./cli",
771
+ "./docs",
772
+ "./landing",
773
+ "./legal",
774
+ "./devenv",
775
+ "./database",
776
+ "./server",
777
+ "./shared",
778
+ "./experimental",
779
+ "./web"
780
+ ],
781
+ devDependencies: {
782
+ "@playwright/test": "^1.57.0",
783
+ "@types/node": "^22.10.2",
784
+ esbuild: "^0.27.0",
785
+ glob: "^10.3.10",
786
+ kubik: "^0.24.0",
787
+ tsx: "^4.19.2",
788
+ typescript: "^5.6.2"
789
+ }
790
+ };
791
+
792
+ // src/flakinessSession.ts
793
+ import fs2 from "fs/promises";
794
+ import os2 from "os";
795
+ import path2 from "path";
796
+
797
+ // src/serverapi.ts
798
+ import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
799
+
800
+ // src/utils.ts
801
+ import { ReportUtils } from "@flakiness/report";
802
+ import assert from "assert";
803
+ import { spawnSync } from "child_process";
804
+ import crypto from "crypto";
805
+ import fs from "fs";
806
+ import http from "http";
807
+ import https from "https";
808
+ import os from "os";
809
+ import path, { posix as posixPath, win32 as win32Path } from "path";
810
+ async function existsAsync(aPath) {
811
+ return fs.promises.stat(aPath).then(() => true).catch((e) => false);
812
+ }
813
+ function extractEnvConfiguration() {
814
+ const ENV_PREFIX = "FK_ENV_";
815
+ return Object.fromEntries(
816
+ Object.entries(process.env).filter(([key]) => key.toUpperCase().startsWith(ENV_PREFIX.toUpperCase())).map(([key, value]) => [key.substring(ENV_PREFIX.length).toLowerCase(), (value ?? "").trim().toLowerCase()])
817
+ );
818
+ }
819
+ function sha1File(filePath) {
820
+ return new Promise((resolve, reject) => {
821
+ const hash = crypto.createHash("sha1");
822
+ const stream = fs.createReadStream(filePath);
823
+ stream.on("data", (chunk) => {
824
+ hash.update(chunk);
825
+ });
826
+ stream.on("end", () => {
827
+ resolve(hash.digest("hex"));
828
+ });
829
+ stream.on("error", (err2) => {
830
+ reject(err2);
831
+ });
832
+ });
833
+ }
834
+ var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
835
+ function errorText(error) {
836
+ return FLAKINESS_DBG ? error.stack : error.message;
837
+ }
838
+ function sha1Buffer(data) {
839
+ const hash = crypto.createHash("sha1");
840
+ hash.update(data);
841
+ return hash.digest("hex");
842
+ }
843
+ async function retryWithBackoff(job, backoff = []) {
844
+ for (const timeout of backoff) {
845
+ try {
846
+ return await job();
847
+ } catch (e) {
848
+ if (e instanceof AggregateError)
849
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
850
+ else if (e instanceof Error)
851
+ console.error(`[flakiness.io err]`, errorText(e));
852
+ else
853
+ console.error(`[flakiness.io err]`, e);
854
+ await new Promise((x) => setTimeout(x, timeout));
855
+ }
856
+ }
857
+ return await job();
858
+ }
859
+ var httpUtils;
860
+ ((httpUtils2) => {
861
+ function createRequest({ url, method = "get", headers = {} }) {
862
+ let resolve;
863
+ let reject;
864
+ const responseDataPromise = new Promise((a, b) => {
865
+ resolve = a;
866
+ reject = b;
867
+ });
868
+ const protocol = url.startsWith("https") ? https : http;
869
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
870
+ const request = protocol.request(url, { method, headers }, (res) => {
871
+ const chunks = [];
872
+ res.on("data", (chunk) => chunks.push(chunk));
873
+ res.on("end", () => {
874
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300)
875
+ resolve(Buffer.concat(chunks));
876
+ else
877
+ reject(new Error(`Request to ${url} failed with ${res.statusCode}`));
878
+ });
879
+ res.on("error", (error) => reject(error));
880
+ });
881
+ request.on("error", reject);
882
+ return { request, responseDataPromise };
883
+ }
884
+ httpUtils2.createRequest = createRequest;
885
+ async function getBuffer(url, backoff) {
886
+ return await retryWithBackoff(async () => {
887
+ const { request, responseDataPromise } = createRequest({ url });
888
+ request.end();
889
+ return await responseDataPromise;
890
+ }, backoff);
891
+ }
892
+ httpUtils2.getBuffer = getBuffer;
893
+ async function getText(url, backoff) {
894
+ const buffer = await getBuffer(url, backoff);
895
+ return buffer.toString("utf-8");
896
+ }
897
+ httpUtils2.getText = getText;
898
+ async function getJSON(url) {
899
+ return JSON.parse(await getText(url));
900
+ }
901
+ httpUtils2.getJSON = getJSON;
902
+ async function postText(url, text, backoff) {
903
+ const headers = {
904
+ "Content-Type": "application/json",
905
+ "Content-Length": Buffer.byteLength(text) + ""
906
+ };
907
+ return await retryWithBackoff(async () => {
908
+ const { request, responseDataPromise } = createRequest({ url, headers, method: "post" });
909
+ request.write(text);
910
+ request.end();
911
+ return await responseDataPromise;
912
+ }, backoff);
913
+ }
914
+ httpUtils2.postText = postText;
915
+ async function postJSON(url, json, backoff) {
916
+ const buffer = await postText(url, JSON.stringify(json), backoff);
917
+ return JSON.parse(buffer.toString("utf-8"));
918
+ }
919
+ httpUtils2.postJSON = postJSON;
920
+ })(httpUtils || (httpUtils = {}));
921
+ 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");
922
+ function stripAnsi(str) {
923
+ return str.replace(ansiRegex, "");
924
+ }
925
+ async function saveReportAndAttachments(report, attachments, outputFolder) {
926
+ const reportPath = path.join(outputFolder, "report.json");
927
+ const attachmentsFolder = path.join(outputFolder, "attachments");
928
+ await fs.promises.rm(outputFolder, { recursive: true, force: true });
929
+ await fs.promises.mkdir(outputFolder, { recursive: true });
930
+ await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
931
+ if (attachments.length)
932
+ await fs.promises.mkdir(attachmentsFolder);
933
+ const movedAttachments = [];
934
+ for (const attachment of attachments) {
935
+ const attachmentPath = path.join(attachmentsFolder, attachment.id);
936
+ if (attachment.path)
937
+ await fs.promises.cp(attachment.path, attachmentPath);
938
+ else if (attachment.body)
939
+ await fs.promises.writeFile(attachmentPath, attachment.body);
940
+ movedAttachments.push({
941
+ contentType: attachment.contentType,
942
+ id: attachment.id,
943
+ path: attachmentPath
944
+ });
945
+ }
946
+ return movedAttachments;
947
+ }
948
+ function shell(command, args, options) {
949
+ try {
950
+ const result = spawnSync(command, args, { encoding: "utf-8", ...options });
951
+ if (result.status !== 0) {
952
+ return void 0;
953
+ }
954
+ return result.stdout.trim();
955
+ } catch (e) {
956
+ console.error(e);
957
+ return void 0;
958
+ }
959
+ }
960
+ function readLinuxOSRelease() {
961
+ const osReleaseText = fs.readFileSync("/etc/os-release", "utf-8");
962
+ return new Map(osReleaseText.toLowerCase().split("\n").filter((line) => line.includes("=")).map((line) => {
963
+ line = line.trim();
964
+ let [key, value] = line.split("=");
965
+ if (value.startsWith('"') && value.endsWith('"'))
966
+ value = value.substring(1, value.length - 1);
967
+ return [key, value];
968
+ }));
969
+ }
970
+ function osLinuxInfo() {
971
+ const arch = shell(`uname`, [`-m`]);
972
+ const osReleaseMap = readLinuxOSRelease();
973
+ const name = osReleaseMap.get("name") ?? shell(`uname`);
974
+ const version = osReleaseMap.get("version_id");
975
+ return { name, arch, version };
976
+ }
977
+ function osDarwinInfo() {
978
+ const name = "macos";
979
+ const arch = shell(`uname`, [`-m`]);
980
+ const version = shell(`sw_vers`, [`-productVersion`]);
981
+ return { name, arch, version };
982
+ }
983
+ function osWinInfo() {
984
+ const name = "win";
985
+ const arch = process.arch;
986
+ const version = os.release();
987
+ return { name, arch, version };
988
+ }
989
+ function getOSInfo() {
990
+ if (process.platform === "darwin")
991
+ return osDarwinInfo();
992
+ if (process.platform === "win32")
993
+ return osWinInfo();
994
+ return osLinuxInfo();
995
+ }
996
+ function inferRunUrl() {
997
+ if (process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID)
998
+ return `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
999
+ return void 0;
1000
+ }
1001
+ function parseStringDate(dateString) {
1002
+ return +new Date(dateString);
1003
+ }
1004
+ function gitCommitInfo(gitRepo) {
1005
+ const sha = shell(`git`, ["rev-parse", "HEAD"], {
1006
+ cwd: gitRepo,
1007
+ encoding: "utf-8"
1008
+ });
1009
+ assert(sha, `FAILED: git rev-parse HEAD @ ${gitRepo}`);
1010
+ return sha.trim();
1011
+ }
1012
+ async function resolveAttachmentPaths(report, attachmentsDir) {
1013
+ const attachmentFiles = await listFilesRecursively(attachmentsDir);
1014
+ const filenameToPath = new Map(attachmentFiles.map((file) => [path.basename(file), file]));
1015
+ const attachmentIdToPath = /* @__PURE__ */ new Map();
1016
+ const missingAttachments = /* @__PURE__ */ new Set();
1017
+ ReportUtils.visitTests(report, (test) => {
1018
+ for (const attempt of test.attempts) {
1019
+ for (const attachment of attempt.attachments ?? []) {
1020
+ const attachmentPath = filenameToPath.get(attachment.id);
1021
+ if (!attachmentPath) {
1022
+ missingAttachments.add(attachment.id);
1023
+ } else {
1024
+ attachmentIdToPath.set(attachment.id, {
1025
+ contentType: attachment.contentType,
1026
+ id: attachment.id,
1027
+ path: attachmentPath
1028
+ });
1029
+ }
1030
+ }
1031
+ }
1032
+ });
1033
+ return { attachmentIdToPath, missingAttachments: Array.from(missingAttachments) };
1034
+ }
1035
+ async function listFilesRecursively(dir, result = []) {
1036
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
1037
+ for (const entry of entries) {
1038
+ const fullPath = path.join(dir, entry.name);
1039
+ if (entry.isDirectory())
1040
+ await listFilesRecursively(fullPath, result);
1041
+ else
1042
+ result.push(fullPath);
1043
+ }
1044
+ return result;
1045
+ }
1046
+ function computeGitRoot(somePathInsideGitRepo) {
1047
+ const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
1048
+ cwd: somePathInsideGitRepo,
1049
+ encoding: "utf-8"
1050
+ });
1051
+ assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
1052
+ return normalizePath(root);
1053
+ }
1054
+ var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
1055
+ var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
1056
+ function normalizePath(aPath) {
1057
+ if (IS_WIN32_PATH.test(aPath)) {
1058
+ aPath = aPath.split(win32Path.sep).join(posixPath.sep);
1059
+ }
1060
+ if (IS_ALMOST_POSIX_PATH.test(aPath))
1061
+ return "/" + aPath[0] + aPath.substring(2);
1062
+ return aPath;
1063
+ }
1064
+ function gitFilePath(gitRoot, absolutePath) {
1065
+ return posixPath.relative(gitRoot, absolutePath);
1066
+ }
1067
+ function parseDurationMS(value) {
1068
+ if (isNaN(value))
1069
+ throw new Error("Duration cannot be NaN");
1070
+ if (value < 0)
1071
+ throw new Error(`Duration cannot be less than 0, found ${value}`);
1072
+ return value | 0;
1073
+ }
1074
+ function createEnvironments(projects) {
1075
+ const envConfiguration = extractEnvConfiguration();
1076
+ const osInfo = getOSInfo();
1077
+ let uniqueNames = /* @__PURE__ */ new Set();
1078
+ const result = /* @__PURE__ */ new Map();
1079
+ for (const project of projects) {
1080
+ let defaultName = project.name;
1081
+ if (!defaultName.trim())
1082
+ defaultName = "anonymous";
1083
+ let name = defaultName;
1084
+ for (let i = 2; uniqueNames.has(name); ++i)
1085
+ name = `${defaultName}-${i}`;
1086
+ uniqueNames.add(defaultName);
1087
+ result.set(project, {
1088
+ name,
1089
+ systemData: {
1090
+ osArch: osInfo.arch,
1091
+ osName: osInfo.name,
1092
+ osVersion: osInfo.version
1093
+ },
1094
+ userSuppliedData: {
1095
+ ...envConfiguration,
1096
+ ...project.metadata
1097
+ },
1098
+ opaqueData: {
1099
+ project
1100
+ }
1101
+ });
1102
+ }
1103
+ return result;
1104
+ }
1105
+
1106
+ // src/serverapi.ts
1107
+ function createServerAPI(endpoint, options) {
1108
+ endpoint += "/api/";
1109
+ const fetcher = options?.auth ? (url, init) => fetch(url, {
1110
+ ...init,
1111
+ headers: {
1112
+ ...init.headers,
1113
+ "Authorization": `Bearer ${options.auth}`
1114
+ }
1115
+ }) : fetch;
1116
+ if (options?.retries)
1117
+ return TypedHTTP.createClient(endpoint, (url, init) => retryWithBackoff(() => fetcher(url, init), options.retries));
1118
+ return TypedHTTP.createClient(endpoint, fetcher);
1119
+ }
1120
+
1121
+ // src/flakinessSession.ts
1122
+ var CONFIG_DIR = (() => {
1123
+ const configDir = process.platform === "darwin" ? path2.join(os2.homedir(), "Library", "Application Support", "flakiness") : process.platform === "win32" ? path2.join(os2.homedir(), "AppData", "Roaming", "flakiness") : path2.join(os2.homedir(), ".config", "flakiness");
1124
+ return configDir;
1125
+ })();
1126
+ var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
1127
+ var FlakinessSession = class _FlakinessSession {
1128
+ constructor(_config) {
1129
+ this._config = _config;
1130
+ this.api = createServerAPI(this._config.endpoint, { auth: this._config.token });
1131
+ }
1132
+ static async loadOrDie() {
1133
+ const session2 = await _FlakinessSession.load();
1134
+ if (!session2)
1135
+ throw new Error(`Please login first with 'npx flakiness login'`);
1136
+ return session2;
1137
+ }
1138
+ static async load() {
1139
+ const data = await fs2.readFile(CONFIG_PATH, "utf-8").catch((e) => void 0);
1140
+ if (!data)
1141
+ return void 0;
1142
+ const json = JSON.parse(data);
1143
+ return new _FlakinessSession(json);
1144
+ }
1145
+ static async remove() {
1146
+ await fs2.unlink(CONFIG_PATH).catch((e) => void 0);
1147
+ }
1148
+ api;
1149
+ endpoint() {
1150
+ return this._config.endpoint;
1151
+ }
1152
+ path() {
1153
+ return CONFIG_PATH;
1154
+ }
1155
+ sessionToken() {
1156
+ return this._config.token;
1157
+ }
1158
+ async save() {
1159
+ await fs2.mkdir(CONFIG_DIR, { recursive: true });
1160
+ await fs2.writeFile(CONFIG_PATH, JSON.stringify(this._config, null, 2));
1161
+ }
1162
+ };
1163
+
1164
+ // src/cli/cmd-convert.ts
1165
+ import fs4 from "fs/promises";
1166
+ import path4 from "path";
1167
+
1168
+ // src/junit.ts
1169
+ import { ReportUtils as ReportUtils2 } from "@flakiness/report";
1170
+ import { parseXml, XmlElement, XmlText } from "@rgrove/parse-xml";
1171
+ import assert2 from "assert";
1172
+ import fs3 from "fs";
1173
+ import path3 from "path";
1174
+ function getProperties(element) {
1175
+ const propertiesNodes = element.children.filter((node) => node instanceof XmlElement).filter((node) => node.name === "properties");
1176
+ if (!propertiesNodes.length)
1177
+ return [];
1178
+ const result = [];
1179
+ for (const propertiesNode of propertiesNodes) {
1180
+ const properties = propertiesNode.children.filter((node) => node instanceof XmlElement).filter((node) => node.name === "property");
1181
+ for (const property of properties) {
1182
+ const name = property.attributes["name"];
1183
+ const innerText = property.children.find((node) => node instanceof XmlText);
1184
+ const value = property.attributes["value"] ?? innerText?.text ?? "";
1185
+ result.push([name, value]);
1186
+ }
1187
+ }
1188
+ return result;
1189
+ }
1190
+ function extractErrors(testcase) {
1191
+ const xmlErrors = testcase.children.filter((e) => e instanceof XmlElement).filter((element) => element.name === "error" || element.name === "failure");
1192
+ if (!xmlErrors.length)
1193
+ return void 0;
1194
+ const errors = [];
1195
+ for (const xmlErr of xmlErrors) {
1196
+ const message = [xmlErr.attributes["type"], xmlErr.attributes["message"]].filter((x) => !!x).join(" ");
1197
+ const xmlStackNodes = xmlErr.children.filter((child) => child instanceof XmlText);
1198
+ const stack = xmlStackNodes ? xmlStackNodes.map((node) => node.text).join("\n") : void 0;
1199
+ errors.push({
1200
+ message,
1201
+ stack
1202
+ });
1203
+ }
1204
+ return errors;
1205
+ }
1206
+ function extractStdout(testcase, stdio) {
1207
+ const xmlStdio = testcase.children.filter((e) => e instanceof XmlElement).filter((element) => element.name === stdio);
1208
+ if (!xmlStdio.length)
1209
+ return void 0;
1210
+ return xmlStdio.map((node) => node.children.filter((node2) => node2 instanceof XmlText)).flat().map((txtNode) => ({
1211
+ text: txtNode.text
1212
+ }));
1213
+ }
1214
+ async function parseAttachment(value) {
1215
+ let absolutePath = path3.resolve(process.cwd(), value);
1216
+ if (fs3.existsSync(absolutePath)) {
1217
+ const id = await sha1File(absolutePath);
1218
+ return {
1219
+ contentType: "image/png",
1220
+ path: absolutePath,
1221
+ id
1222
+ };
1223
+ }
1224
+ return {
1225
+ contentType: "text/plain",
1226
+ id: sha1Buffer(value),
1227
+ body: Buffer.from(value)
1228
+ };
1229
+ }
1230
+ async function traverseJUnitReport(context, node) {
1231
+ const element = node;
1232
+ if (!(element instanceof XmlElement))
1233
+ return;
1234
+ let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs, attachments } = context;
1235
+ if (element.attributes["timestamp"])
1236
+ currentTimeMs = new Date(element.attributes["timestamp"]).getTime();
1237
+ if (element.name === "testsuite") {
1238
+ const file = element.attributes["file"];
1239
+ const line = parseInt(element.attributes["line"], 10);
1240
+ const name = element.attributes["name"];
1241
+ const newSuite = {
1242
+ title: name ?? file,
1243
+ location: file && !isNaN(line) ? {
1244
+ file,
1245
+ line,
1246
+ column: 1
1247
+ } : void 0,
1248
+ type: name ? "suite" : file ? "file" : "anonymous suite",
1249
+ suites: [],
1250
+ tests: []
1251
+ };
1252
+ if (currentSuite) {
1253
+ currentSuite.suites ??= [];
1254
+ currentSuite.suites.push(newSuite);
1255
+ } else {
1256
+ report.suites.push(newSuite);
1257
+ }
1258
+ currentSuite = newSuite;
1259
+ const userSuppliedData = getProperties(element);
1260
+ if (userSuppliedData.length) {
1261
+ currentEnv = structuredClone(currentEnv);
1262
+ currentEnv.userSuppliedData ??= {};
1263
+ for (const [key, value] of userSuppliedData)
1264
+ currentEnv.userSuppliedData[key] = value;
1265
+ currentEnvIndex = report.environments.push(currentEnv) - 1;
1266
+ }
1267
+ } else if (element.name === "testcase") {
1268
+ assert2(currentSuite);
1269
+ const file = element.attributes["file"];
1270
+ const name = element.attributes["name"];
1271
+ const line = parseInt(element.attributes["line"], 10);
1272
+ const timeMs = parseFloat(element.attributes["time"]) * 1e3;
1273
+ const startTimestamp = currentTimeMs;
1274
+ const duration = timeMs;
1275
+ currentTimeMs += timeMs;
1276
+ const annotations = [];
1277
+ const attachments2 = [];
1278
+ for (const [key, value] of getProperties(element)) {
1279
+ if (key.toLowerCase().startsWith("attachment")) {
1280
+ if (context.ignoreAttachments)
1281
+ continue;
1282
+ const attachment = await parseAttachment(value);
1283
+ context.attachments.set(attachment.id, attachment);
1284
+ attachments2.push({
1285
+ id: attachment.id,
1286
+ contentType: attachment.contentType,
1287
+ //TODO: better default names for attachments?
1288
+ name: attachment.path ? path3.basename(attachment.path) : `attachment`
1289
+ });
1290
+ } else {
1291
+ annotations.push({
1292
+ type: key,
1293
+ description: value.length ? value : void 0
1294
+ });
1295
+ }
1296
+ }
1297
+ const childElements = element.children.filter((child) => child instanceof XmlElement);
1298
+ const xmlSkippedAnnotation = childElements.find((child) => child.name === "skipped");
1299
+ if (xmlSkippedAnnotation)
1300
+ annotations.push({ type: "skipped", description: xmlSkippedAnnotation.attributes["message"] });
1301
+ const expectedStatus = xmlSkippedAnnotation ? "skipped" : "passed";
1302
+ const errors = extractErrors(element);
1303
+ const test = {
1304
+ title: name,
1305
+ location: file && !isNaN(line) ? {
1306
+ file,
1307
+ line,
1308
+ column: 1
1309
+ } : void 0,
1310
+ attempts: [{
1311
+ environmentIdx: currentEnvIndex,
1312
+ expectedStatus,
1313
+ annotations,
1314
+ attachments: attachments2,
1315
+ startTimestamp,
1316
+ duration,
1317
+ status: xmlSkippedAnnotation ? "skipped" : errors ? "failed" : "passed",
1318
+ errors,
1319
+ stdout: extractStdout(element, "system-out"),
1320
+ stderr: extractStdout(element, "system-err")
1321
+ }]
1322
+ };
1323
+ currentSuite.tests ??= [];
1324
+ currentSuite.tests.push(test);
1325
+ }
1326
+ context = { ...context, currentEnv, currentEnvIndex, currentSuite, currentTimeMs };
1327
+ for (const child of element.children)
1328
+ await traverseJUnitReport(context, child);
1329
+ }
1330
+ async function parseJUnit(xmls, options) {
1331
+ const report = {
1332
+ category: "junit",
1333
+ commitId: options.commitId,
1334
+ duration: options.runDuration,
1335
+ startTimestamp: options.runStartTimestamp,
1336
+ url: options.runUrl,
1337
+ environments: [options.defaultEnv],
1338
+ suites: [],
1339
+ unattributedErrors: []
1340
+ };
1341
+ const context = {
1342
+ currentEnv: options.defaultEnv,
1343
+ currentEnvIndex: 0,
1344
+ currentTimeMs: 0,
1345
+ report,
1346
+ currentSuite: void 0,
1347
+ attachments: /* @__PURE__ */ new Map(),
1348
+ ignoreAttachments: !!options.ignoreAttachments
1349
+ };
1350
+ for (const xml of xmls) {
1351
+ const doc = parseXml(xml);
1352
+ for (const element of doc.children)
1353
+ await traverseJUnitReport(context, element);
1354
+ }
1355
+ return {
1356
+ report: ReportUtils2.dedupeSuitesTestsEnvironments(report),
1357
+ attachments: Array.from(context.attachments.values())
1358
+ };
1359
+ }
1360
+
1361
+ // src/cli/cmd-convert.ts
1362
+ async function cmdConvert(junitPath, options) {
1363
+ const fullPath = path4.resolve(junitPath);
1364
+ if (!await fs4.access(fullPath, fs4.constants.F_OK).then(() => true).catch(() => false)) {
1365
+ console.error(`Error: path ${fullPath} is not accessible`);
1366
+ process.exit(1);
1367
+ }
1368
+ const stat = await fs4.stat(fullPath);
1369
+ let xmlContents = [];
1370
+ if (stat.isFile()) {
1371
+ const xmlContent = await fs4.readFile(fullPath, "utf-8");
1372
+ xmlContents.push(xmlContent);
1373
+ } else if (stat.isDirectory()) {
1374
+ const xmlFiles = await findXmlFiles(fullPath);
1375
+ if (xmlFiles.length === 0) {
1376
+ console.error(`Error: No XML files found in directory ${fullPath}`);
1377
+ process.exit(1);
1378
+ }
1379
+ console.log(`Found ${xmlFiles.length} XML files`);
1380
+ for (const xmlFile of xmlFiles) {
1381
+ const xmlContent = await fs4.readFile(xmlFile, "utf-8");
1382
+ xmlContents.push(xmlContent);
1383
+ }
1384
+ } else {
1385
+ console.error(`Error: ${fullPath} is neither a file nor a directory`);
1386
+ process.exit(1);
1387
+ }
1388
+ let commitId;
1389
+ if (options.commitId) {
1390
+ commitId = options.commitId;
1391
+ } else {
1392
+ try {
1393
+ commitId = gitCommitInfo(process.cwd());
1394
+ } catch (e) {
1395
+ console.error("Failed to get git commit info. Please provide --commit-id option.");
1396
+ process.exit(1);
1397
+ }
1398
+ }
1399
+ const { report, attachments } = await parseJUnit(xmlContents, {
1400
+ commitId,
1401
+ defaultEnv: { name: options.envName },
1402
+ runStartTimestamp: Date.now(),
1403
+ runDuration: 0
1404
+ });
1405
+ await saveReportAndAttachments(report, attachments, options.outputDir);
1406
+ console.log(`\u2713 Saved to ${options.outputDir}`);
1407
+ }
1408
+ async function findXmlFiles(dir, result = []) {
1409
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1410
+ for (const entry of entries) {
1411
+ const fullPath = path4.join(dir, entry.name);
1412
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".xml"))
1413
+ result.push(fullPath);
1414
+ else if (entry.isDirectory())
1415
+ await findXmlFiles(fullPath, result);
1416
+ }
1417
+ return result;
1418
+ }
1419
+
1420
+ // src/cli/cmd-download.ts
1421
+ import fs5 from "fs";
1422
+ import path5 from "path";
1423
+ async function cmdDownload(session2, project, runId, rootDir) {
1424
+ if (fs5.existsSync(rootDir)) {
1425
+ console.log(`Directory ${rootDir} already exists!`);
1426
+ return;
1427
+ }
1428
+ const urls = await session2.api.run.downloadURLs.GET({
1429
+ orgSlug: project.org.orgSlug,
1430
+ projectSlug: project.projectSlug,
1431
+ runId
1432
+ });
1433
+ const attachmentsDir = path5.join(rootDir, "attachments");
1434
+ await fs5.promises.mkdir(rootDir, { recursive: true });
1435
+ if (urls.attachmentURLs.length)
1436
+ await fs5.promises.mkdir(attachmentsDir, { recursive: true });
1437
+ const response = await fetch(urls.reportURL);
1438
+ if (!response.ok)
1439
+ throw new Error(`HTTP error ${response.status} for report URL: ${urls.reportURL}`);
1440
+ const reportContent = await response.text();
1441
+ await fs5.promises.writeFile(path5.join(rootDir, "report.json"), reportContent);
1442
+ const attachmentDownloader = async () => {
1443
+ while (urls.attachmentURLs.length) {
1444
+ const url = urls.attachmentURLs.pop();
1445
+ const response2 = await fetch(url);
1446
+ if (!response2.ok)
1447
+ throw new Error(`HTTP error ${response2.status} for attachment URL: ${url}`);
1448
+ const fileBuffer = Buffer.from(await response2.arrayBuffer());
1449
+ const filename = path5.basename(new URL(url).pathname);
1450
+ await fs5.promises.writeFile(path5.join(attachmentsDir, filename), fileBuffer);
1451
+ }
1452
+ };
1453
+ const workerPromises = [];
1454
+ for (let i = 0; i < 4; ++i)
1455
+ workerPromises.push(attachmentDownloader());
1456
+ await Promise.all(workerPromises);
1457
+ }
1458
+
1459
+ // src/cli/cmd-link.ts
1460
+ import { FlakinessProjectConfig } from "@flakiness/sdk/flakinessProjectConfig";
1461
+ async function cmdLink(session2, slug) {
1462
+ const [orgSlug, projectSlug] = slug.split("/");
1463
+ const project = await session2.api.project.findProject.GET({
1464
+ orgSlug,
1465
+ projectSlug
1466
+ });
1467
+ if (!project) {
1468
+ console.log(`Failed to find project ${slug}`);
1469
+ process.exit(1);
1470
+ }
1471
+ const config = FlakinessProjectConfig.createEmpty();
1472
+ config.setProjectPublicId(project.projectPublicId);
1473
+ await config.save();
1474
+ console.log(`\u2713 Linked to ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1475
+ }
1476
+
1477
+ // ../server/lib/common/knownClientIds.js
1478
+ var KNOWN_CLIENT_IDS = {
1479
+ OFFICIAL_WEB: "flakiness-io-official-cli",
1480
+ OFFICIAL_CLI: "flakiness-io-official-website"
1481
+ };
1482
+
1483
+ // src/cli/cmd-login.ts
1484
+ import open from "open";
1485
+ import os3 from "os";
1486
+
1487
+ // src/cli/cmd-logout.ts
1488
+ async function cmdLogout() {
1489
+ const session2 = await FlakinessSession.load();
1490
+ if (!session2)
1491
+ return;
1492
+ const currentSession = await session2.api.user.currentSession.GET().catch((e) => void 0);
1493
+ if (currentSession)
1494
+ await session2.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId }).catch((e) => void 0);
1495
+ await FlakinessSession.remove();
1496
+ }
1497
+
1498
+ // src/cli/cmd-login.ts
1499
+ var DEFAULT_FLAKINESS_ENDPOINT = "https://flakiness.io";
1500
+ async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
1501
+ await cmdLogout();
1502
+ const api = createServerAPI(endpoint);
1503
+ const data = await api.deviceauth.createRequest.POST({
1504
+ clientId: KNOWN_CLIENT_IDS.OFFICIAL_CLI,
1505
+ name: os3.hostname()
1506
+ });
1507
+ await open(new URL(data.verificationUrl, endpoint).href);
1508
+ console.log(`Please navigate to ${new URL(data.verificationUrl, endpoint)}`);
1509
+ let token;
1510
+ while (Date.now() < data.deadline) {
1511
+ await new Promise((x) => setTimeout(x, 2e3));
1512
+ const result = await api.deviceauth.getToken.GET({ deviceCode: data.deviceCode }).catch((e) => void 0);
1513
+ if (!result) {
1514
+ console.error(`Authorization request was rejected.`);
1515
+ process.exit(1);
1516
+ }
1517
+ token = result.token;
1518
+ if (token)
1519
+ break;
1520
+ }
1521
+ if (!token) {
1522
+ console.log(`Failed to login.`);
1523
+ process.exit(1);
1524
+ }
1525
+ const session2 = new FlakinessSession({
1526
+ endpoint,
1527
+ token
1528
+ });
1529
+ try {
1530
+ const user = await session2.api.user.whoami.GET();
1531
+ await session2.save();
1532
+ console.log(`\u2713 Logged in as ${user.userName} (${user.userLogin})`);
1533
+ } catch (e) {
1534
+ const message = e instanceof Error ? e.message : String(e);
1535
+ console.error(`x Failed to login:`, message);
1536
+ }
1537
+ return session2;
1538
+ }
1539
+
1540
+ // src/cli/cmd-status.ts
1541
+ import { FlakinessProjectConfig as FlakinessProjectConfig2 } from "@flakiness/sdk/flakinessProjectConfig";
1542
+ async function cmdStatus() {
1543
+ const session2 = await FlakinessSession.load();
1544
+ if (!session2) {
1545
+ console.log(`user: not logged in`);
1546
+ return;
1547
+ }
1548
+ const user = await session2.api.user.whoami.GET();
1549
+ console.log(`user: ${user.userName} (${user.userLogin})`);
1550
+ const config = await FlakinessProjectConfig2.load();
1551
+ const projectPublicId = config.projectPublicId();
1552
+ if (!projectPublicId) {
1553
+ console.log(`project: <not linked>`);
1554
+ return;
1555
+ }
1556
+ const project = await session2.api.project.getProject.GET({ projectPublicId });
1557
+ console.log(`project: ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1558
+ }
1559
+
1560
+ // src/cli/cmd-unlink.ts
1561
+ import { FlakinessProjectConfig as FlakinessProjectConfig3 } from "@flakiness/sdk/flakinessProjectConfig";
1562
+ async function cmdUnlink() {
1563
+ const config = await FlakinessProjectConfig3.load();
1564
+ if (!config.projectPublicId())
1565
+ return;
1566
+ config.setProjectPublicId(void 0);
1567
+ await config.save();
1568
+ }
1569
+
1570
+ // src/cli/cmd-upload-playwright-json.ts
1571
+ import { ReportUploader } from "@flakiness/sdk/uploader";
1572
+ import fs6 from "fs/promises";
1573
+ import path6 from "path";
1574
+
1575
+ // src/playwrightJSONReport.ts
1576
+ import { FlakinessReport as FK2, ReportUtils as ReportUtils3 } from "@flakiness/report";
1577
+ import debug from "debug";
1578
+ import { posix as posixPath2 } from "path";
1579
+ var dlog = debug("flakiness:json-report");
1580
+ var PlaywrightJSONReport;
1581
+ ((PlaywrightJSONReport2) => {
1582
+ function collectMetadata(somePathInsideProject = process.cwd()) {
1583
+ const commitId = gitCommitInfo(somePathInsideProject);
1584
+ const osInfo = getOSInfo();
1585
+ const metadata = {
1586
+ gitRoot: computeGitRoot(somePathInsideProject),
1587
+ commitId,
1588
+ osName: osInfo.name,
1589
+ arch: osInfo.arch,
1590
+ osVersion: osInfo.version,
1591
+ runURL: inferRunUrl()
1592
+ };
1593
+ dlog(`metadata directory: ${somePathInsideProject}`);
1594
+ dlog(`metadata: ${JSON.stringify(metadata)}`);
1595
+ dlog(`commit info: ${JSON.stringify(commitId)}`);
1596
+ dlog(`os info: ${JSON.stringify(osInfo)}`);
1597
+ return metadata;
1598
+ }
1599
+ PlaywrightJSONReport2.collectMetadata = collectMetadata;
1600
+ async function parse(metadata, jsonReport, options) {
1601
+ const context = {
1602
+ projectId2environmentIdx: /* @__PURE__ */ new Map(),
1603
+ testBaseDir: normalizePath(jsonReport.config.rootDir),
1604
+ gitRoot: metadata.gitRoot,
1605
+ attachments: /* @__PURE__ */ new Map(),
1606
+ unaccessibleAttachmentPaths: [],
1607
+ extractAttachments: options.extractAttachments
1608
+ };
1609
+ const configPath = jsonReport.config.configFile ? gitFilePath(context.gitRoot, normalizePath(jsonReport.config.configFile)) : void 0;
1610
+ const report = {
1611
+ category: FK2.CATEGORY_PLAYWRIGHT,
1612
+ commitId: metadata.commitId,
1613
+ configPath,
1614
+ url: metadata.runURL,
1615
+ environments: [],
1616
+ suites: [],
1617
+ opaqueData: jsonReport.config,
1618
+ unattributedErrors: jsonReport.errors.map((error) => parseJSONError(context, error)),
1619
+ // The report.stats is a releatively new addition to Playwright's JSONReport,
1620
+ // so we have to polyfill with some reasonable values when it's missing.
1621
+ duration: jsonReport.stats?.duration && jsonReport.stats?.duration > 0 ? parseDurationMS(jsonReport.stats.duration) : 0,
1622
+ startTimestamp: jsonReport.stats && jsonReport.stats.startTime ? parseStringDate(jsonReport.stats.startTime) : Date.now()
1623
+ };
1624
+ report.environments = [...createEnvironments(jsonReport.config.projects).values()];
1625
+ for (let envIdx = 0; envIdx < report.environments.length; ++envIdx)
1626
+ context.projectId2environmentIdx.set(jsonReport.config.projects[envIdx].id, envIdx);
1627
+ report.suites = await Promise.all(jsonReport.suites.map((suite) => parseJSONSuite(context, suite)));
1628
+ return {
1629
+ report: ReportUtils3.dedupeSuitesTestsEnvironments(report),
1630
+ attachments: [...context.attachments.values()],
1631
+ unaccessibleAttachmentPaths: context.unaccessibleAttachmentPaths
1632
+ };
1633
+ }
1634
+ PlaywrightJSONReport2.parse = parse;
1635
+ })(PlaywrightJSONReport || (PlaywrightJSONReport = {}));
1636
+ async function parseJSONSuite(context, jsonSuite) {
1637
+ let type = "suite";
1638
+ if (jsonSuite.column === 0 && jsonSuite.line === 0)
1639
+ type = "file";
1640
+ else if (!jsonSuite.title)
1641
+ type = "anonymous suite";
1642
+ const suite = {
1643
+ type,
1644
+ title: jsonSuite.title,
1645
+ location: {
1646
+ file: gitFilePath(context.gitRoot, normalizePath(jsonSuite.file)),
1647
+ line: jsonSuite.line,
1648
+ column: jsonSuite.column
1649
+ }
1650
+ };
1651
+ if (jsonSuite.suites && jsonSuite.suites.length)
1652
+ suite.suites = await Promise.all(jsonSuite.suites.map((suite2) => parseJSONSuite(context, suite2)));
1653
+ if (jsonSuite.specs && jsonSuite.specs.length)
1654
+ suite.tests = await Promise.all(jsonSuite.specs.map((spec) => parseJSONSpec(context, spec)));
1655
+ return suite;
1656
+ }
1657
+ async function parseJSONSpec(context, jsonSpec) {
1658
+ const test = {
1659
+ title: jsonSpec.title,
1660
+ tags: jsonSpec.tags,
1661
+ location: {
1662
+ file: gitFilePath(context.gitRoot, normalizePath(posixPath2.join(context.testBaseDir, normalizePath(jsonSpec.file)))),
1663
+ line: jsonSpec.line,
1664
+ column: jsonSpec.column
1665
+ },
1666
+ attempts: []
1667
+ };
1668
+ for (const jsonTest of jsonSpec.tests) {
1669
+ const environmentIdx = context.projectId2environmentIdx.get(jsonTest.projectId);
1670
+ if (environmentIdx === void 0)
1671
+ throw new Error("Inconsistent report - no project for a test found!");
1672
+ const testResults = jsonTest.results.filter((result) => result.status !== void 0);
1673
+ if (!testResults.length)
1674
+ continue;
1675
+ test.attempts.push(...await Promise.all(testResults.map((jsonTestResult) => parseJSONTestResult(context, jsonTest, environmentIdx, jsonTestResult))));
1676
+ }
1677
+ return test;
1678
+ }
1679
+ function createLocation(context, location) {
1680
+ return {
1681
+ file: gitFilePath(context.gitRoot, normalizePath(location.file)),
1682
+ line: location.line,
1683
+ column: location.column
1684
+ };
1685
+ }
1686
+ async function parseJSONTestResult(context, jsonTest, environmentIdx, jsonTestResult) {
1687
+ const attachments = [];
1688
+ const attempt = {
1689
+ timeout: parseDurationMS(jsonTest.timeout),
1690
+ annotations: jsonTest.annotations.map((annotation) => ({
1691
+ type: annotation.type,
1692
+ description: annotation.description,
1693
+ location: annotation.location ? createLocation(context, annotation.location) : void 0
1694
+ })),
1695
+ environmentIdx,
1696
+ expectedStatus: jsonTest.expectedStatus,
1697
+ parallelIndex: jsonTestResult.parallelIndex,
1698
+ status: jsonTestResult.status,
1699
+ errors: jsonTestResult.errors && jsonTestResult.errors.length ? jsonTestResult.errors.map((error) => parseJSONError(context, error)) : void 0,
1700
+ stdout: jsonTestResult.stdout && jsonTestResult.stdout.length ? jsonTestResult.stdout : void 0,
1701
+ stderr: jsonTestResult.stderr && jsonTestResult.stderr.length ? jsonTestResult.stderr : void 0,
1702
+ steps: jsonTestResult.steps ? jsonTestResult.steps.map((jsonTestStep) => parseJSONTestStep(context, jsonTestStep)) : void 0,
1703
+ startTimestamp: parseStringDate(jsonTestResult.startTime),
1704
+ duration: jsonTestResult.duration && jsonTestResult.duration > 0 ? parseDurationMS(jsonTestResult.duration) : 0,
1705
+ attachments
1706
+ };
1707
+ if (context.extractAttachments) {
1708
+ await Promise.all((jsonTestResult.attachments ?? []).map(async (jsonAttachment) => {
1709
+ if (jsonAttachment.path && !await existsAsync(jsonAttachment.path)) {
1710
+ context.unaccessibleAttachmentPaths.push(jsonAttachment.path);
1711
+ return;
1712
+ }
1713
+ const id = jsonAttachment.path ? await sha1File(jsonAttachment.path) : sha1Buffer(jsonAttachment.body ?? "");
1714
+ context.attachments.set(id, {
1715
+ contentType: jsonAttachment.contentType,
1716
+ id,
1717
+ body: jsonAttachment.body ? Buffer.from(jsonAttachment.body) : void 0,
1718
+ path: jsonAttachment.path
1719
+ });
1720
+ attachments.push({
1721
+ id,
1722
+ name: jsonAttachment.name,
1723
+ contentType: jsonAttachment.contentType
1724
+ });
1725
+ }));
1726
+ }
1727
+ return attempt;
1728
+ }
1729
+ function parseJSONTestStep(context, jsonStep) {
1730
+ const step = {
1731
+ // NOTE: jsonStep.duration was -1 in some playwright versions
1732
+ duration: parseDurationMS(Math.max(jsonStep.duration, 0)),
1733
+ title: jsonStep.title
1734
+ };
1735
+ if (jsonStep.error)
1736
+ step.error = parseJSONError(context, jsonStep.error);
1737
+ if (jsonStep.steps)
1738
+ step.steps = jsonStep.steps.map((childJSONStep) => parseJSONTestStep(context, childJSONStep));
1739
+ return step;
1740
+ }
1741
+ function parseJSONError(context, error) {
1742
+ return {
1743
+ location: error.location ? createLocation(context, error.location) : void 0,
1744
+ message: error.message ? stripAnsi(error.message).split("\n")[0] : void 0,
1745
+ stack: error.stack,
1746
+ value: error.value
1747
+ };
1748
+ }
1749
+
1750
+ // src/cli/cmd-upload-playwright-json.ts
1751
+ async function cmdUploadPlaywrightJson(relativePath, options) {
1752
+ const fullPath = path6.resolve(relativePath);
1753
+ if (!await fs6.access(fullPath, fs6.constants.F_OK).then(() => true).catch(() => false)) {
1754
+ console.error(`Error: path ${fullPath} is not accessible`);
1755
+ process.exit(1);
1756
+ }
1757
+ const text = await fs6.readFile(fullPath, "utf-8");
1758
+ const playwrightJson = JSON.parse(text);
1759
+ const { attachments, report, unaccessibleAttachmentPaths } = await PlaywrightJSONReport.parse(PlaywrightJSONReport.collectMetadata(), playwrightJson, {
1760
+ extractAttachments: true
1761
+ });
1762
+ for (const unaccessibleAttachment of unaccessibleAttachmentPaths)
1763
+ console.warn(`WARN: cannot access attachment ${unaccessibleAttachment}`);
1764
+ const uploader = new ReportUploader({
1765
+ flakinessAccessToken: options.accessToken,
1766
+ flakinessEndpoint: options.endpoint
1767
+ });
1768
+ const upload = uploader.createUpload(report, attachments);
1769
+ const uploadResult = await upload.upload();
1770
+ if (!uploadResult.success) {
1771
+ console.log(`[flakiness.io] X Failed to upload to ${options.endpoint}: ${uploadResult.message}`);
1772
+ } else {
1773
+ console.log(`[flakiness.io] \u2713 Report uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
1774
+ }
1775
+ }
1776
+
1777
+ // src/cli/cmd-upload.ts
1778
+ import { ReportUploader as ReportUploader2 } from "@flakiness/sdk/uploader";
1779
+ import chalk from "chalk";
1780
+ import fs7 from "fs/promises";
1781
+ import path7 from "path";
1782
+ var warn = (txt) => console.warn(chalk.yellow(`[flakiness.io] WARN: ${txt}`));
1783
+ var err = (txt) => console.error(chalk.red(`[flakiness.io] Error: ${txt}`));
1784
+ var log = (txt) => console.log(`[flakiness.io] ${txt}`);
1785
+ async function cmdUpload(relativePaths, options) {
1786
+ const uploader = new ReportUploader2({
1787
+ flakinessAccessToken: options.accessToken,
1788
+ flakinessEndpoint: options.endpoint
1789
+ });
1790
+ for (const relativePath of relativePaths) {
1791
+ const fullPath = path7.resolve(relativePath);
1792
+ if (!await fs7.access(fullPath, fs7.constants.F_OK).then(() => true).catch(() => false)) {
1793
+ err(`Path ${fullPath} is not accessible!`);
1794
+ process.exit(1);
1795
+ }
1796
+ const text = await fs7.readFile(fullPath, "utf-8");
1797
+ const report = JSON.parse(text);
1798
+ const attachmentsDir = options.attachmentsDir ?? path7.dirname(fullPath);
1799
+ const { attachmentIdToPath, missingAttachments } = await resolveAttachmentPaths(report, attachmentsDir);
1800
+ if (missingAttachments.length) {
1801
+ warn(`Missing ${missingAttachments.length} attachments`);
1802
+ }
1803
+ const upload = uploader.createUpload(report, Array.from(attachmentIdToPath.values()));
1804
+ const uploadResult = await upload.upload();
1805
+ if (!uploadResult.success) {
1806
+ err(`Failed to upload to ${options.endpoint}: ${uploadResult.message}`);
1807
+ } else {
1808
+ log(`\u2713 Uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
1809
+ }
1810
+ }
1811
+ }
1812
+
1813
+ // src/cli/cmd-whoami.ts
1814
+ async function cmdWhoami() {
1815
+ const session2 = await FlakinessSession.load();
1816
+ if (!session2) {
1817
+ console.log('Not logged in. Run "flakiness login" first.');
1818
+ process.exit(1);
1819
+ }
1820
+ console.log(`Logged into ${session2.endpoint()}`);
1821
+ const user = await session2.api.user.whoami.GET();
1822
+ console.log(user);
1823
+ }
1824
+
1825
+ // src/cli/cli.ts
1826
+ var session = await FlakinessSession.load();
1827
+ var optAccessToken = new Option("-t, --access-token <token>", "A read-write flakiness.io access token").env("FLAKINESS_ACCESS_TOKEN");
1828
+ var optEndpoint = new Option("-e, --endpoint <url>", "An endpoint where the service is deployed").default(session?.endpoint() ?? DEFAULT_FLAKINESS_ENDPOINT).env("FLAKINESS_ENDPOINT");
1829
+ var optAttachmentsDir = new Option("--attachments-dir <dir>", "Directory containing attachments to upload. Defaults to the report directory");
1830
+ async function runCommand(callback) {
1831
+ try {
1832
+ await callback();
1833
+ } catch (e) {
1834
+ if (!(e instanceof Error))
1835
+ throw e;
1836
+ console.error(errorText(e));
1837
+ process.exit(1);
1838
+ }
1839
+ }
1840
+ var program = new Command().name("flakiness").description("Flakiness CLI tool").version(package_default.version);
1841
+ async function ensureAccessToken(options) {
1842
+ let accessToken = options.accessToken;
1843
+ if (!accessToken) {
1844
+ const config = await FlakinessProjectConfig4.load();
1845
+ const projectPublicId = config.projectPublicId();
1846
+ if (session && projectPublicId) {
1847
+ try {
1848
+ accessToken = (await session.api.project.getProject.GET({ projectPublicId })).readWriteAccessToken;
1849
+ } catch (e) {
1850
+ if (e instanceof TypedHTTP2.HttpError && e.status === 404) {
1851
+ } else {
1852
+ throw e;
1853
+ }
1854
+ }
1855
+ }
1856
+ }
1857
+ assert3(accessToken, `Please either pass FLAKINESS_ACCESS_TOKEN or run login + link`);
1858
+ return {
1859
+ ...options,
1860
+ accessToken
1861
+ };
1862
+ }
1863
+ program.command("upload-playwright-json", { hidden: true }).description("Upload Playwright Test JSON report to the flakiness.io service").argument("<relative-path-to-json>", "Path to the Playwright JSON report file").addOption(optAccessToken).addOption(optEndpoint).action(async (relativePath, options) => runCommand(async () => {
1864
+ await cmdUploadPlaywrightJson(relativePath, await ensureAccessToken(options));
1865
+ }));
1866
+ program.command("login").description("Login to the Flakiness.io service").addOption(optEndpoint).action(async (options) => runCommand(async () => {
1867
+ await cmdLogin(options.endpoint);
1868
+ }));
1869
+ program.command("logout").description("Logout from current session").action(async () => runCommand(async () => {
1870
+ await cmdLogout();
1871
+ }));
1872
+ program.command("whoami").description("Show current logged in user information").action(async () => runCommand(async () => {
1873
+ await cmdWhoami();
1874
+ }));
1875
+ program.command("link").description("Link repository to the flakiness project").addOption(optEndpoint).argument("flakiness.io/org/project", "A URL of the Flakiness.io project").action(async (slugOrUrl, options) => runCommand(async () => {
1876
+ let slug = slugOrUrl;
1877
+ let endpoint = options.endpoint;
1878
+ if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
1879
+ const url = URL.parse(slugOrUrl);
1880
+ if (!url) {
1881
+ console.error(`Invalid URL: ${slugOrUrl}`);
1882
+ process.exit(1);
1883
+ }
1884
+ slug = url.pathname.substring(1);
1885
+ endpoint = url.origin;
1886
+ } else if (slugOrUrl.startsWith("flakiness.io/")) {
1887
+ endpoint = "https://flakiness.io";
1888
+ slug = slugOrUrl.substring("flakiness.io/".length);
1889
+ }
1890
+ let session2 = await FlakinessSession.load();
1891
+ if (!session2 || session2.endpoint() !== endpoint || await session2.api.user.whoami.GET().catch((e) => void 0) === void 0)
1892
+ session2 = await cmdLogin(endpoint);
1893
+ await cmdLink(session2, slug);
1894
+ }));
1895
+ program.command("unlink").description("Unlink repository from the flakiness project").action(async () => runCommand(async () => {
1896
+ await cmdUnlink();
1897
+ }));
1898
+ program.command("status").description("Status repository from the flakiness project").action(async () => runCommand(async () => {
1899
+ await cmdStatus();
1900
+ }));
1901
+ var optRunId = new Option("--run-id <runId>", "RunId flakiness.io access token").argParser((value) => {
1902
+ const parsed = parseInt(value, 10);
1903
+ if (isNaN(parsed) || parsed < 1) {
1904
+ throw new Error("runId must be a number >= 1");
1905
+ }
1906
+ return parsed;
1907
+ });
1908
+ var optSince = new Option("--since <date>", "Start date for filtering").argParser((value) => {
1909
+ const parsed = new Date(value);
1910
+ if (isNaN(parsed.getTime())) {
1911
+ throw new Error("Invalid date format");
1912
+ }
1913
+ return parsed;
1914
+ });
1915
+ var optParallel = new Option("-j, --parallel <date>", "Parallel jobs to run").argParser((value) => {
1916
+ const parsed = parseInt(value, 10);
1917
+ if (isNaN(parsed) || parsed < 1) {
1918
+ throw new Error("parallel must be a number >= 1");
1919
+ }
1920
+ return parsed;
1921
+ });
1922
+ program.command("download").description("Download run").addOption(optSince).addOption(optRunId).addOption(optParallel).action(async (options) => runCommand(async () => {
1923
+ const config = await FlakinessProjectConfig4.load();
1924
+ const session2 = await FlakinessSession.loadOrDie();
1925
+ const projectPublicId = config.projectPublicId();
1926
+ if (!projectPublicId)
1927
+ throw new Error(`Please link to flakiness project with 'npx flakiness link'`);
1928
+ const project = await session2.api.project.getProject.GET({ projectPublicId }).catch((e) => void 0);
1929
+ if (!project)
1930
+ throw new Error(`Failed to fetch linked project; please re-link with 'npx flakiness link'`);
1931
+ let runIds = [];
1932
+ if (options.runId) {
1933
+ runIds = [options.runId, options.runId];
1934
+ } else if (options.since) {
1935
+ runIds = await session2.api.project.listRuns.GET({
1936
+ orgSlug: project.org.orgSlug,
1937
+ projectSlug: project.projectSlug,
1938
+ sinceTimestampMs: +options.since
1939
+ });
1940
+ console.log(`Found ${Ranges.cardinality(runIds)} reports uploaded since ${options.since}`);
1941
+ }
1942
+ const alreadyExisting = fs8.readdirSync(process.cwd());
1943
+ const downloadedRuns = alreadyExisting.filter((entry) => entry.startsWith("fkrun-")).map((run) => parseInt(run.substring(`fkrun-`.length), 10)).filter((runId) => !isNaN(runId));
1944
+ console.log(`Found ${downloadedRuns.length} locally downloaded reports`);
1945
+ const toBeDownloaded = Ranges.subtract(runIds, Ranges.fromList(downloadedRuns));
1946
+ console.log(`Downloading ${Ranges.cardinality(toBeDownloaded)} reports`);
1947
+ const it = Ranges.iterate(toBeDownloaded);
1948
+ const downloaders = [];
1949
+ const spinner = ora("Downloading reports:").start();
1950
+ const total = Ranges.cardinality(toBeDownloaded);
1951
+ let downloaded = 0;
1952
+ for (let i = 0; i < (options.parallel ?? 1); ++i) {
1953
+ downloaders.push((async () => {
1954
+ for (let result = it.next(); !result.done; result = it.next()) {
1955
+ const runId = result.value;
1956
+ await cmdDownload(session2, project, result.value, `fkrun-${runId}`);
1957
+ ++downloaded;
1958
+ spinner.text = `Downloaded ${Math.floor(downloaded / total * 100)}% [${downloaded}/${total}] reports`;
1959
+ }
1960
+ })());
1961
+ }
1962
+ await Promise.all(downloaders);
1963
+ spinner.stop();
1964
+ }));
1965
+ program.command("upload").description("Upload Flakiness report to the flakiness.io service").argument("<relative-paths...>", "Paths to the Flakiness report files").addOption(optAccessToken).addOption(optEndpoint).addOption(optAttachmentsDir).action(async (relativePaths, options) => {
1966
+ await runCommand(async () => {
1967
+ await cmdUpload(relativePaths, await ensureAccessToken(options));
1968
+ });
1969
+ });
1970
+ program.command("show").description("Show flakiness report").argument("[relative-path]", "Path to the Flakiness report file or folder that contains `report.json`. (default: flakiness-report)").action(async (arg) => runCommand(async () => {
1971
+ const dir = path8.resolve(arg ?? "flakiness-report");
1972
+ await showReport(dir);
1973
+ }));
1974
+ program.command("convert-junit").description("Convert JUnit XML report(s) to Flakiness report format").argument("<junit-root-dir-path>", "Path to JUnit XML file or directory containing XML files").option("--env-name <name>", "Environment name for the report", "junit").option("--commit-id <id>", "Git commit ID (auto-detected if not provided)").option("--output-dir <dir>", "Output directory for the report", "flakiness-report").action(async (junitPath, options) => {
1975
+ await runCommand(async () => {
1976
+ await cmdConvert(junitPath, options);
1977
+ });
1978
+ });
1979
+ await program.parseAsync();
1980
+ //# sourceMappingURL=cli.js.map