@flakiness/sdk 0.153.0 → 0.155.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 +1 -0
- package/lib/browser.js +136 -6
- package/lib/index.js +218 -35
- package/package.json +5 -2
- package/types/src/index.d.ts +1 -1
- package/types/src/index.d.ts.map +1 -1
- package/types/src/readReport.d.ts +49 -0
- package/types/src/readReport.d.ts.map +1 -0
package/README.md
CHANGED
|
@@ -95,6 +95,7 @@ Use this entry point when you need to process or manipulate reports in browser-b
|
|
|
95
95
|
- **`RAMUtilization`** - Track RAM utilization over time via periodic sampling
|
|
96
96
|
|
|
97
97
|
### Working with Reports
|
|
98
|
+
- **`readReport()`** - Read a Flakiness report and its attachments from disk
|
|
98
99
|
- **`showReport()`** - Start a local server and open the report in your browser
|
|
99
100
|
- **`uploadReport()`** - Upload reports and attachments to Flakiness.io
|
|
100
101
|
- **`writeReport()`** - Write reports to disk in the standard Flakiness report format
|
package/lib/browser.js
CHANGED
|
@@ -4,8 +4,139 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
|
|
7
|
+
// node_modules/@flakiness/flakiness-report/lib/flakinessReport.js
|
|
8
|
+
var FlakinessReport;
|
|
9
|
+
((FlakinessReport22) => {
|
|
10
|
+
FlakinessReport22.CATEGORY_PLAYWRIGHT = "playwright";
|
|
11
|
+
FlakinessReport22.CATEGORY_PYTEST = "pytest";
|
|
12
|
+
FlakinessReport22.CATEGORY_JUNIT = "junit";
|
|
13
|
+
})(FlakinessReport || (FlakinessReport = {}));
|
|
14
|
+
|
|
15
|
+
// node_modules/@flakiness/flakiness-report/lib/schema.js
|
|
16
|
+
import z from "zod/v4";
|
|
17
|
+
var Schema;
|
|
18
|
+
((Schema2) => {
|
|
19
|
+
Schema2.CommitId = z.string().min(40).max(40);
|
|
20
|
+
Schema2.AttachmentId = z.string().min(1).max(1024);
|
|
21
|
+
Schema2.UnixTimestampMS = z.number().min(0);
|
|
22
|
+
Schema2.DurationMS = z.number().min(0);
|
|
23
|
+
Schema2.Number1Based = z.number();
|
|
24
|
+
Schema2.GitFilePath = z.string().min(0);
|
|
25
|
+
Schema2.Location = z.object({
|
|
26
|
+
file: Schema2.GitFilePath,
|
|
27
|
+
line: Schema2.Number1Based,
|
|
28
|
+
// Note: Locations for file suites are (0, 0).
|
|
29
|
+
column: Schema2.Number1Based
|
|
30
|
+
});
|
|
31
|
+
Schema2.TestStatus = z.enum(["passed", "failed", "timedOut", "skipped", "interrupted"]);
|
|
32
|
+
Schema2.Environment = z.object({
|
|
33
|
+
name: z.string().min(1).max(512),
|
|
34
|
+
systemData: z.object({
|
|
35
|
+
osName: z.string().optional(),
|
|
36
|
+
osVersion: z.string().optional(),
|
|
37
|
+
osArch: z.string().optional()
|
|
38
|
+
}).optional(),
|
|
39
|
+
metadata: z.any().optional(),
|
|
40
|
+
userSuppliedData: z.any().optional()
|
|
41
|
+
});
|
|
42
|
+
Schema2.STDIOEntry = z.union([
|
|
43
|
+
z.object({ text: z.string() }),
|
|
44
|
+
z.object({ buffer: z.string() })
|
|
45
|
+
]);
|
|
46
|
+
Schema2.ReportError = z.object({
|
|
47
|
+
location: Schema2.Location.optional(),
|
|
48
|
+
message: z.string().optional(),
|
|
49
|
+
stack: z.string().optional(),
|
|
50
|
+
snippet: z.string().optional(),
|
|
51
|
+
value: z.string().optional()
|
|
52
|
+
});
|
|
53
|
+
Schema2.SuiteType = z.enum(["file", "anonymous suite", "suite"]);
|
|
54
|
+
Schema2.TestStep = z.object({
|
|
55
|
+
title: z.string(),
|
|
56
|
+
duration: Schema2.DurationMS.optional(),
|
|
57
|
+
location: Schema2.Location.optional(),
|
|
58
|
+
snippet: z.string().optional(),
|
|
59
|
+
error: Schema2.ReportError.optional(),
|
|
60
|
+
get steps() {
|
|
61
|
+
return z.array(Schema2.TestStep).optional();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
Schema2.Attachment = z.object({
|
|
65
|
+
name: z.string(),
|
|
66
|
+
contentType: z.string(),
|
|
67
|
+
id: Schema2.AttachmentId
|
|
68
|
+
});
|
|
69
|
+
Schema2.Annotation = z.object({
|
|
70
|
+
type: z.string(),
|
|
71
|
+
description: z.string().optional(),
|
|
72
|
+
location: Schema2.Location.optional()
|
|
73
|
+
});
|
|
74
|
+
Schema2.RunAttempt = z.object({
|
|
75
|
+
// Index of the environment in the environments array (must be >= 0).
|
|
76
|
+
environmentIdx: z.number().min(0).optional(),
|
|
77
|
+
expectedStatus: Schema2.TestStatus.optional(),
|
|
78
|
+
status: Schema2.TestStatus.optional(),
|
|
79
|
+
startTimestamp: Schema2.UnixTimestampMS,
|
|
80
|
+
duration: Schema2.DurationMS.optional(),
|
|
81
|
+
timeout: Schema2.DurationMS.optional(),
|
|
82
|
+
annotations: z.array(Schema2.Annotation).optional(),
|
|
83
|
+
errors: z.array(Schema2.ReportError).optional(),
|
|
84
|
+
parallelIndex: z.number().optional(),
|
|
85
|
+
steps: z.array(Schema2.TestStep).optional(),
|
|
86
|
+
stdout: z.array(Schema2.STDIOEntry).optional(),
|
|
87
|
+
stderr: z.array(Schema2.STDIOEntry).optional(),
|
|
88
|
+
attachments: z.array(Schema2.Attachment).optional()
|
|
89
|
+
});
|
|
90
|
+
Schema2.Suite = z.object({
|
|
91
|
+
type: Schema2.SuiteType,
|
|
92
|
+
title: z.string(),
|
|
93
|
+
location: Schema2.Location.optional(),
|
|
94
|
+
get suites() {
|
|
95
|
+
return z.array(Schema2.Suite).optional();
|
|
96
|
+
},
|
|
97
|
+
get tests() {
|
|
98
|
+
return z.array(Schema2.Test).optional();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
Schema2.Test = z.object({
|
|
102
|
+
title: z.string(),
|
|
103
|
+
location: Schema2.Location.optional(),
|
|
104
|
+
tags: z.array(z.string()).optional(),
|
|
105
|
+
attempts: z.array(Schema2.RunAttempt)
|
|
106
|
+
});
|
|
107
|
+
Schema2.SystemUtilizationSample = z.object({
|
|
108
|
+
dts: Schema2.DurationMS,
|
|
109
|
+
// Must be between 0 and 100 (inclusive). Can be a rational number.
|
|
110
|
+
cpuUtilization: z.number().min(0).max(100),
|
|
111
|
+
// Must be between 0 and 100 (inclusive). Can be a rational number.
|
|
112
|
+
memoryUtilization: z.number().min(0).max(100)
|
|
113
|
+
});
|
|
114
|
+
Schema2.SystemUtilization = z.object({
|
|
115
|
+
totalMemoryBytes: z.number().min(0),
|
|
116
|
+
startTimestamp: Schema2.UnixTimestampMS,
|
|
117
|
+
samples: z.array(Schema2.SystemUtilizationSample)
|
|
118
|
+
});
|
|
119
|
+
Schema2.UtilizationTelemetry = z.tuple([Schema2.DurationMS, z.number().min(0).max(100)]);
|
|
120
|
+
Schema2.Report = z.object({
|
|
121
|
+
category: z.string().min(1).max(100),
|
|
122
|
+
commitId: Schema2.CommitId,
|
|
123
|
+
relatedCommitIds: z.array(Schema2.CommitId).optional(),
|
|
124
|
+
configPath: Schema2.GitFilePath.optional(),
|
|
125
|
+
url: z.string().optional(),
|
|
126
|
+
environments: z.array(Schema2.Environment).min(1),
|
|
127
|
+
suites: z.array(Schema2.Suite).optional(),
|
|
128
|
+
tests: z.array(Schema2.Test).optional(),
|
|
129
|
+
unattributedErrors: z.array(Schema2.ReportError).optional(),
|
|
130
|
+
startTimestamp: Schema2.UnixTimestampMS,
|
|
131
|
+
duration: Schema2.DurationMS,
|
|
132
|
+
systemUtilization: z.optional(Schema2.SystemUtilization),
|
|
133
|
+
cpuCount: z.number().min(0).optional(),
|
|
134
|
+
cpuAvg: z.array(Schema2.UtilizationTelemetry).optional(),
|
|
135
|
+
cpuMax: z.array(Schema2.UtilizationTelemetry).optional(),
|
|
136
|
+
ram: z.array(Schema2.UtilizationTelemetry).optional(),
|
|
137
|
+
ramBytes: z.number().min(0).optional()
|
|
138
|
+
});
|
|
139
|
+
})(Schema || (Schema = {}));
|
|
9
140
|
|
|
10
141
|
// src/reportUtilsBrowser.ts
|
|
11
142
|
var reportUtilsBrowser_exports = {};
|
|
@@ -175,8 +306,7 @@ function computeTestId(test, suiteId) {
|
|
|
175
306
|
}
|
|
176
307
|
|
|
177
308
|
// src/validateReport.ts
|
|
178
|
-
import
|
|
179
|
-
import z from "zod";
|
|
309
|
+
import z2 from "zod/v4";
|
|
180
310
|
function validateReport(report) {
|
|
181
311
|
const validation = Schema.Report.safeParse(report);
|
|
182
312
|
if (!validation.success) {
|
|
@@ -184,7 +314,7 @@ function validateReport(report) {
|
|
|
184
314
|
const allIssues = validation.error.issues;
|
|
185
315
|
const shownIssues = allIssues.slice(0, MAX_ISSUES);
|
|
186
316
|
const remaining = allIssues.length - shownIssues.length;
|
|
187
|
-
const base = [
|
|
317
|
+
const base = [z2.prettifyError(new z2.ZodError(shownIssues))];
|
|
188
318
|
if (remaining > 0)
|
|
189
319
|
base.push(`... and ${remaining} more issue${remaining === 1 ? "" : "s"} ...`);
|
|
190
320
|
return base.join("\n");
|
|
@@ -214,7 +344,7 @@ function visitTests(report, testVisitor) {
|
|
|
214
344
|
visitSuite(suite, []);
|
|
215
345
|
}
|
|
216
346
|
export {
|
|
217
|
-
|
|
347
|
+
FlakinessReport,
|
|
218
348
|
reportUtilsBrowser_exports as ReportUtils
|
|
219
349
|
};
|
|
220
350
|
//# sourceMappingURL=browser.js.map
|
package/lib/index.js
CHANGED
|
@@ -4,9 +4,6 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
// src/index.ts
|
|
8
|
-
import { FlakinessReport as FlakinessReport2, Schema as Schema2 } from "@flakiness/flakiness-report";
|
|
9
|
-
|
|
10
7
|
// src/ciUtils.ts
|
|
11
8
|
var CIUtils;
|
|
12
9
|
((CIUtils2) => {
|
|
@@ -778,9 +775,142 @@ function computeTestId(test, suiteId) {
|
|
|
778
775
|
});
|
|
779
776
|
}
|
|
780
777
|
|
|
778
|
+
// node_modules/@flakiness/flakiness-report/lib/flakinessReport.js
|
|
779
|
+
var FlakinessReport;
|
|
780
|
+
((FlakinessReport22) => {
|
|
781
|
+
FlakinessReport22.CATEGORY_PLAYWRIGHT = "playwright";
|
|
782
|
+
FlakinessReport22.CATEGORY_PYTEST = "pytest";
|
|
783
|
+
FlakinessReport22.CATEGORY_JUNIT = "junit";
|
|
784
|
+
})(FlakinessReport || (FlakinessReport = {}));
|
|
785
|
+
|
|
786
|
+
// node_modules/@flakiness/flakiness-report/lib/schema.js
|
|
787
|
+
import z from "zod/v4";
|
|
788
|
+
var Schema;
|
|
789
|
+
((Schema2) => {
|
|
790
|
+
Schema2.CommitId = z.string().min(40).max(40);
|
|
791
|
+
Schema2.AttachmentId = z.string().min(1).max(1024);
|
|
792
|
+
Schema2.UnixTimestampMS = z.number().min(0);
|
|
793
|
+
Schema2.DurationMS = z.number().min(0);
|
|
794
|
+
Schema2.Number1Based = z.number();
|
|
795
|
+
Schema2.GitFilePath = z.string().min(0);
|
|
796
|
+
Schema2.Location = z.object({
|
|
797
|
+
file: Schema2.GitFilePath,
|
|
798
|
+
line: Schema2.Number1Based,
|
|
799
|
+
// Note: Locations for file suites are (0, 0).
|
|
800
|
+
column: Schema2.Number1Based
|
|
801
|
+
});
|
|
802
|
+
Schema2.TestStatus = z.enum(["passed", "failed", "timedOut", "skipped", "interrupted"]);
|
|
803
|
+
Schema2.Environment = z.object({
|
|
804
|
+
name: z.string().min(1).max(512),
|
|
805
|
+
systemData: z.object({
|
|
806
|
+
osName: z.string().optional(),
|
|
807
|
+
osVersion: z.string().optional(),
|
|
808
|
+
osArch: z.string().optional()
|
|
809
|
+
}).optional(),
|
|
810
|
+
metadata: z.any().optional(),
|
|
811
|
+
userSuppliedData: z.any().optional()
|
|
812
|
+
});
|
|
813
|
+
Schema2.STDIOEntry = z.union([
|
|
814
|
+
z.object({ text: z.string() }),
|
|
815
|
+
z.object({ buffer: z.string() })
|
|
816
|
+
]);
|
|
817
|
+
Schema2.ReportError = z.object({
|
|
818
|
+
location: Schema2.Location.optional(),
|
|
819
|
+
message: z.string().optional(),
|
|
820
|
+
stack: z.string().optional(),
|
|
821
|
+
snippet: z.string().optional(),
|
|
822
|
+
value: z.string().optional()
|
|
823
|
+
});
|
|
824
|
+
Schema2.SuiteType = z.enum(["file", "anonymous suite", "suite"]);
|
|
825
|
+
Schema2.TestStep = z.object({
|
|
826
|
+
title: z.string(),
|
|
827
|
+
duration: Schema2.DurationMS.optional(),
|
|
828
|
+
location: Schema2.Location.optional(),
|
|
829
|
+
snippet: z.string().optional(),
|
|
830
|
+
error: Schema2.ReportError.optional(),
|
|
831
|
+
get steps() {
|
|
832
|
+
return z.array(Schema2.TestStep).optional();
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
Schema2.Attachment = z.object({
|
|
836
|
+
name: z.string(),
|
|
837
|
+
contentType: z.string(),
|
|
838
|
+
id: Schema2.AttachmentId
|
|
839
|
+
});
|
|
840
|
+
Schema2.Annotation = z.object({
|
|
841
|
+
type: z.string(),
|
|
842
|
+
description: z.string().optional(),
|
|
843
|
+
location: Schema2.Location.optional()
|
|
844
|
+
});
|
|
845
|
+
Schema2.RunAttempt = z.object({
|
|
846
|
+
// Index of the environment in the environments array (must be >= 0).
|
|
847
|
+
environmentIdx: z.number().min(0).optional(),
|
|
848
|
+
expectedStatus: Schema2.TestStatus.optional(),
|
|
849
|
+
status: Schema2.TestStatus.optional(),
|
|
850
|
+
startTimestamp: Schema2.UnixTimestampMS,
|
|
851
|
+
duration: Schema2.DurationMS.optional(),
|
|
852
|
+
timeout: Schema2.DurationMS.optional(),
|
|
853
|
+
annotations: z.array(Schema2.Annotation).optional(),
|
|
854
|
+
errors: z.array(Schema2.ReportError).optional(),
|
|
855
|
+
parallelIndex: z.number().optional(),
|
|
856
|
+
steps: z.array(Schema2.TestStep).optional(),
|
|
857
|
+
stdout: z.array(Schema2.STDIOEntry).optional(),
|
|
858
|
+
stderr: z.array(Schema2.STDIOEntry).optional(),
|
|
859
|
+
attachments: z.array(Schema2.Attachment).optional()
|
|
860
|
+
});
|
|
861
|
+
Schema2.Suite = z.object({
|
|
862
|
+
type: Schema2.SuiteType,
|
|
863
|
+
title: z.string(),
|
|
864
|
+
location: Schema2.Location.optional(),
|
|
865
|
+
get suites() {
|
|
866
|
+
return z.array(Schema2.Suite).optional();
|
|
867
|
+
},
|
|
868
|
+
get tests() {
|
|
869
|
+
return z.array(Schema2.Test).optional();
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
Schema2.Test = z.object({
|
|
873
|
+
title: z.string(),
|
|
874
|
+
location: Schema2.Location.optional(),
|
|
875
|
+
tags: z.array(z.string()).optional(),
|
|
876
|
+
attempts: z.array(Schema2.RunAttempt)
|
|
877
|
+
});
|
|
878
|
+
Schema2.SystemUtilizationSample = z.object({
|
|
879
|
+
dts: Schema2.DurationMS,
|
|
880
|
+
// Must be between 0 and 100 (inclusive). Can be a rational number.
|
|
881
|
+
cpuUtilization: z.number().min(0).max(100),
|
|
882
|
+
// Must be between 0 and 100 (inclusive). Can be a rational number.
|
|
883
|
+
memoryUtilization: z.number().min(0).max(100)
|
|
884
|
+
});
|
|
885
|
+
Schema2.SystemUtilization = z.object({
|
|
886
|
+
totalMemoryBytes: z.number().min(0),
|
|
887
|
+
startTimestamp: Schema2.UnixTimestampMS,
|
|
888
|
+
samples: z.array(Schema2.SystemUtilizationSample)
|
|
889
|
+
});
|
|
890
|
+
Schema2.UtilizationTelemetry = z.tuple([Schema2.DurationMS, z.number().min(0).max(100)]);
|
|
891
|
+
Schema2.Report = z.object({
|
|
892
|
+
category: z.string().min(1).max(100),
|
|
893
|
+
commitId: Schema2.CommitId,
|
|
894
|
+
relatedCommitIds: z.array(Schema2.CommitId).optional(),
|
|
895
|
+
configPath: Schema2.GitFilePath.optional(),
|
|
896
|
+
url: z.string().optional(),
|
|
897
|
+
environments: z.array(Schema2.Environment).min(1),
|
|
898
|
+
suites: z.array(Schema2.Suite).optional(),
|
|
899
|
+
tests: z.array(Schema2.Test).optional(),
|
|
900
|
+
unattributedErrors: z.array(Schema2.ReportError).optional(),
|
|
901
|
+
startTimestamp: Schema2.UnixTimestampMS,
|
|
902
|
+
duration: Schema2.DurationMS,
|
|
903
|
+
systemUtilization: z.optional(Schema2.SystemUtilization),
|
|
904
|
+
cpuCount: z.number().min(0).optional(),
|
|
905
|
+
cpuAvg: z.array(Schema2.UtilizationTelemetry).optional(),
|
|
906
|
+
cpuMax: z.array(Schema2.UtilizationTelemetry).optional(),
|
|
907
|
+
ram: z.array(Schema2.UtilizationTelemetry).optional(),
|
|
908
|
+
ramBytes: z.number().min(0).optional()
|
|
909
|
+
});
|
|
910
|
+
})(Schema || (Schema = {}));
|
|
911
|
+
|
|
781
912
|
// src/validateReport.ts
|
|
782
|
-
import
|
|
783
|
-
import z from "zod";
|
|
913
|
+
import z2 from "zod/v4";
|
|
784
914
|
function validateReport(report) {
|
|
785
915
|
const validation = Schema.Report.safeParse(report);
|
|
786
916
|
if (!validation.success) {
|
|
@@ -788,7 +918,7 @@ function validateReport(report) {
|
|
|
788
918
|
const allIssues = validation.error.issues;
|
|
789
919
|
const shownIssues = allIssues.slice(0, MAX_ISSUES);
|
|
790
920
|
const remaining = allIssues.length - shownIssues.length;
|
|
791
|
-
const base = [
|
|
921
|
+
const base = [z2.prettifyError(new z2.ZodError(shownIssues))];
|
|
792
922
|
if (remaining > 0)
|
|
793
923
|
base.push(`... and ${remaining} more issue${remaining === 1 ? "" : "s"} ...`);
|
|
794
924
|
return base.join("\n");
|
|
@@ -988,15 +1118,69 @@ function visitTests(report, testVisitor) {
|
|
|
988
1118
|
visitSuite(suite, []);
|
|
989
1119
|
}
|
|
990
1120
|
|
|
1121
|
+
// src/readReport.ts
|
|
1122
|
+
import fs5 from "fs/promises";
|
|
1123
|
+
import path from "path";
|
|
1124
|
+
async function readReport(reportFolder) {
|
|
1125
|
+
reportFolder = path.resolve(reportFolder);
|
|
1126
|
+
const text = await fs5.readFile(path.join(reportFolder, "report.json"), "utf-8");
|
|
1127
|
+
const report = JSON.parse(text);
|
|
1128
|
+
const attachmentsFolder = path.join(reportFolder, "attachments");
|
|
1129
|
+
let attachmentFiles = [];
|
|
1130
|
+
try {
|
|
1131
|
+
attachmentFiles = await listFilesRecursively(attachmentsFolder);
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
if (error.code !== "ENOENT") {
|
|
1134
|
+
throw error;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const filenameToPath = new Map(attachmentFiles.map((file) => [path.basename(file), file]));
|
|
1138
|
+
const attachmentIdToPath = /* @__PURE__ */ new Map();
|
|
1139
|
+
const missingAttachments = /* @__PURE__ */ new Map();
|
|
1140
|
+
visitTests(report, (test) => {
|
|
1141
|
+
for (const attempt of test.attempts) {
|
|
1142
|
+
for (const attachment of attempt.attachments ?? []) {
|
|
1143
|
+
const attachmentPath = filenameToPath.get(attachment.id);
|
|
1144
|
+
if (!attachmentPath) {
|
|
1145
|
+
missingAttachments.set(attachment.id, attachment);
|
|
1146
|
+
} else {
|
|
1147
|
+
attachmentIdToPath.set(attachment.id, {
|
|
1148
|
+
contentType: attachment.contentType,
|
|
1149
|
+
id: attachment.id,
|
|
1150
|
+
path: attachmentPath,
|
|
1151
|
+
type: "file"
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
return {
|
|
1158
|
+
report,
|
|
1159
|
+
attachments: Array.from(attachmentIdToPath.values()),
|
|
1160
|
+
missingAttachments: Array.from(missingAttachments.values())
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
async function listFilesRecursively(dir, result = []) {
|
|
1164
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
1165
|
+
for (const entry of entries) {
|
|
1166
|
+
const fullPath = path.join(dir, entry.name);
|
|
1167
|
+
if (entry.isDirectory())
|
|
1168
|
+
await listFilesRecursively(fullPath, result);
|
|
1169
|
+
else
|
|
1170
|
+
result.push(fullPath);
|
|
1171
|
+
}
|
|
1172
|
+
return result;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
991
1175
|
// src/showReport.ts
|
|
992
1176
|
import chalk from "chalk";
|
|
993
1177
|
import open from "open";
|
|
994
1178
|
|
|
995
1179
|
// src/flakinessProjectConfig.ts
|
|
996
|
-
import
|
|
997
|
-
import
|
|
1180
|
+
import fs6 from "fs";
|
|
1181
|
+
import path2 from "path";
|
|
998
1182
|
function createConfigPath(dir) {
|
|
999
|
-
return
|
|
1183
|
+
return path2.join(dir, ".flakiness", "config.json");
|
|
1000
1184
|
}
|
|
1001
1185
|
var gConfigPath;
|
|
1002
1186
|
function ensureConfigPath() {
|
|
@@ -1005,9 +1189,9 @@ function ensureConfigPath() {
|
|
|
1005
1189
|
return gConfigPath;
|
|
1006
1190
|
}
|
|
1007
1191
|
function computeConfigPath() {
|
|
1008
|
-
for (let p = process.cwd(); p !==
|
|
1192
|
+
for (let p = process.cwd(); p !== path2.resolve(p, ".."); p = path2.resolve(p, "..")) {
|
|
1009
1193
|
const configPath = createConfigPath(p);
|
|
1010
|
-
if (
|
|
1194
|
+
if (fs6.existsSync(configPath))
|
|
1011
1195
|
return configPath;
|
|
1012
1196
|
}
|
|
1013
1197
|
try {
|
|
@@ -1040,7 +1224,7 @@ var FlakinessProjectConfig = class _FlakinessProjectConfig {
|
|
|
1040
1224
|
*/
|
|
1041
1225
|
static async load() {
|
|
1042
1226
|
const configPath = ensureConfigPath();
|
|
1043
|
-
const data = await
|
|
1227
|
+
const data = await fs6.promises.readFile(configPath, "utf-8").catch((e) => void 0);
|
|
1044
1228
|
const json = data ? JSON.parse(data) : {};
|
|
1045
1229
|
return new _FlakinessProjectConfig(configPath, json);
|
|
1046
1230
|
}
|
|
@@ -1127,16 +1311,16 @@ var FlakinessProjectConfig = class _FlakinessProjectConfig {
|
|
|
1127
1311
|
* ```
|
|
1128
1312
|
*/
|
|
1129
1313
|
async save() {
|
|
1130
|
-
await
|
|
1131
|
-
await
|
|
1314
|
+
await fs6.promises.mkdir(path2.dirname(this._configPath), { recursive: true });
|
|
1315
|
+
await fs6.promises.writeFile(this._configPath, JSON.stringify(this._config, null, 2));
|
|
1132
1316
|
}
|
|
1133
1317
|
};
|
|
1134
1318
|
|
|
1135
1319
|
// src/staticServer.ts
|
|
1136
1320
|
import debug2 from "debug";
|
|
1137
|
-
import * as
|
|
1321
|
+
import * as fs7 from "fs";
|
|
1138
1322
|
import * as http from "http";
|
|
1139
|
-
import * as
|
|
1323
|
+
import * as path3 from "path";
|
|
1140
1324
|
var log2 = debug2("fk:static_server");
|
|
1141
1325
|
var StaticServer = class {
|
|
1142
1326
|
_server;
|
|
@@ -1157,7 +1341,7 @@ var StaticServer = class {
|
|
|
1157
1341
|
};
|
|
1158
1342
|
constructor(pathPrefix, folderPath, cors) {
|
|
1159
1343
|
this._pathPrefix = "/" + pathPrefix.replace(/^\//, "").replace(/\/$/, "");
|
|
1160
|
-
this._absoluteFolderPath =
|
|
1344
|
+
this._absoluteFolderPath = path3.resolve(folderPath);
|
|
1161
1345
|
this._cors = cors;
|
|
1162
1346
|
this._server = http.createServer((req, res) => this._handleRequest(req, res));
|
|
1163
1347
|
}
|
|
@@ -1253,22 +1437,22 @@ var StaticServer = class {
|
|
|
1253
1437
|
return;
|
|
1254
1438
|
}
|
|
1255
1439
|
const relativePath = url.slice(this._pathPrefix.length);
|
|
1256
|
-
const safeSuffix =
|
|
1257
|
-
const filePath =
|
|
1440
|
+
const safeSuffix = path3.normalize(decodeURIComponent(relativePath)).replace(/^(\.\.[\/\\])+/, "");
|
|
1441
|
+
const filePath = path3.join(this._absoluteFolderPath, safeSuffix);
|
|
1258
1442
|
if (!filePath.startsWith(this._absoluteFolderPath)) {
|
|
1259
1443
|
this._errorResponse(req, res, 403, "Forbidden");
|
|
1260
1444
|
return;
|
|
1261
1445
|
}
|
|
1262
|
-
|
|
1446
|
+
fs7.stat(filePath, (err, stats) => {
|
|
1263
1447
|
if (err || !stats.isFile()) {
|
|
1264
1448
|
this._errorResponse(req, res, 404, "File Not Found");
|
|
1265
1449
|
return;
|
|
1266
1450
|
}
|
|
1267
|
-
const ext =
|
|
1451
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
1268
1452
|
const contentType = this._mimeTypes[ext] || "application/octet-stream";
|
|
1269
1453
|
res.writeHead(200, { "Content-Type": contentType });
|
|
1270
1454
|
log2(`[200] ${req.method} ${req.url} -> ${filePath}`);
|
|
1271
|
-
const readStream =
|
|
1455
|
+
const readStream = fs7.createReadStream(filePath);
|
|
1272
1456
|
readStream.pipe(res);
|
|
1273
1457
|
readStream.on("error", (err2) => {
|
|
1274
1458
|
log2("Stream error: %o", err2);
|
|
@@ -1300,23 +1484,23 @@ async function showReport(reportFolder) {
|
|
|
1300
1484
|
}
|
|
1301
1485
|
|
|
1302
1486
|
// src/writeReport.ts
|
|
1303
|
-
import
|
|
1304
|
-
import
|
|
1487
|
+
import fs8 from "fs";
|
|
1488
|
+
import path4 from "path";
|
|
1305
1489
|
async function writeReport(report, attachments, outputFolder) {
|
|
1306
|
-
const reportPath =
|
|
1307
|
-
const attachmentsFolder =
|
|
1308
|
-
await
|
|
1309
|
-
await
|
|
1310
|
-
await
|
|
1490
|
+
const reportPath = path4.join(outputFolder, "report.json");
|
|
1491
|
+
const attachmentsFolder = path4.join(outputFolder, "attachments");
|
|
1492
|
+
await fs8.promises.rm(outputFolder, { recursive: true, force: true });
|
|
1493
|
+
await fs8.promises.mkdir(outputFolder, { recursive: true });
|
|
1494
|
+
await fs8.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
|
|
1311
1495
|
if (attachments.length)
|
|
1312
|
-
await
|
|
1496
|
+
await fs8.promises.mkdir(attachmentsFolder);
|
|
1313
1497
|
const movedAttachments = [];
|
|
1314
1498
|
for (const attachment of attachments) {
|
|
1315
|
-
const attachmentPath =
|
|
1499
|
+
const attachmentPath = path4.join(attachmentsFolder, attachment.id);
|
|
1316
1500
|
if (attachment.type === "file")
|
|
1317
|
-
await
|
|
1501
|
+
await fs8.promises.cp(attachment.path, attachmentPath);
|
|
1318
1502
|
else if (attachment.type === "buffer")
|
|
1319
|
-
await
|
|
1503
|
+
await fs8.promises.writeFile(attachmentPath, attachment.body);
|
|
1320
1504
|
movedAttachments.push({
|
|
1321
1505
|
type: "file",
|
|
1322
1506
|
contentType: attachment.contentType,
|
|
@@ -1330,11 +1514,10 @@ export {
|
|
|
1330
1514
|
CIUtils,
|
|
1331
1515
|
CPUUtilization,
|
|
1332
1516
|
FlakinessProjectConfig,
|
|
1333
|
-
FlakinessReport2 as FlakinessReport,
|
|
1334
1517
|
GitWorktree,
|
|
1335
1518
|
RAMUtilization,
|
|
1336
1519
|
reportUtils_exports as ReportUtils,
|
|
1337
|
-
|
|
1520
|
+
readReport,
|
|
1338
1521
|
showReport,
|
|
1339
1522
|
uploadReport,
|
|
1340
1523
|
writeReport
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flakiness/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.155.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"author": "Degu Labs, Inc",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@flakiness/flakiness-report": "^0.22.0",
|
|
32
33
|
"@types/debug": "^4.1.12",
|
|
33
34
|
"@types/node": "^25.0.3",
|
|
34
35
|
"esbuild": "^0.27.0",
|
|
@@ -36,8 +37,10 @@
|
|
|
36
37
|
"tsx": "^4.21.0",
|
|
37
38
|
"typescript": "^5.6.2"
|
|
38
39
|
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@flakiness/flakiness-report": "^0.22.0"
|
|
42
|
+
},
|
|
39
43
|
"dependencies": {
|
|
40
|
-
"@flakiness/flakiness-report": "^0.20.0",
|
|
41
44
|
"chalk": "^5.6.2",
|
|
42
45
|
"debug": "^4.4.3",
|
|
43
46
|
"open": "^10.2.0",
|
package/types/src/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { FlakinessReport, Schema } from '@flakiness/flakiness-report';
|
|
2
1
|
export { CIUtils } from './ciUtils.js';
|
|
3
2
|
export { CPUUtilization } from './cpuUtilization.js';
|
|
4
3
|
export { GitWorktree } from './gitWorktree.js';
|
|
5
4
|
export { RAMUtilization } from './ramUtilization.js';
|
|
6
5
|
export * as ReportUtils from './reportUtils.js';
|
|
6
|
+
export { readReport } from './readReport.js';
|
|
7
7
|
export { showReport } from './showReport.js';
|
|
8
8
|
export { uploadReport } from './uploadReport.js';
|
|
9
9
|
export { writeReport } from './writeReport.js';
|
package/types/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,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,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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { FlakinessReport } from '@flakiness/flakiness-report';
|
|
2
|
+
import { FileAttachment } from './uploadReport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads a Flakiness report and its attachments from a folder on disk.
|
|
5
|
+
*
|
|
6
|
+
* This function reads a Flakiness report that was previously written to disk using `writeReport()`.
|
|
7
|
+
* It parses the `report.json` file and locates all attachment files referenced in the report.
|
|
8
|
+
*
|
|
9
|
+
* The function expects the report folder to follow the standard Flakiness report structure:
|
|
10
|
+
* - `report.json` - The main report file containing test results and metadata
|
|
11
|
+
* - `attachments/` - Directory containing all attachment files, named by their ID
|
|
12
|
+
*
|
|
13
|
+
* @param {string} reportFolder - Absolute or relative path to the folder containing the Flakiness
|
|
14
|
+
* report. The folder must contain a `report.json` file. Attachments are expected to be in the
|
|
15
|
+
* `attachments/` subdirectory, but the function will work even if the attachments directory
|
|
16
|
+
* doesn't exist (all attachments will be reported as missing).
|
|
17
|
+
*
|
|
18
|
+
* @returns {Promise<{report: FlakinessReport.Report, attachments: FileAttachment[], missingAttachments: FlakinessReport.Attachment[]}>}
|
|
19
|
+
* Promise that resolves to an object containing:
|
|
20
|
+
* - `report` - The parsed Flakiness report object
|
|
21
|
+
* - `attachments` - Array of `FileAttachment` objects for attachments that were found on disk.
|
|
22
|
+
* Each attachment has:
|
|
23
|
+
* - `type: 'file'` - All returned attachments are file-based
|
|
24
|
+
* - `contentType` - MIME type from the report
|
|
25
|
+
* - `id` - Attachment ID (filename)
|
|
26
|
+
* - `path` - Absolute path to the attachment file
|
|
27
|
+
* - `missingAttachments` - Array of attachment objects from the report that could not be found
|
|
28
|
+
* on disk. This allows callers to detect and handle missing attachments gracefully.
|
|
29
|
+
*
|
|
30
|
+
* @throws {Error} Throws if `report.json` cannot be read, the folder doesn't exist, or JSON parsing fails.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const { report, attachments, missingAttachments } = await readReport('./flakiness-report');
|
|
35
|
+
*
|
|
36
|
+
* if (missingAttachments.length > 0) {
|
|
37
|
+
* console.warn(`Warning: ${missingAttachments.length} attachments are missing`);
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* // Use the report and attachments
|
|
41
|
+
* await uploadReport(report, attachments);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function readReport(reportFolder: string): Promise<{
|
|
45
|
+
report: FlakinessReport.Report;
|
|
46
|
+
attachments: FileAttachment[];
|
|
47
|
+
missingAttachments: FlakinessReport.Attachment[];
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=readReport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readReport.d.ts","sourceRoot":"","sources":["../../src/readReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAsB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9D,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC;IAC/B,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,kBAAkB,EAAE,eAAe,CAAC,UAAU,EAAE,CAAC;CAClD,CAAC,CA0CD"}
|