@flakiness/sdk 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -102,11 +102,31 @@ Use this entry point when you need to process or manipulate reports in browser-b
102
102
 
103
103
  ### Working with Reports
104
104
  - **`readReport()`** - Read a Flakiness report and its attachments from disk
105
+ - **`fetchTestDurations()`** - Fetch historical test durations from Flakiness.io and return a report enriched with timings
105
106
  - **`showReport()`** - Start a local server and open the report in your browser
106
107
  - **`showReportCommand()`** - Build a shell command for opening the report later with the Flakiness CLI
107
108
  - **`uploadReport()`** - Upload reports and attachments to Flakiness.io
108
109
  - **`writeReport()`** - Write reports to disk in the standard Flakiness report format
109
110
 
111
+ ## Fetching Test Durations
112
+
113
+ `fetchTestDurations()` sends a report to Flakiness.io and returns a copy enriched
114
+ with historical test durations. Test runners can use these timings to split tests
115
+ into balanced shards.
116
+
117
+ ```typescript
118
+ import { fetchTestDurations } from '@flakiness/sdk';
119
+
120
+ const reportWithDurations = await fetchTestDurations(report, {
121
+ flakinessAccessToken: 'your-token',
122
+ });
123
+ ```
124
+
125
+ Authentication follows the same priority order as `uploadReport()`:
126
+
127
+ 1. **Access token** — pass `flakinessAccessToken` option or set the `FLAKINESS_ACCESS_TOKEN` environment variable.
128
+ 2. **GitHub Actions OIDC** — when running inside GitHub Actions and the report has `flakinessProject` set.
129
+
110
130
  ## Uploading Reports
111
131
 
112
132
  `uploadReport()` authenticates using one of the following methods (in order of priority):
package/lib/index.js CHANGED
@@ -150,26 +150,42 @@ async function compressTextAsync(text) {
150
150
  }
151
151
  });
152
152
  }
153
- var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
154
- function errorText(error) {
155
- return FLAKINESS_DBG ? error.stack : error.message;
156
- }
157
153
  async function retryWithBackoff(job, backoff = []) {
158
154
  for (const timeout of backoff) {
159
155
  try {
160
156
  return await job();
161
157
  } catch (e) {
162
- if (e instanceof AggregateError)
163
- console.error(`[flakiness.io err]`, errorText(e.errors[0]));
164
- else if (e instanceof Error)
165
- console.error(`[flakiness.io err]`, errorText(e));
166
- else
167
- console.error(`[flakiness.io err]`, e);
168
158
  await new Promise((x) => setTimeout(x, timeout));
169
159
  }
170
160
  }
171
161
  return await job();
172
162
  }
163
+ var HTTP_BACKOFF = [100, 500, 1e3, 1e3, 1e3, 1e3];
164
+ async function fetchOk(input, init) {
165
+ const response = await fetch(input, init);
166
+ if (!response.ok) {
167
+ const url = response.url || (input instanceof URL ? input.href : typeof input === "string" ? input : input.url);
168
+ const body = await response.text().catch(() => "");
169
+ throw new Error(response.status + " " + url + " " + body);
170
+ }
171
+ return response;
172
+ }
173
+ async function getJSON(input, init, backoff = HTTP_BACKOFF) {
174
+ return await retryWithBackoff(async () => {
175
+ const response = await fetchOk(input, init);
176
+ return await response.json();
177
+ }, backoff);
178
+ }
179
+ async function putBuffer(input, body, headers, backoff = HTTP_BACKOFF) {
180
+ await retryWithBackoff(async () => {
181
+ const response = await fetchOk(input, {
182
+ method: "PUT",
183
+ headers,
184
+ body: new Uint8Array(body)
185
+ });
186
+ await response.arrayBuffer();
187
+ }, backoff);
188
+ }
173
189
  function shell(command, args, options) {
174
190
  try {
175
191
  const result = spawnSync(command, args, { encoding: "utf-8", ...options });
@@ -507,17 +523,17 @@ var GithubOIDC = class _GithubOIDC {
507
523
  async createFlakinessAccessToken(flakinessProject) {
508
524
  const url = new URL(this._requestUrl);
509
525
  url.searchParams.set("audience", flakinessProject);
510
- const response = await fetch(url, {
511
- headers: {
512
- "Authorization": `bearer ${this._requestToken}`,
513
- "Accept": "application/json; api-version=2.0"
514
- }
515
- });
516
- if (!response.ok) {
517
- const body = await response.text().catch(() => "");
518
- throw new Error(`Failed to request GitHub OIDC token: ${response.status} ${body}`);
526
+ let json;
527
+ try {
528
+ json = await getJSON(url, {
529
+ headers: {
530
+ "Authorization": `bearer ${this._requestToken}`,
531
+ "Accept": "application/json; api-version=2.0"
532
+ }
533
+ });
534
+ } catch (error) {
535
+ throw new Error(`Failed to request GitHub OIDC token: ${error.message || String(error)}`);
519
536
  }
520
- const json = await response.json();
521
537
  if (!json.value)
522
538
  throw new Error("GitHub OIDC token response did not contain a token value.");
523
539
  return json.value;
@@ -948,7 +964,6 @@ async function uploadReport(report, attachments, options) {
948
964
  return { status: "failed", error: errorMessage };
949
965
  }
950
966
  }
951
- var HTTP_BACKOFF = [100, 500, 1e3, 1e3, 1e3, 1e3];
952
967
  var ReportUpload = class {
953
968
  _report;
954
969
  _attachments;
@@ -961,23 +976,25 @@ var ReportUpload = class {
961
976
  async _api(pathname, token, body) {
962
977
  const url = new URL2(this._options.flakinessEndpoint);
963
978
  url.pathname = pathname;
964
- return await fetch(url, {
965
- method: "POST",
966
- headers: {
967
- "Authorization": `Bearer ${token}`,
968
- "Content-Type": "application/json"
969
- },
970
- body: body ? JSON.stringify(body) : void 0
971
- }).then(async (response) => !response.ok ? {
972
- result: void 0,
973
- error: response.status + " " + url.href + " " + await response.text()
974
- } : {
975
- result: await response.json(),
976
- error: void 0
977
- }).catch((error) => ({
978
- result: void 0,
979
- error
980
- }));
979
+ try {
980
+ const result = await getJSON(url, {
981
+ method: "POST",
982
+ headers: {
983
+ "Authorization": `Bearer ${token}`,
984
+ "Content-Type": "application/json"
985
+ },
986
+ body: body ? JSON.stringify(body) : void 0
987
+ });
988
+ return {
989
+ result,
990
+ error: void 0
991
+ };
992
+ } catch (error) {
993
+ return {
994
+ result: void 0,
995
+ error: error instanceof Error ? error.message : String(error)
996
+ };
997
+ }
981
998
  }
982
999
  async upload() {
983
1000
  const [orgSlug, projectSlug] = this._report.flakinessProject ? this._report.flakinessProject.split("/") : [];
@@ -1019,37 +1036,17 @@ var ReportUpload = class {
1019
1036
  "Content-Length": Buffer.byteLength(compressed) + "",
1020
1037
  "Content-Encoding": "br"
1021
1038
  };
1022
- await retryWithBackoff(async () => {
1023
- const response = await fetch(uploadUrl, {
1024
- method: "PUT",
1025
- headers,
1026
- body: Buffer.from(compressed)
1027
- });
1028
- if (!response.ok) {
1029
- throw new Error(`Request to ${uploadUrl} failed with ${response.status}`);
1030
- }
1031
- await response.arrayBuffer();
1032
- }, HTTP_BACKOFF);
1039
+ await putBuffer(uploadUrl, compressed, headers);
1033
1040
  }
1034
1041
  async _uploadAttachment(attachment, uploadUrl) {
1035
1042
  const mimeType = attachment.contentType.toLocaleLowerCase().trim();
1036
1043
  const compressable = mimeType.startsWith("text/") || mimeType.endsWith("+json") || mimeType.endsWith("+text") || mimeType.endsWith("+xml");
1037
1044
  if (!compressable && attachment.type === "file") {
1038
- await retryWithBackoff(async () => {
1039
- const fileBuffer = await fs4.promises.readFile(attachment.path);
1040
- const response = await fetch(uploadUrl, {
1041
- method: "PUT",
1042
- headers: {
1043
- "Content-Type": attachment.contentType,
1044
- "Content-Length": fileBuffer.length + ""
1045
- },
1046
- body: new Uint8Array(fileBuffer)
1047
- });
1048
- if (!response.ok) {
1049
- throw new Error(`Request to ${uploadUrl} failed with ${response.status}`);
1050
- }
1051
- await response.arrayBuffer();
1052
- }, HTTP_BACKOFF);
1045
+ const fileBuffer = await fs4.promises.readFile(attachment.path);
1046
+ await putBuffer(uploadUrl, fileBuffer, {
1047
+ "Content-Type": attachment.contentType,
1048
+ "Content-Length": fileBuffer.length + ""
1049
+ });
1053
1050
  return;
1054
1051
  }
1055
1052
  let buffer = attachment.type === "buffer" ? attachment.body : await fs4.promises.readFile(attachment.path);
@@ -1064,17 +1061,7 @@ var ReportUpload = class {
1064
1061
  if (encoding) {
1065
1062
  headers["Content-Encoding"] = encoding;
1066
1063
  }
1067
- await retryWithBackoff(async () => {
1068
- const response = await fetch(uploadUrl, {
1069
- method: "PUT",
1070
- headers,
1071
- body: new Uint8Array(buffer)
1072
- });
1073
- if (!response.ok) {
1074
- throw new Error(`Request to ${uploadUrl} failed with ${response.status}`);
1075
- }
1076
- await response.arrayBuffer();
1077
- }, HTTP_BACKOFF);
1064
+ await putBuffer(uploadUrl, buffer, headers);
1078
1065
  }
1079
1066
  };
1080
1067
 
@@ -1094,6 +1081,69 @@ function visitTests(report, testVisitor) {
1094
1081
  visitSuite(suite, []);
1095
1082
  }
1096
1083
 
1084
+ // src/fetchTestDurations.ts
1085
+ import { URL as URL3 } from "url";
1086
+ var DOWNLOAD_BACKOFF = [Array(10).fill(1e3), Array(10).fill(2e3), Array(20).fill(3e3)].flat();
1087
+ async function fetchTestDurations(report, options) {
1088
+ let flakinessAccessToken = options?.flakinessAccessToken ?? process.env["FLAKINESS_ACCESS_TOKEN"];
1089
+ const githubOIDC = GithubOIDC.initializeFromEnv();
1090
+ if (!flakinessAccessToken && githubOIDC && report.flakinessProject)
1091
+ flakinessAccessToken = await githubOIDC.createFlakinessAccessToken(report.flakinessProject);
1092
+ if (!flakinessAccessToken)
1093
+ throw new Error("No Flakiness access token available (set FLAKINESS_ACCESS_TOKEN, pass `flakinessAccessToken`, or run in GitHub Actions with `id-token: write` and a configured `flakinessProject`)");
1094
+ const flakinessEndpoint = options?.flakinessEndpoint ?? process.env["FLAKINESS_ENDPOINT"] ?? "https://flakiness.io";
1095
+ const fetcher = new TestDurationsFetcher(report, { flakinessAccessToken, flakinessEndpoint });
1096
+ return await fetcher.fetch();
1097
+ }
1098
+ var TestDurationsFetcher = class {
1099
+ _report;
1100
+ _options;
1101
+ constructor(report, options) {
1102
+ this._report = report;
1103
+ this._options = options;
1104
+ }
1105
+ async _api(pathname, token, body) {
1106
+ const url = new URL3(this._options.flakinessEndpoint);
1107
+ url.pathname = pathname;
1108
+ return await getJSON(url, {
1109
+ method: "POST",
1110
+ headers: {
1111
+ "Authorization": `Bearer ${token}`,
1112
+ "Content-Type": "application/json"
1113
+ },
1114
+ body: body ? JSON.stringify(body) : void 0
1115
+ });
1116
+ }
1117
+ async fetch() {
1118
+ const shardGroupKey = sha1Text(JSON.stringify({
1119
+ commitId: this._report.commitId,
1120
+ category: this._report.category,
1121
+ testRunnerName: this._report.testRunner?.name ?? "unknown",
1122
+ testRunnerVersion: this._report.testRunner?.version ?? "unknown"
1123
+ }));
1124
+ const createResponse = await this._api(
1125
+ "/api/testDurations/create",
1126
+ this._options.flakinessAccessToken,
1127
+ { commitId: this._report.commitId, shardGroupKey }
1128
+ );
1129
+ await this._uploadReport(JSON.stringify(this._report), createResponse.uploadUrl);
1130
+ const submitResponse = await this._api(
1131
+ "/api/testDurations/submit",
1132
+ createResponse.testDurationsToken
1133
+ );
1134
+ return await getJSON(submitResponse.downloadUrl, void 0, DOWNLOAD_BACKOFF);
1135
+ }
1136
+ async _uploadReport(data, uploadUrl) {
1137
+ const compressed = await compressTextAsync(data);
1138
+ const headers = {
1139
+ "Content-Type": "application/json",
1140
+ "Content-Length": Buffer.byteLength(compressed) + "",
1141
+ "Content-Encoding": "br"
1142
+ };
1143
+ await putBuffer(uploadUrl, compressed, headers);
1144
+ }
1145
+ };
1146
+
1097
1147
  // src/readReport.ts
1098
1148
  import fs5 from "fs/promises";
1099
1149
  import path from "path";
@@ -1463,6 +1513,7 @@ export {
1463
1513
  GithubOIDC,
1464
1514
  RAMUtilization,
1465
1515
  reportUtils_exports as ReportUtils,
1516
+ fetchTestDurations,
1466
1517
  readReport,
1467
1518
  showReport,
1468
1519
  showReportCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flakiness/sdk",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,8 +4,10 @@ export declare function compressTextAsync(text: string | Buffer): Promise<Buffer
4
4
  export type Brand<T, Brand extends string> = T & {
5
5
  readonly [B in Brand as `__${B}_brand`]: never;
6
6
  };
7
- export declare function errorText(error: Error): string | undefined;
8
7
  export declare function retryWithBackoff<T>(job: () => Promise<T>, backoff?: number[]): Promise<T>;
8
+ export declare const HTTP_BACKOFF: number[];
9
+ export declare function getJSON<T>(input: RequestInfo | URL, init?: RequestInit, backoff?: number[]): Promise<T>;
10
+ export declare function putBuffer(input: RequestInfo | URL, body: Buffer, headers?: HeadersInit, backoff?: number[]): Promise<void>;
9
11
  export declare function shell(command: string, args?: string[], options?: SpawnSyncOptionsWithStringEncoding): string | undefined;
10
12
  export declare function sha1Text(data: crypto.BinaryLike): string;
11
13
  export declare function sha1File(filePath: string): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"_internalUtils.d.ts","sourceRoot":"","sources":["../../src/_internalUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,kCAAkC,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,MAAM,MAAM,QAAQ,CAAC;AAQ5B,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,KAAK,SAAS,MAAM,IAAI,CAAC,GAAG;IAC/C,QAAQ,EAAE,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK;CAC/C,CAAC;AAGF,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,sBAErC;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAenG;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,kCAAkC,sBAWnG;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,UAI/C;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1D;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAczC"}
1
+ {"version":3,"file":"_internalUtils.d.ts","sourceRoot":"","sources":["../../src/_internalUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,kCAAkC,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,MAAM,MAAM,QAAQ,CAAC;AAM5B,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,KAAK,SAAS,MAAM,IAAI,CAAC,GAAG;IAC/C,QAAQ,EAAE,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK;CAC/C,CAAC;AAGF,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CASnG;AAED,eAAO,MAAM,YAAY,UAAqC,CAAC;AAY/D,wBAAsB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,OAAO,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,CAAC,CAAC,CAK3H;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9I;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,kCAAkC,sBAWnG;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,UAI/C;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1D;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAczC"}
@@ -0,0 +1,63 @@
1
+ import { FlakinessReport } from '@flakiness/flakiness-report';
2
+ /**
3
+ * Options for {@link fetchTestDurations}.
4
+ */
5
+ export type FetchTestDurationsOptions = {
6
+ /**
7
+ * Custom Flakiness.io endpoint URL.
8
+ *
9
+ * Defaults to the `FLAKINESS_ENDPOINT` environment variable, or 'https://flakiness.io'
10
+ * if the environment variable is not set.
11
+ *
12
+ * @example 'https://custom.flakiness.io'
13
+ */
14
+ flakinessEndpoint?: string;
15
+ /**
16
+ * Access token for authenticating with the Flakiness.io platform.
17
+ *
18
+ * Defaults to the `FLAKINESS_ACCESS_TOKEN` environment variable. If no token is provided
19
+ * through this option or the environment variable, the function will attempt to authenticate
20
+ * via GitHub Actions OIDC when running in GitHub Actions (requires `report.flakinessProject`
21
+ * to be set and the project to be bound to the repository).
22
+ *
23
+ * @example 'flakiness-io-1234567890abcdef...'
24
+ */
25
+ flakinessAccessToken?: string;
26
+ };
27
+ /**
28
+ * Fetches historical test durations for a report from the Flakiness.io platform.
29
+ *
30
+ * This is used to compute "balanced shards" — by knowing how long each test took
31
+ * historically, a test runner can balance tests across shards so that every shard
32
+ * finishes at roughly the same time.
33
+ *
34
+ * The function performs the following steps:
35
+ * 1. Authenticates using an access token or GitHub Actions OIDC.
36
+ * 2. Computes a shard-group key from the report so that all shards of the same run
37
+ * fetch an identical set of timings.
38
+ * 3. Uploads the (compressed) report so the platform knows which tests to time.
39
+ * 4. Submits the request and polls the resulting download URL until the computed
40
+ * durations are ready (up to ~90 seconds).
41
+ *
42
+ * ## Authentication
43
+ *
44
+ * Authentication follows the same priority order as {@link uploadReport}:
45
+ * 1. **Access token** — provided via `flakinessAccessToken` option or `FLAKINESS_ACCESS_TOKEN` env var.
46
+ * 2. **GitHub Actions OIDC** — when running in GitHub Actions with no access token. This requires
47
+ * `report.flakinessProject` to be set and the project to be bound to the GitHub repository.
48
+ *
49
+ * @param {FlakinessReport.Report} report - The report describing the tests to fetch durations for.
50
+ * @param {FetchTestDurationsOptions} options - Optional configuration object.
51
+ *
52
+ * @returns {Promise<FlakinessReport.Report>} A report enriched with historical test durations.
53
+ *
54
+ * @throws {Error} If no access token is available, any API call fails, or the durations are not
55
+ * ready within the polling timeout.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const reportWithDurations = await fetchTestDurations(report);
60
+ * ```
61
+ */
62
+ export declare function fetchTestDurations(report: FlakinessReport.Report, options?: FetchTestDurationsOptions): Promise<FlakinessReport.Report>;
63
+ //# sourceMappingURL=fetchTestDurations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchTestDurations.d.ts","sourceRoot":"","sources":["../../src/fetchTestDurations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAU9D;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAA;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,eAAe,CAAC,MAAM,EAC9B,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAajC"}
@@ -1 +1 @@
1
- {"version":3,"file":"githubOIDC.d.ts","sourceRoot":"","sources":["../../src/githubOIDC.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,UAAU;IAiBnB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,aAAa;IAjBvB;;;;;;;;OAQG;IACH,MAAM,CAAC,iBAAiB,IAAI,UAAU,GAAC,SAAS;gBAOtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM;IAK/B;;;;;;;;;;;;;OAaG;IACG,0BAA0B,CAAC,gBAAgB,EAAE,MAAM;CAsB1D"}
1
+ {"version":3,"file":"githubOIDC.d.ts","sourceRoot":"","sources":["../../src/githubOIDC.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,UAAU;IAiBnB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,aAAa;IAjBvB;;;;;;;;OAQG;IACH,MAAM,CAAC,iBAAiB,IAAI,UAAU,GAAC,SAAS;gBAOtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM;IAK/B;;;;;;;;;;;;;OAaG;IACG,0BAA0B,CAAC,gBAAgB,EAAE,MAAM;CAqB1D"}
@@ -4,6 +4,7 @@ export { GitWorktree, type GitWorktreeInitResult } from './gitWorktree.js';
4
4
  export { RAMUtilization } from './ramUtilization.js';
5
5
  export { GithubOIDC } from './githubOIDC.js';
6
6
  export * as ReportUtils from './reportUtils.js';
7
+ export { fetchTestDurations, type FetchTestDurationsOptions } from './fetchTestDurations.js';
7
8
  export { readReport } from './readReport.js';
8
9
  export { showReport } from './showReport.js';
9
10
  export { showReportCommand } from './showReportCommand.js';
@@ -1 +1 @@
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,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,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,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,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,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}