flakiness 0.219.0 → 0.221.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
@@ -1021,14 +1021,16 @@ var WireTypes;
1021
1021
  })(WireTypes || (WireTypes = {}));
1022
1022
 
1023
1023
  // src/cli/cli.ts
1024
+ import chalk5 from "chalk";
1024
1025
  import { Command, Option } from "commander";
1025
1026
  import debug2 from "debug";
1027
+ import fs7 from "fs";
1026
1028
  import path7 from "path";
1027
1029
 
1028
1030
  // ../package.json
1029
1031
  var package_default = {
1030
1032
  name: "@flakiness/monorepo",
1031
- version: "0.219.0",
1033
+ version: "0.221.0",
1032
1034
  type: "module",
1033
1035
  private: true,
1034
1036
  scripts: {
@@ -1519,6 +1521,35 @@ import assert from "assert";
1519
1521
  import fs2 from "fs";
1520
1522
  import mime from "mime";
1521
1523
  import path2 from "path";
1524
+ import { Temporal } from "temporal-polyfill";
1525
+ var gTZAbbreviationToIANATimezone;
1526
+ function tzAbbreviationToIANA(tz) {
1527
+ if (!gTZAbbreviationToIANATimezone) {
1528
+ gTZAbbreviationToIANATimezone = /* @__PURE__ */ new Map();
1529
+ const probes = [/* @__PURE__ */ new Date("2026-06-15T12:00:00Z"), /* @__PURE__ */ new Date("2026-01-15T12:00:00Z")];
1530
+ for (const tz2 of Intl.supportedValuesOf("timeZone")) {
1531
+ for (const date of probes) {
1532
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone: tz2, timeZoneName: "short" }).formatToParts(date);
1533
+ const abbr = parts.find((p) => p.type === "timeZoneName")?.value;
1534
+ if (abbr)
1535
+ gTZAbbreviationToIANATimezone.set(abbr, tz2);
1536
+ }
1537
+ }
1538
+ }
1539
+ return gTZAbbreviationToIANATimezone.get(tz);
1540
+ }
1541
+ function parseTimestamp(timestamp) {
1542
+ const native = new Date(timestamp).getTime();
1543
+ if (!isNaN(native))
1544
+ return native;
1545
+ const parts = timestamp.split(/\s+/);
1546
+ const iana = parts.length === 2 ? tzAbbreviationToIANA(parts[1]) : void 0;
1547
+ if (iana) {
1548
+ const d = Temporal.PlainDateTime.from(parts[0]);
1549
+ return d.toZonedDateTime(iana).epochMilliseconds;
1550
+ }
1551
+ throw new Error(`failed to parse timestamp: ${timestamp}`);
1552
+ }
1522
1553
  function getProperties(element) {
1523
1554
  const propertiesNodes = element.children.filter((node) => node instanceof XmlElement).filter((node) => node.name === "properties");
1524
1555
  if (!propertiesNodes.length)
@@ -1583,7 +1614,7 @@ async function traverseJUnitReport(context, node) {
1583
1614
  return;
1584
1615
  let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs } = context;
1585
1616
  if (element.attributes["timestamp"])
1586
- currentTimeMs = new Date(element.attributes["timestamp"]).getTime();
1617
+ currentTimeMs = parseTimestamp(element.attributes["timestamp"]);
1587
1618
  if (element.name === "testsuite") {
1588
1619
  const file = element.attributes["file"];
1589
1620
  const line = parseInt(element.attributes["line"], 10);
@@ -1607,12 +1638,12 @@ async function traverseJUnitReport(context, node) {
1607
1638
  report.suites.push(newSuite);
1608
1639
  }
1609
1640
  currentSuite = newSuite;
1610
- const userSuppliedData = getProperties(element);
1611
- if (userSuppliedData.length) {
1641
+ const metadata = getProperties(element);
1642
+ if (metadata.length) {
1612
1643
  currentEnv = structuredClone(currentEnv);
1613
- currentEnv.userSuppliedData ??= {};
1614
- for (const [key, value] of userSuppliedData)
1615
- currentEnv.userSuppliedData[key] = value;
1644
+ currentEnv.metadata ??= {};
1645
+ for (const [key, value] of metadata)
1646
+ currentEnv.metadata[key] = value;
1616
1647
  currentEnvIndex = report.environments.push(currentEnv) - 1;
1617
1648
  }
1618
1649
  } else if (element.name === "testcase") {
@@ -2192,7 +2223,7 @@ var warn = (txt) => console.warn(chalk4.yellow(`[flakiness.io] WARN: ${txt}`));
2192
2223
  var err = (txt) => console.error(chalk4.red(`[flakiness.io] Error: ${txt}`));
2193
2224
  async function cmdUpload(relativePaths, options) {
2194
2225
  const total = relativePaths.length;
2195
- const spinner = total > 1 ? ora2("Uploading reports:").start() : void 0;
2226
+ const spinner = options.progress ? ora2("Uploading reports:").start() : void 0;
2196
2227
  let uploaded = 0;
2197
2228
  let sharedAuth;
2198
2229
  async function ensureAuth(flakinessProject) {
@@ -2223,12 +2254,12 @@ async function cmdUpload(relativePaths, options) {
2223
2254
  const status = await uploadReport(report, attachments, {
2224
2255
  flakinessAccessToken: auth2.accessToken,
2225
2256
  flakinessEndpoint: auth2.endpoint,
2226
- logger: {
2257
+ logger: spinner ? {
2227
2258
  log: () => {
2228
2259
  },
2229
2260
  error: console.error,
2230
2261
  warn: console.warn
2231
- }
2262
+ } : void 0
2232
2263
  });
2233
2264
  if (status.status === "failed" || status.status === "skipped") {
2234
2265
  spinner?.stop();
@@ -2238,7 +2269,9 @@ async function cmdUpload(relativePaths, options) {
2238
2269
  if (spinner)
2239
2270
  spinner.text = `Uploaded ${Math.floor(uploaded / total * 100)}% [${uploaded}/${total}] reports`;
2240
2271
  }
2241
- spinner?.stop();
2272
+ if (spinner) {
2273
+ spinner.succeed(`Uploaded ${total} report${total === 1 ? "" : "s"}`);
2274
+ }
2242
2275
  }
2243
2276
 
2244
2277
  // src/cli/cli.ts
@@ -2371,18 +2404,23 @@ program.command("download").description("Download run").addOption(mustFlakinessP
2371
2404
  accessToken: options.accessToken
2372
2405
  });
2373
2406
  }));
2374
- program.command("upload").description("Upload Flakiness report to the flakiness.io service").argument("<relative-paths...>", "Paths to Flakiness report files or directories").addOption(optAccessToken).addOption(optFlakinessProject).addOption(optEndpoint).action(async (relativePaths, options) => {
2407
+ program.command("upload").description("Upload Flakiness report to the flakiness.io service").argument("<relative-paths...>", "Paths to Flakiness report files or directories").addOption(optAccessToken).addOption(optFlakinessProject).addOption(optEndpoint).option("--progress", "Show upload progress spinner instead of printing run URLs").action(async (relativePaths, options) => {
2375
2408
  await runCommand(async () => {
2376
2409
  await cmdUpload(relativePaths, {
2377
2410
  endpoint: options.endpoint,
2378
2411
  accessToken: options.accessToken,
2379
- flakinessProject: options.project
2412
+ flakinessProject: options.project,
2413
+ progress: options.progress
2380
2414
  });
2381
2415
  });
2382
2416
  });
2383
2417
  var optViewerUrl = new Option("-e, --viewer-url <url>", "A URL where report viewer is deployed").default("https://report.flakiness.io").env("FLAKINESS_ENDPOINT");
2384
- 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)").addOption(optViewerUrl).action(async (arg, options) => runCommand(async () => {
2418
+ program.command("show").description("Show flakiness report").argument("[relative-path]", "Path to the folder that contains `report.json`", "flakiness-report").addOption(optViewerUrl).action(async (arg, options) => runCommand(async () => {
2385
2419
  const dir = path7.resolve(arg ?? "flakiness-report");
2420
+ if (!fs7.existsSync(dir))
2421
+ throw new Error(`Directory ${chalk5.bold(dir)} does not exist`);
2422
+ if (!fs7.existsSync(path7.join(dir, "report.json")))
2423
+ throw new Error(`The folder ${chalk5.bold(dir)} does not contain report.json - is this a Flakiness report folder?`);
2386
2424
  await showReport(dir, {
2387
2425
  reportViewerUrl: options.viewerUrl
2388
2426
  });
package/lib/junit.js CHANGED
@@ -6,6 +6,35 @@ import assert from "assert";
6
6
  import fs from "fs";
7
7
  import mime from "mime";
8
8
  import path from "path";
9
+ import { Temporal } from "temporal-polyfill";
10
+ var gTZAbbreviationToIANATimezone;
11
+ function tzAbbreviationToIANA(tz) {
12
+ if (!gTZAbbreviationToIANATimezone) {
13
+ gTZAbbreviationToIANATimezone = /* @__PURE__ */ new Map();
14
+ const probes = [/* @__PURE__ */ new Date("2026-06-15T12:00:00Z"), /* @__PURE__ */ new Date("2026-01-15T12:00:00Z")];
15
+ for (const tz2 of Intl.supportedValuesOf("timeZone")) {
16
+ for (const date of probes) {
17
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone: tz2, timeZoneName: "short" }).formatToParts(date);
18
+ const abbr = parts.find((p) => p.type === "timeZoneName")?.value;
19
+ if (abbr)
20
+ gTZAbbreviationToIANATimezone.set(abbr, tz2);
21
+ }
22
+ }
23
+ }
24
+ return gTZAbbreviationToIANATimezone.get(tz);
25
+ }
26
+ function parseTimestamp(timestamp) {
27
+ const native = new Date(timestamp).getTime();
28
+ if (!isNaN(native))
29
+ return native;
30
+ const parts = timestamp.split(/\s+/);
31
+ const iana = parts.length === 2 ? tzAbbreviationToIANA(parts[1]) : void 0;
32
+ if (iana) {
33
+ const d = Temporal.PlainDateTime.from(parts[0]);
34
+ return d.toZonedDateTime(iana).epochMilliseconds;
35
+ }
36
+ throw new Error(`failed to parse timestamp: ${timestamp}`);
37
+ }
9
38
  function getProperties(element) {
10
39
  const propertiesNodes = element.children.filter((node) => node instanceof XmlElement).filter((node) => node.name === "properties");
11
40
  if (!propertiesNodes.length)
@@ -70,7 +99,7 @@ async function traverseJUnitReport(context, node) {
70
99
  return;
71
100
  let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs } = context;
72
101
  if (element.attributes["timestamp"])
73
- currentTimeMs = new Date(element.attributes["timestamp"]).getTime();
102
+ currentTimeMs = parseTimestamp(element.attributes["timestamp"]);
74
103
  if (element.name === "testsuite") {
75
104
  const file = element.attributes["file"];
76
105
  const line = parseInt(element.attributes["line"], 10);
@@ -94,12 +123,12 @@ async function traverseJUnitReport(context, node) {
94
123
  report.suites.push(newSuite);
95
124
  }
96
125
  currentSuite = newSuite;
97
- const userSuppliedData = getProperties(element);
98
- if (userSuppliedData.length) {
126
+ const metadata = getProperties(element);
127
+ if (metadata.length) {
99
128
  currentEnv = structuredClone(currentEnv);
100
- currentEnv.userSuppliedData ??= {};
101
- for (const [key, value] of userSuppliedData)
102
- currentEnv.userSuppliedData[key] = value;
129
+ currentEnv.metadata ??= {};
130
+ for (const [key, value] of metadata)
131
+ currentEnv.metadata[key] = value;
103
132
  currentEnvIndex = report.environments.push(currentEnv) - 1;
104
133
  }
105
134
  } else if (element.name === "testcase") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flakiness",
3
- "version": "0.219.0",
3
+ "version": "0.221.0",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "flakiness": "./lib/cli/cli.js"
@@ -23,8 +23,8 @@
23
23
  "@types/debug": "^4.1.12",
24
24
  "@types/express": "^4.17.20",
25
25
  "gray-matter": "^4.0.3",
26
- "@flakiness/shared": "0.219.0",
27
- "@flakiness/server": "0.219.0"
26
+ "@flakiness/server": "0.221.0",
27
+ "@flakiness/shared": "0.221.0"
28
28
  },
29
29
  "dependencies": {
30
30
  "@flakiness/flakiness-report": "^0.28.0",
@@ -35,9 +35,11 @@
35
35
  "debug": "^4.4.3",
36
36
  "mime": "^4.1.0",
37
37
  "open": "^10.2.0",
38
- "ora": "^8.2.0"
38
+ "ora": "^8.2.0",
39
+ "temporal-polyfill": "^0.3.0"
39
40
  },
40
41
  "scripts": {
42
+ "test": "pnpm playwright test",
41
43
  "build:all": "pnpm build:win && pnpm build:linux && pnpm build:mac && pnpm build:alpine && pnpm build:mac_intel",
42
44
  "build:win": "bun build ./lib/cli/cli.js --compile --minify --target=bun-windows-x64 --outfile dist/flakiness-win-x64.exe",
43
45
  "build:linux": "bun build ./lib/cli/cli.js --compile --minify --target=bun-linux-x64 --outfile dist/flakiness-linux-x64",