@flakiness/sdk 0.154.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 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
- // src/browser.ts
8
- import { FlakinessReport as FlakinessReport2 } from "@flakiness/flakiness-report";
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 { Schema } from "@flakiness/flakiness-report";
179
- import z from "zod/v4";
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 = [z.prettifyError(new z.ZodError(shownIssues))];
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
- FlakinessReport2 as FlakinessReport,
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
 
781
- // src/validateReport.ts
782
- import { Schema } from "@flakiness/flakiness-report";
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
783
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
+
912
+ // src/validateReport.ts
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 = [z.prettifyError(new z.ZodError(shownIssues))];
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 fs5 from "fs";
997
- import path from "path";
1180
+ import fs6 from "fs";
1181
+ import path2 from "path";
998
1182
  function createConfigPath(dir) {
999
- return path.join(dir, ".flakiness", "config.json");
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 !== path.resolve(p, ".."); p = path.resolve(p, "..")) {
1192
+ for (let p = process.cwd(); p !== path2.resolve(p, ".."); p = path2.resolve(p, "..")) {
1009
1193
  const configPath = createConfigPath(p);
1010
- if (fs5.existsSync(configPath))
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 fs5.promises.readFile(configPath, "utf-8").catch((e) => void 0);
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 fs5.promises.mkdir(path.dirname(this._configPath), { recursive: true });
1131
- await fs5.promises.writeFile(this._configPath, JSON.stringify(this._config, null, 2));
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 fs6 from "fs";
1321
+ import * as fs7 from "fs";
1138
1322
  import * as http from "http";
1139
- import * as path2 from "path";
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 = path2.resolve(folderPath);
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 = path2.normalize(decodeURIComponent(relativePath)).replace(/^(\.\.[\/\\])+/, "");
1257
- const filePath = path2.join(this._absoluteFolderPath, safeSuffix);
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
- fs6.stat(filePath, (err, stats) => {
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 = path2.extname(filePath).toLowerCase();
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 = fs6.createReadStream(filePath);
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 fs7 from "fs";
1304
- import path3 from "path";
1487
+ import fs8 from "fs";
1488
+ import path4 from "path";
1305
1489
  async function writeReport(report, attachments, outputFolder) {
1306
- const reportPath = path3.join(outputFolder, "report.json");
1307
- const attachmentsFolder = path3.join(outputFolder, "attachments");
1308
- await fs7.promises.rm(outputFolder, { recursive: true, force: true });
1309
- await fs7.promises.mkdir(outputFolder, { recursive: true });
1310
- await fs7.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
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 fs7.promises.mkdir(attachmentsFolder);
1496
+ await fs8.promises.mkdir(attachmentsFolder);
1313
1497
  const movedAttachments = [];
1314
1498
  for (const attachment of attachments) {
1315
- const attachmentPath = path3.join(attachmentsFolder, attachment.id);
1499
+ const attachmentPath = path4.join(attachmentsFolder, attachment.id);
1316
1500
  if (attachment.type === "file")
1317
- await fs7.promises.cp(attachment.path, attachmentPath);
1501
+ await fs8.promises.cp(attachment.path, attachmentPath);
1318
1502
  else if (attachment.type === "buffer")
1319
- await fs7.promises.writeFile(attachmentPath, attachment.body);
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
- Schema2 as Schema,
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.154.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.21.0",
41
44
  "chalk": "^5.6.2",
42
45
  "debug": "^4.4.3",
43
46
  "open": "^10.2.0",
@@ -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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAGtE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"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"}