@flakiness/sdk 0.130.0 → 0.131.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/lib/cli/cli.js CHANGED
@@ -742,7 +742,7 @@ import path11 from "path";
742
742
  // ../package.json
743
743
  var package_default = {
744
744
  name: "flakiness",
745
- version: "0.130.0",
745
+ version: "0.131.0",
746
746
  private: true,
747
747
  scripts: {
748
748
  minor: "./version.mjs minor",
@@ -911,6 +911,29 @@ var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:
911
911
  function stripAnsi(str) {
912
912
  return str.replace(ansiRegex, "");
913
913
  }
914
+ async function saveReportAndAttachments(report, attachments, outputFolder) {
915
+ const reportPath = path.join(outputFolder, "report.json");
916
+ const attachmentsFolder = path.join(outputFolder, "attachments");
917
+ await fs.promises.rm(outputFolder, { recursive: true, force: true });
918
+ await fs.promises.mkdir(outputFolder, { recursive: true });
919
+ await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
920
+ if (attachments.length)
921
+ await fs.promises.mkdir(attachmentsFolder);
922
+ const movedAttachments = [];
923
+ for (const attachment of attachments) {
924
+ const attachmentPath = path.join(attachmentsFolder, attachment.id);
925
+ if (attachment.path)
926
+ await fs.promises.cp(attachment.path, attachmentPath);
927
+ else if (attachment.body)
928
+ await fs.promises.writeFile(attachmentPath, attachment.body);
929
+ movedAttachments.push({
930
+ contentType: attachment.contentType,
931
+ id: attachment.id,
932
+ path: attachmentPath
933
+ });
934
+ }
935
+ return movedAttachments;
936
+ }
914
937
  function shell(command, args, options) {
915
938
  try {
916
939
  const result = spawnSync(command, args, { encoding: "utf-8", ...options });
@@ -1436,21 +1459,8 @@ async function cmdConvert(junitPath, options) {
1436
1459
  runStartTimestamp: Date.now(),
1437
1460
  runDuration: 0
1438
1461
  });
1439
- await fs5.writeFile("fkreport.json", JSON.stringify(report, null, 2));
1440
- console.log("\u2713 Saved report to fkreport.json");
1441
- if (attachments.length > 0) {
1442
- await fs5.mkdir("fkattachments", { recursive: true });
1443
- for (const attachment of attachments) {
1444
- if (attachment.path) {
1445
- const destPath = path5.join("fkattachments", attachment.id);
1446
- await fs5.copyFile(attachment.path, destPath);
1447
- } else if (attachment.body) {
1448
- const destPath = path5.join("fkattachments", attachment.id);
1449
- await fs5.writeFile(destPath, attachment.body);
1450
- }
1451
- }
1452
- console.log(`\u2713 Saved ${attachments.length} attachments to fkattachments/`);
1453
- }
1462
+ await saveReportAndAttachments(report, attachments, options.outputDir);
1463
+ console.log(`\u2713 Saved to ${options.outputDir}`);
1454
1464
  }
1455
1465
  async function findXmlFiles(dir, result = []) {
1456
1466
  const entries = await fs5.readdir(dir, { withFileTypes: true });
@@ -1505,28 +1515,6 @@ async function cmdDownload(session2, project, runId) {
1505
1515
  console.log(`\u2714\uFE0F Saved as ${rootDir}`);
1506
1516
  }
1507
1517
 
1508
- // src/cli/cmd-link.ts
1509
- async function cmdLink(slug) {
1510
- const session2 = await FlakinessSession.load();
1511
- if (!session2) {
1512
- console.log(`Please login first`);
1513
- process.exit(1);
1514
- }
1515
- const [orgSlug, projectSlug] = slug.split("/");
1516
- const project = await session2.api.project.findProject.GET({
1517
- orgSlug,
1518
- projectSlug
1519
- });
1520
- if (!project) {
1521
- console.log(`Failed to find project ${slug}`);
1522
- process.exit(1);
1523
- }
1524
- const config = FlakinessConfig.createEmpty();
1525
- config.setProjectPublicId(project.projectPublicId);
1526
- await config.save();
1527
- console.log(`\u2713 Linked to ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1528
- }
1529
-
1530
1518
  // ../server/lib/common/knownClientIds.js
1531
1519
  var KNOWN_CLIENT_IDS = {
1532
1520
  OFFICIAL_WEB: "flakiness-io-official-cli",
@@ -1549,7 +1537,8 @@ async function cmdLogout() {
1549
1537
  }
1550
1538
 
1551
1539
  // src/cli/cmd-login.ts
1552
- async function cmdLogin(endpoint) {
1540
+ var DEFAULT_FLAKINESS_ENDPOINT = "https://flakiness.io";
1541
+ async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
1553
1542
  await cmdLogout();
1554
1543
  const api = createServerAPI(endpoint);
1555
1544
  const data = await api.deviceauth.createRequest.POST({
@@ -1563,8 +1552,8 @@ async function cmdLogin(endpoint) {
1563
1552
  await new Promise((x) => setTimeout(x, 2e3));
1564
1553
  const result = await api.deviceauth.getToken.GET({ deviceCode: data.deviceCode }).catch((e) => void 0);
1565
1554
  if (!result) {
1566
- console.log(`Authorization request was rejected.`);
1567
- return;
1555
+ console.error(`Authorization request was rejected.`);
1556
+ process.exit(1);
1568
1557
  }
1569
1558
  token = result.token;
1570
1559
  if (token)
@@ -1586,6 +1575,41 @@ async function cmdLogin(endpoint) {
1586
1575
  const message = e instanceof Error ? e.message : String(e);
1587
1576
  console.error(`x Failed to login:`, message);
1588
1577
  }
1578
+ return session2;
1579
+ }
1580
+
1581
+ // src/cli/cmd-link.ts
1582
+ async function cmdLink(slugOrUrl) {
1583
+ let slug = slugOrUrl;
1584
+ let endpoint = DEFAULT_FLAKINESS_ENDPOINT;
1585
+ if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
1586
+ const url = URL.parse(slugOrUrl);
1587
+ if (!url) {
1588
+ console.error(`Invalid URL: ${slugOrUrl}`);
1589
+ process.exit(1);
1590
+ }
1591
+ slug = url.pathname.substring(1);
1592
+ endpoint = url.origin;
1593
+ } else if (slugOrUrl.startsWith("flakiness.io/")) {
1594
+ endpoint = "https://flakiness.io";
1595
+ slug = slugOrUrl.substring("flakiness.io/".length);
1596
+ }
1597
+ let session2 = await FlakinessSession.load();
1598
+ if (!session2 || session2.endpoint() !== endpoint)
1599
+ session2 = await cmdLogin(endpoint);
1600
+ const [orgSlug, projectSlug] = slug.split("/");
1601
+ const project = await session2.api.project.findProject.GET({
1602
+ orgSlug,
1603
+ projectSlug
1604
+ });
1605
+ if (!project) {
1606
+ console.log(`Failed to find project ${slug}`);
1607
+ process.exit(1);
1608
+ }
1609
+ const config = FlakinessConfig.createEmpty();
1610
+ config.setProjectPublicId(project.projectPublicId);
1611
+ await config.save();
1612
+ console.log(`\u2713 Linked to ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1589
1613
  }
1590
1614
 
1591
1615
  // src/cli/cmd-show-report.ts
@@ -2221,7 +2245,7 @@ async function cmdWhoami() {
2221
2245
  // src/cli/cli.ts
2222
2246
  var session = await FlakinessSession.load();
2223
2247
  var optAccessToken = new Option("-t, --access-token <token>", "A read-write flakiness.io access token").env("FLAKINESS_ACCESS_TOKEN");
2224
- var optEndpoint = new Option("-e, --endpoint <url>", "An endpoint where the service is deployed").default(session?.endpoint() ?? "https://flakiness.io").env("FLAKINESS_ENDPOINT");
2248
+ var optEndpoint = new Option("-e, --endpoint <url>", "An endpoint where the service is deployed").default(session?.endpoint() ?? DEFAULT_FLAKINESS_ENDPOINT).env("FLAKINESS_ENDPOINT");
2225
2249
  var optAttachmentsDir = new Option("--attachments-dir <dir>", "Directory containing attachments to upload. Defaults to the report directory");
2226
2250
  async function runCommand(callback) {
2227
2251
  try {
@@ -2260,7 +2284,7 @@ program.command("upload-playwright-json", { hidden: true }).description("Upload
2260
2284
  await cmdUploadPlaywrightJson(relativePath, await ensureAccessToken(options));
2261
2285
  }));
2262
2286
  var optLink = new Option("--link <org/proj>", "A project to link to");
2263
- program.command("login").description("Login to the flakiness.io service").addOption(optEndpoint).addOption(optLink).action(async (options) => runCommand(async () => {
2287
+ program.command("login").description("Login to the Flakiness.io service").addOption(optEndpoint).addOption(optLink).action(async (options) => runCommand(async () => {
2264
2288
  await cmdLogin(options.endpoint);
2265
2289
  if (options.link)
2266
2290
  await cmdLink(options.link);
@@ -2271,11 +2295,11 @@ program.command("logout").description("Logout from current session").action(asyn
2271
2295
  program.command("whoami").description("Show current logged in user information").action(async () => runCommand(async () => {
2272
2296
  await cmdWhoami();
2273
2297
  }));
2274
- program.command("link").description("Link repository to the flakiness project").addOption(optEndpoint).argument("org/project", 'An org and project slugs, e.g. "facebook/react"').action(async (slug, options) => runCommand(async () => {
2298
+ 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 () => {
2275
2299
  const session2 = await FlakinessSession.load();
2276
2300
  if (!session2)
2277
2301
  await cmdLogin(options.endpoint);
2278
- await cmdLink(slug);
2302
+ await cmdLink(slugOrUrl);
2279
2303
  }));
2280
2304
  program.command("unlink").description("Unlink repository from the flakiness project").action(async () => runCommand(async () => {
2281
2305
  await cmdUnlink();
@@ -2333,11 +2357,11 @@ program.command("upload").description("Upload Flakiness report to the flakiness.
2333
2357
  await cmdUpload(relativePath, await ensureAccessToken(options));
2334
2358
  });
2335
2359
  });
2336
- program.command("show-report [report]").description("Show flakiness report").argument("[relative-path]", "Path to the Flakiness report file or folder that contains `report.json`.").action(async (arg) => runCommand(async () => {
2360
+ 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 () => {
2337
2361
  const dir = path11.resolve(arg ?? "flakiness-report");
2338
2362
  await cmdShowReport(dir);
2339
2363
  }));
2340
- 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", "default").option("--commit-id <id>", "Git commit ID (auto-detected if not provided)").action(async (junitPath, options) => {
2364
+ 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) => {
2341
2365
  await runCommand(async () => {
2342
2366
  await cmdConvert(junitPath, options);
2343
2367
  });
@@ -2,14 +2,14 @@
2
2
 
3
3
  // src/cli/cmd-convert.ts
4
4
  import fs3 from "fs/promises";
5
- import path2 from "path";
5
+ import path3 from "path";
6
6
 
7
7
  // src/junit.ts
8
8
  import { ReportUtils as ReportUtils2 } from "@flakiness/report";
9
9
  import { parseXml, XmlElement, XmlText } from "@rgrove/parse-xml";
10
10
  import assert2 from "assert";
11
11
  import fs2 from "fs";
12
- import path from "path";
12
+ import path2 from "path";
13
13
 
14
14
  // src/utils.ts
15
15
  import { ReportUtils } from "@flakiness/report";
@@ -19,6 +19,7 @@ import crypto from "crypto";
19
19
  import fs from "fs";
20
20
  import http from "http";
21
21
  import https from "https";
22
+ import path, { posix as posixPath, win32 as win32Path } from "path";
22
23
  function sha1File(filePath) {
23
24
  return new Promise((resolve, reject) => {
24
25
  const hash = crypto.createHash("sha1");
@@ -122,6 +123,29 @@ var httpUtils;
122
123
  httpUtils2.postJSON = postJSON;
123
124
  })(httpUtils || (httpUtils = {}));
124
125
  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");
126
+ async function saveReportAndAttachments(report, attachments, outputFolder) {
127
+ const reportPath = path.join(outputFolder, "report.json");
128
+ const attachmentsFolder = path.join(outputFolder, "attachments");
129
+ await fs.promises.rm(outputFolder, { recursive: true, force: true });
130
+ await fs.promises.mkdir(outputFolder, { recursive: true });
131
+ await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
132
+ if (attachments.length)
133
+ await fs.promises.mkdir(attachmentsFolder);
134
+ const movedAttachments = [];
135
+ for (const attachment of attachments) {
136
+ const attachmentPath = path.join(attachmentsFolder, attachment.id);
137
+ if (attachment.path)
138
+ await fs.promises.cp(attachment.path, attachmentPath);
139
+ else if (attachment.body)
140
+ await fs.promises.writeFile(attachmentPath, attachment.body);
141
+ movedAttachments.push({
142
+ contentType: attachment.contentType,
143
+ id: attachment.id,
144
+ path: attachmentPath
145
+ });
146
+ }
147
+ return movedAttachments;
148
+ }
125
149
  function shell(command, args, options) {
126
150
  try {
127
151
  const result = spawnSync(command, args, { encoding: "utf-8", ...options });
@@ -187,7 +211,7 @@ function extractStdout(testcase, stdio) {
187
211
  }));
188
212
  }
189
213
  async function parseAttachment(value) {
190
- let absolutePath = path.resolve(process.cwd(), value);
214
+ let absolutePath = path2.resolve(process.cwd(), value);
191
215
  if (fs2.existsSync(absolutePath)) {
192
216
  const id = await sha1File(absolutePath);
193
217
  return {
@@ -260,7 +284,7 @@ async function traverseJUnitReport(context, node) {
260
284
  id: attachment.id,
261
285
  contentType: attachment.contentType,
262
286
  //TODO: better default names for attachments?
263
- name: attachment.path ? path.basename(attachment.path) : `attachment`
287
+ name: attachment.path ? path2.basename(attachment.path) : `attachment`
264
288
  });
265
289
  } else {
266
290
  annotations.push({
@@ -335,7 +359,7 @@ async function parseJUnit(xmls, options) {
335
359
 
336
360
  // src/cli/cmd-convert.ts
337
361
  async function cmdConvert(junitPath, options) {
338
- const fullPath = path2.resolve(junitPath);
362
+ const fullPath = path3.resolve(junitPath);
339
363
  if (!await fs3.access(fullPath, fs3.constants.F_OK).then(() => true).catch(() => false)) {
340
364
  console.error(`Error: path ${fullPath} is not accessible`);
341
365
  process.exit(1);
@@ -377,26 +401,13 @@ async function cmdConvert(junitPath, options) {
377
401
  runStartTimestamp: Date.now(),
378
402
  runDuration: 0
379
403
  });
380
- await fs3.writeFile("fkreport.json", JSON.stringify(report, null, 2));
381
- console.log("\u2713 Saved report to fkreport.json");
382
- if (attachments.length > 0) {
383
- await fs3.mkdir("fkattachments", { recursive: true });
384
- for (const attachment of attachments) {
385
- if (attachment.path) {
386
- const destPath = path2.join("fkattachments", attachment.id);
387
- await fs3.copyFile(attachment.path, destPath);
388
- } else if (attachment.body) {
389
- const destPath = path2.join("fkattachments", attachment.id);
390
- await fs3.writeFile(destPath, attachment.body);
391
- }
392
- }
393
- console.log(`\u2713 Saved ${attachments.length} attachments to fkattachments/`);
394
- }
404
+ await saveReportAndAttachments(report, attachments, options.outputDir);
405
+ console.log(`\u2713 Saved to ${options.outputDir}`);
395
406
  }
396
407
  async function findXmlFiles(dir, result = []) {
397
408
  const entries = await fs3.readdir(dir, { withFileTypes: true });
398
409
  for (const entry of entries) {
399
- const fullPath = path2.join(dir, entry.name);
410
+ const fullPath = path3.join(dir, entry.name);
400
411
  if (entry.isFile() && entry.name.toLowerCase().endsWith(".xml"))
401
412
  result.push(fullPath);
402
413
  else if (entry.isDirectory())
@@ -249,13 +249,88 @@ var FlakinessSession = class _FlakinessSession {
249
249
  }
250
250
  };
251
251
 
252
- // src/cli/cmd-link.ts
253
- async function cmdLink(slug) {
252
+ // ../server/lib/common/knownClientIds.js
253
+ var KNOWN_CLIENT_IDS = {
254
+ OFFICIAL_WEB: "flakiness-io-official-cli",
255
+ OFFICIAL_CLI: "flakiness-io-official-website"
256
+ };
257
+
258
+ // src/cli/cmd-login.ts
259
+ import open from "open";
260
+ import os2 from "os";
261
+
262
+ // src/cli/cmd-logout.ts
263
+ async function cmdLogout() {
254
264
  const session = await FlakinessSession.load();
255
- if (!session) {
256
- console.log(`Please login first`);
265
+ if (!session)
266
+ return;
267
+ const currentSession = await session.api.user.currentSession.GET().catch((e) => void 0);
268
+ if (currentSession)
269
+ await session.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId });
270
+ await FlakinessSession.remove();
271
+ }
272
+
273
+ // src/cli/cmd-login.ts
274
+ var DEFAULT_FLAKINESS_ENDPOINT = "https://flakiness.io";
275
+ async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
276
+ await cmdLogout();
277
+ const api = createServerAPI(endpoint);
278
+ const data = await api.deviceauth.createRequest.POST({
279
+ clientId: KNOWN_CLIENT_IDS.OFFICIAL_CLI,
280
+ name: os2.hostname()
281
+ });
282
+ await open(new URL(data.verificationUrl, endpoint).href);
283
+ console.log(`Please navigate to ${new URL(data.verificationUrl, endpoint)}`);
284
+ let token;
285
+ while (Date.now() < data.deadline) {
286
+ await new Promise((x) => setTimeout(x, 2e3));
287
+ const result = await api.deviceauth.getToken.GET({ deviceCode: data.deviceCode }).catch((e) => void 0);
288
+ if (!result) {
289
+ console.error(`Authorization request was rejected.`);
290
+ process.exit(1);
291
+ }
292
+ token = result.token;
293
+ if (token)
294
+ break;
295
+ }
296
+ if (!token) {
297
+ console.log(`Failed to login.`);
257
298
  process.exit(1);
258
299
  }
300
+ const session = new FlakinessSession({
301
+ endpoint,
302
+ token
303
+ });
304
+ try {
305
+ const user = await session.api.user.whoami.GET();
306
+ await session.save();
307
+ console.log(`\u2713 Logged in as ${user.userName} (${user.userLogin})`);
308
+ } catch (e) {
309
+ const message = e instanceof Error ? e.message : String(e);
310
+ console.error(`x Failed to login:`, message);
311
+ }
312
+ return session;
313
+ }
314
+
315
+ // src/cli/cmd-link.ts
316
+ async function cmdLink(slugOrUrl) {
317
+ let slug = slugOrUrl;
318
+ let endpoint = DEFAULT_FLAKINESS_ENDPOINT;
319
+ if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
320
+ const url = URL.parse(slugOrUrl);
321
+ if (!url) {
322
+ console.error(`Invalid URL: ${slugOrUrl}`);
323
+ process.exit(1);
324
+ }
325
+ slug = url.pathname.substring(1);
326
+ endpoint = url.origin;
327
+ } else if (slugOrUrl.startsWith("flakiness.io/")) {
328
+ endpoint = "https://flakiness.io";
329
+ slug = slugOrUrl.substring("flakiness.io/".length);
330
+ }
331
+ let session = await FlakinessSession.load();
332
+ if (!session || session.endpoint() !== endpoint)
333
+ session = await cmdLogin(endpoint);
259
334
  const [orgSlug, projectSlug] = slug.split("/");
260
335
  const project = await session.api.project.findProject.GET({
261
336
  orgSlug,
@@ -176,7 +176,8 @@ async function cmdLogout() {
176
176
  }
177
177
 
178
178
  // src/cli/cmd-login.ts
179
- async function cmdLogin(endpoint) {
179
+ var DEFAULT_FLAKINESS_ENDPOINT = "https://flakiness.io";
180
+ async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
180
181
  await cmdLogout();
181
182
  const api = createServerAPI(endpoint);
182
183
  const data = await api.deviceauth.createRequest.POST({
@@ -190,8 +191,8 @@ async function cmdLogin(endpoint) {
190
191
  await new Promise((x) => setTimeout(x, 2e3));
191
192
  const result = await api.deviceauth.getToken.GET({ deviceCode: data.deviceCode }).catch((e) => void 0);
192
193
  if (!result) {
193
- console.log(`Authorization request was rejected.`);
194
- return;
194
+ console.error(`Authorization request was rejected.`);
195
+ process.exit(1);
195
196
  }
196
197
  token = result.token;
197
198
  if (token)
@@ -213,8 +214,10 @@ async function cmdLogin(endpoint) {
213
214
  const message = e instanceof Error ? e.message : String(e);
214
215
  console.error(`x Failed to login:`, message);
215
216
  }
217
+ return session;
216
218
  }
217
219
  export {
220
+ DEFAULT_FLAKINESS_ENDPOINT,
218
221
  cmdLogin
219
222
  };
220
223
  //# sourceMappingURL=cmd-login.js.map
@@ -139,6 +139,29 @@ var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:
139
139
  function stripAnsi(str) {
140
140
  return str.replace(ansiRegex, "");
141
141
  }
142
+ async function saveReportAndAttachments(report, attachments, outputFolder) {
143
+ const reportPath = path.join(outputFolder, "report.json");
144
+ const attachmentsFolder = path.join(outputFolder, "attachments");
145
+ await fs.promises.rm(outputFolder, { recursive: true, force: true });
146
+ await fs.promises.mkdir(outputFolder, { recursive: true });
147
+ await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
148
+ if (attachments.length)
149
+ await fs.promises.mkdir(attachmentsFolder);
150
+ const movedAttachments = [];
151
+ for (const attachment of attachments) {
152
+ const attachmentPath = path.join(attachmentsFolder, attachment.id);
153
+ if (attachment.path)
154
+ await fs.promises.cp(attachment.path, attachmentPath);
155
+ else if (attachment.body)
156
+ await fs.promises.writeFile(attachmentPath, attachment.body);
157
+ movedAttachments.push({
158
+ contentType: attachment.contentType,
159
+ id: attachment.id,
160
+ path: attachmentPath
161
+ });
162
+ }
163
+ return movedAttachments;
164
+ }
142
165
  function shell(command, args, options) {
143
166
  try {
144
167
  const result = spawnSync(command, args, { encoding: "utf-8", ...options });
@@ -1072,25 +1095,7 @@ var FlakinessReporter = class {
1072
1095
  for (const unaccessibleAttachment of context.unaccessibleAttachmentPaths)
1073
1096
  warn(`cannot access attachment ${unaccessibleAttachment}`);
1074
1097
  this._report = report;
1075
- const reportPath = path6.join(this._outputFolder, "report.json");
1076
- const attachmentsFolder = path6.join(this._outputFolder, "attachments");
1077
- await fs7.promises.rm(this._outputFolder, { recursive: true, force: true });
1078
- await fs7.promises.mkdir(this._outputFolder, { recursive: true });
1079
- await fs7.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
1080
- if (context.attachments.size)
1081
- await fs7.promises.mkdir(attachmentsFolder);
1082
- for (const attachment of context.attachments.values()) {
1083
- const attachmentPath = path6.join(attachmentsFolder, attachment.id);
1084
- if (attachment.path)
1085
- await fs7.promises.cp(attachment.path, attachmentPath);
1086
- else if (attachment.body)
1087
- await fs7.promises.writeFile(attachmentPath, attachment.body);
1088
- this._attachments.push({
1089
- contentType: attachment.contentType,
1090
- id: attachment.id,
1091
- path: attachmentPath
1092
- });
1093
- }
1098
+ this._attachments = await saveReportAndAttachments(report, Array.from(context.attachments.values()), this._outputFolder);
1094
1099
  this._result = result;
1095
1100
  }
1096
1101
  async onExit() {
@@ -1111,9 +1116,9 @@ var FlakinessReporter = class {
1111
1116
  const defaultOutputFolder = path6.join(process.cwd(), "flakiness-report");
1112
1117
  const folder = defaultOutputFolder === this._outputFolder ? "" : path6.relative(process.cwd(), this._outputFolder);
1113
1118
  console.log(`
1114
- To open last Flakiness report run:
1119
+ To open last Flakiness report, install Flakiness CLI tool and run:
1115
1120
 
1116
- ${chalk2.cyan(`npx flakiness show-report ${folder}`)}
1121
+ ${chalk2.cyan(`flakiness show ${folder}`)}
1117
1122
  `);
1118
1123
  }
1119
1124
  }
package/lib/utils.js CHANGED
@@ -123,6 +123,29 @@ var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:
123
123
  function stripAnsi(str) {
124
124
  return str.replace(ansiRegex, "");
125
125
  }
126
+ async function saveReportAndAttachments(report, attachments, outputFolder) {
127
+ const reportPath = path.join(outputFolder, "report.json");
128
+ const attachmentsFolder = path.join(outputFolder, "attachments");
129
+ await fs.promises.rm(outputFolder, { recursive: true, force: true });
130
+ await fs.promises.mkdir(outputFolder, { recursive: true });
131
+ await fs.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
132
+ if (attachments.length)
133
+ await fs.promises.mkdir(attachmentsFolder);
134
+ const movedAttachments = [];
135
+ for (const attachment of attachments) {
136
+ const attachmentPath = path.join(attachmentsFolder, attachment.id);
137
+ if (attachment.path)
138
+ await fs.promises.cp(attachment.path, attachmentPath);
139
+ else if (attachment.body)
140
+ await fs.promises.writeFile(attachmentPath, attachment.body);
141
+ movedAttachments.push({
142
+ contentType: attachment.contentType,
143
+ id: attachment.id,
144
+ path: attachmentPath
145
+ });
146
+ }
147
+ return movedAttachments;
148
+ }
126
149
  function shell(command, args, options) {
127
150
  try {
128
151
  const result = spawnSync(command, args, { encoding: "utf-8", ...options });
@@ -342,6 +365,7 @@ export {
342
365
  parseStringDate,
343
366
  resolveAttachmentPaths,
344
367
  retryWithBackoff,
368
+ saveReportAndAttachments,
345
369
  sha1Buffer,
346
370
  sha1File,
347
371
  shell,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flakiness/sdk",
3
- "version": "0.130.0",
3
+ "version": "0.131.0",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "flakiness": "./lib/cli/cli.js"
@@ -50,7 +50,7 @@
50
50
  "author": "Degu Labs, Inc",
51
51
  "license": "Fair Source 100",
52
52
  "devDependencies": {
53
- "@flakiness/server": "0.130.0",
53
+ "@flakiness/server": "0.131.0",
54
54
  "@playwright/test": "^1.54.0",
55
55
  "@types/babel__code-frame": "^7.0.6",
56
56
  "@types/compression": "^1.8.1",
@@ -58,8 +58,8 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@babel/code-frame": "^7.26.2",
61
- "@flakiness/report": "0.130.0",
62
- "@flakiness/shared": "0.130.0",
61
+ "@flakiness/report": "0.131.0",
62
+ "@flakiness/shared": "0.131.0",
63
63
  "@rgrove/parse-xml": "^4.2.0",
64
64
  "body-parser": "^1.20.3",
65
65
  "chalk": "^5.6.2",