@flakiness/sdk 3.1.0 → 3.3.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 +21 -0
- package/lib/index.js +139 -65
- package/package.json +2 -2
- package/types/src/_internalUtils.d.ts +3 -0
- package/types/src/_internalUtils.d.ts.map +1 -1
- package/types/src/detectRuntime.d.ts +22 -0
- package/types/src/detectRuntime.d.ts.map +1 -0
- package/types/src/fetchTestDurations.d.ts +63 -0
- package/types/src/fetchTestDurations.d.ts.map +1 -0
- package/types/src/githubOIDC.d.ts.map +1 -1
- package/types/src/index.d.ts +1 -0
- package/types/src/index.d.ts.map +1 -1
- package/types/src/reportUtils.d.ts +1 -0
- package/types/src/reportUtils.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,7 @@ Use this entry point when you need to process or manipulate reports in browser-b
|
|
|
91
91
|
- **`GitWorktree`** - Git repository utilities for path conversion and commit information
|
|
92
92
|
- **`ReportUtils`** - Namespace with utilities for report creation and manipulation:
|
|
93
93
|
- `createEnvironment()` - Create environment objects with system information
|
|
94
|
+
- `detectRuntime()` - Detect the JS runtime (`node` / `bun` / `deno`) and its version; suitable for `Report.runtime`
|
|
94
95
|
- `normalizeReport()` - Deduplicate environments, suites, and tests
|
|
95
96
|
- `collectSources()` - Extract source code snippets for locations in the report
|
|
96
97
|
- `stripAnsi()` - Remove ANSI escape codes from strings
|
|
@@ -101,11 +102,31 @@ Use this entry point when you need to process or manipulate reports in browser-b
|
|
|
101
102
|
|
|
102
103
|
### Working with Reports
|
|
103
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
|
|
104
106
|
- **`showReport()`** - Start a local server and open the report in your browser
|
|
105
107
|
- **`showReportCommand()`** - Build a shell command for opening the report later with the Flakiness CLI
|
|
106
108
|
- **`uploadReport()`** - Upload reports and attachments to Flakiness.io
|
|
107
109
|
- **`writeReport()`** - Write reports to disk in the standard Flakiness report format
|
|
108
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
|
+
|
|
109
130
|
## Uploading Reports
|
|
110
131
|
|
|
111
132
|
`uploadReport()` authenticates using one of the following methods (in order of priority):
|
package/lib/index.js
CHANGED
|
@@ -170,6 +170,32 @@ async function retryWithBackoff(job, backoff = []) {
|
|
|
170
170
|
}
|
|
171
171
|
return await job();
|
|
172
172
|
}
|
|
173
|
+
var HTTP_BACKOFF = [100, 500, 1e3, 1e3, 1e3, 1e3];
|
|
174
|
+
async function fetchOk(input, init) {
|
|
175
|
+
const response = await fetch(input, init);
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
const url = response.url || (input instanceof URL ? input.href : typeof input === "string" ? input : input.url);
|
|
178
|
+
const body = await response.text().catch(() => "");
|
|
179
|
+
throw new Error(response.status + " " + url + " " + body);
|
|
180
|
+
}
|
|
181
|
+
return response;
|
|
182
|
+
}
|
|
183
|
+
async function getJSON(input, init, backoff = HTTP_BACKOFF) {
|
|
184
|
+
return await retryWithBackoff(async () => {
|
|
185
|
+
const response = await fetchOk(input, init);
|
|
186
|
+
return await response.json();
|
|
187
|
+
}, backoff);
|
|
188
|
+
}
|
|
189
|
+
async function putBuffer(input, body, headers, backoff = HTTP_BACKOFF) {
|
|
190
|
+
await retryWithBackoff(async () => {
|
|
191
|
+
const response = await fetchOk(input, {
|
|
192
|
+
method: "PUT",
|
|
193
|
+
headers,
|
|
194
|
+
body: new Uint8Array(body)
|
|
195
|
+
});
|
|
196
|
+
await response.arrayBuffer();
|
|
197
|
+
}, backoff);
|
|
198
|
+
}
|
|
173
199
|
function shell(command, args, options) {
|
|
174
200
|
try {
|
|
175
201
|
const result = spawnSync(command, args, { encoding: "utf-8", ...options });
|
|
@@ -507,17 +533,17 @@ var GithubOIDC = class _GithubOIDC {
|
|
|
507
533
|
async createFlakinessAccessToken(flakinessProject) {
|
|
508
534
|
const url = new URL(this._requestUrl);
|
|
509
535
|
url.searchParams.set("audience", flakinessProject);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
536
|
+
let json;
|
|
537
|
+
try {
|
|
538
|
+
json = await getJSON(url, {
|
|
539
|
+
headers: {
|
|
540
|
+
"Authorization": `bearer ${this._requestToken}`,
|
|
541
|
+
"Accept": "application/json; api-version=2.0"
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
throw new Error(`Failed to request GitHub OIDC token: ${error.message || String(error)}`);
|
|
519
546
|
}
|
|
520
|
-
const json = await response.json();
|
|
521
547
|
if (!json.value)
|
|
522
548
|
throw new Error("GitHub OIDC token response did not contain a token value.");
|
|
523
549
|
return json.value;
|
|
@@ -531,6 +557,7 @@ __export(reportUtils_exports, {
|
|
|
531
557
|
createDataAttachment: () => createDataAttachment,
|
|
532
558
|
createEnvironment: () => createEnvironment,
|
|
533
559
|
createFileAttachment: () => createFileAttachment,
|
|
560
|
+
detectRuntime: () => detectRuntime,
|
|
534
561
|
normalizeReport: () => normalizeReport,
|
|
535
562
|
stripAnsi: () => stripAnsi,
|
|
536
563
|
validateReport: () => validateReport,
|
|
@@ -680,6 +707,18 @@ function createEnvironment(options) {
|
|
|
680
707
|
};
|
|
681
708
|
}
|
|
682
709
|
|
|
710
|
+
// src/detectRuntime.ts
|
|
711
|
+
function detectRuntime() {
|
|
712
|
+
const g = globalThis;
|
|
713
|
+
if (g.Bun?.version)
|
|
714
|
+
return { name: "bun", version: g.Bun.version };
|
|
715
|
+
if (g.Deno?.version?.deno)
|
|
716
|
+
return { name: "deno", version: g.Deno.version.deno };
|
|
717
|
+
if (g.process?.versions?.node)
|
|
718
|
+
return { name: "node", version: g.process.versions.node };
|
|
719
|
+
return void 0;
|
|
720
|
+
}
|
|
721
|
+
|
|
683
722
|
// src/normalizeReport.ts
|
|
684
723
|
import { FlakinessReport } from "@flakiness/flakiness-report";
|
|
685
724
|
import stableObjectHash from "stable-hash";
|
|
@@ -935,7 +974,6 @@ async function uploadReport(report, attachments, options) {
|
|
|
935
974
|
return { status: "failed", error: errorMessage };
|
|
936
975
|
}
|
|
937
976
|
}
|
|
938
|
-
var HTTP_BACKOFF = [100, 500, 1e3, 1e3, 1e3, 1e3];
|
|
939
977
|
var ReportUpload = class {
|
|
940
978
|
_report;
|
|
941
979
|
_attachments;
|
|
@@ -948,23 +986,25 @@ var ReportUpload = class {
|
|
|
948
986
|
async _api(pathname, token, body) {
|
|
949
987
|
const url = new URL2(this._options.flakinessEndpoint);
|
|
950
988
|
url.pathname = pathname;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
989
|
+
try {
|
|
990
|
+
const result = await getJSON(url, {
|
|
991
|
+
method: "POST",
|
|
992
|
+
headers: {
|
|
993
|
+
"Authorization": `Bearer ${token}`,
|
|
994
|
+
"Content-Type": "application/json"
|
|
995
|
+
},
|
|
996
|
+
body: body ? JSON.stringify(body) : void 0
|
|
997
|
+
});
|
|
998
|
+
return {
|
|
999
|
+
result,
|
|
1000
|
+
error: void 0
|
|
1001
|
+
};
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
return {
|
|
1004
|
+
result: void 0,
|
|
1005
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
968
1008
|
}
|
|
969
1009
|
async upload() {
|
|
970
1010
|
const [orgSlug, projectSlug] = this._report.flakinessProject ? this._report.flakinessProject.split("/") : [];
|
|
@@ -1006,37 +1046,17 @@ var ReportUpload = class {
|
|
|
1006
1046
|
"Content-Length": Buffer.byteLength(compressed) + "",
|
|
1007
1047
|
"Content-Encoding": "br"
|
|
1008
1048
|
};
|
|
1009
|
-
await
|
|
1010
|
-
const response = await fetch(uploadUrl, {
|
|
1011
|
-
method: "PUT",
|
|
1012
|
-
headers,
|
|
1013
|
-
body: Buffer.from(compressed)
|
|
1014
|
-
});
|
|
1015
|
-
if (!response.ok) {
|
|
1016
|
-
throw new Error(`Request to ${uploadUrl} failed with ${response.status}`);
|
|
1017
|
-
}
|
|
1018
|
-
await response.arrayBuffer();
|
|
1019
|
-
}, HTTP_BACKOFF);
|
|
1049
|
+
await putBuffer(uploadUrl, compressed, headers);
|
|
1020
1050
|
}
|
|
1021
1051
|
async _uploadAttachment(attachment, uploadUrl) {
|
|
1022
1052
|
const mimeType = attachment.contentType.toLocaleLowerCase().trim();
|
|
1023
1053
|
const compressable = mimeType.startsWith("text/") || mimeType.endsWith("+json") || mimeType.endsWith("+text") || mimeType.endsWith("+xml");
|
|
1024
1054
|
if (!compressable && attachment.type === "file") {
|
|
1025
|
-
await
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
"Content-Type": attachment.contentType,
|
|
1031
|
-
"Content-Length": fileBuffer.length + ""
|
|
1032
|
-
},
|
|
1033
|
-
body: new Uint8Array(fileBuffer)
|
|
1034
|
-
});
|
|
1035
|
-
if (!response.ok) {
|
|
1036
|
-
throw new Error(`Request to ${uploadUrl} failed with ${response.status}`);
|
|
1037
|
-
}
|
|
1038
|
-
await response.arrayBuffer();
|
|
1039
|
-
}, HTTP_BACKOFF);
|
|
1055
|
+
const fileBuffer = await fs4.promises.readFile(attachment.path);
|
|
1056
|
+
await putBuffer(uploadUrl, fileBuffer, {
|
|
1057
|
+
"Content-Type": attachment.contentType,
|
|
1058
|
+
"Content-Length": fileBuffer.length + ""
|
|
1059
|
+
});
|
|
1040
1060
|
return;
|
|
1041
1061
|
}
|
|
1042
1062
|
let buffer = attachment.type === "buffer" ? attachment.body : await fs4.promises.readFile(attachment.path);
|
|
@@ -1051,17 +1071,7 @@ var ReportUpload = class {
|
|
|
1051
1071
|
if (encoding) {
|
|
1052
1072
|
headers["Content-Encoding"] = encoding;
|
|
1053
1073
|
}
|
|
1054
|
-
await
|
|
1055
|
-
const response = await fetch(uploadUrl, {
|
|
1056
|
-
method: "PUT",
|
|
1057
|
-
headers,
|
|
1058
|
-
body: new Uint8Array(buffer)
|
|
1059
|
-
});
|
|
1060
|
-
if (!response.ok) {
|
|
1061
|
-
throw new Error(`Request to ${uploadUrl} failed with ${response.status}`);
|
|
1062
|
-
}
|
|
1063
|
-
await response.arrayBuffer();
|
|
1064
|
-
}, HTTP_BACKOFF);
|
|
1074
|
+
await putBuffer(uploadUrl, buffer, headers);
|
|
1065
1075
|
}
|
|
1066
1076
|
};
|
|
1067
1077
|
|
|
@@ -1081,6 +1091,69 @@ function visitTests(report, testVisitor) {
|
|
|
1081
1091
|
visitSuite(suite, []);
|
|
1082
1092
|
}
|
|
1083
1093
|
|
|
1094
|
+
// src/fetchTestDurations.ts
|
|
1095
|
+
import { URL as URL3 } from "url";
|
|
1096
|
+
var DOWNLOAD_BACKOFF = [Array(10).fill(1e3), Array(10).fill(2e3), Array(20).fill(3e3)].flat();
|
|
1097
|
+
async function fetchTestDurations(report, options) {
|
|
1098
|
+
let flakinessAccessToken = options?.flakinessAccessToken ?? process.env["FLAKINESS_ACCESS_TOKEN"];
|
|
1099
|
+
const githubOIDC = GithubOIDC.initializeFromEnv();
|
|
1100
|
+
if (!flakinessAccessToken && githubOIDC && report.flakinessProject)
|
|
1101
|
+
flakinessAccessToken = await githubOIDC.createFlakinessAccessToken(report.flakinessProject);
|
|
1102
|
+
if (!flakinessAccessToken)
|
|
1103
|
+
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`)");
|
|
1104
|
+
const flakinessEndpoint = options?.flakinessEndpoint ?? process.env["FLAKINESS_ENDPOINT"] ?? "https://flakiness.io";
|
|
1105
|
+
const fetcher = new TestDurationsFetcher(report, { flakinessAccessToken, flakinessEndpoint });
|
|
1106
|
+
return await fetcher.fetch();
|
|
1107
|
+
}
|
|
1108
|
+
var TestDurationsFetcher = class {
|
|
1109
|
+
_report;
|
|
1110
|
+
_options;
|
|
1111
|
+
constructor(report, options) {
|
|
1112
|
+
this._report = report;
|
|
1113
|
+
this._options = options;
|
|
1114
|
+
}
|
|
1115
|
+
async _api(pathname, token, body) {
|
|
1116
|
+
const url = new URL3(this._options.flakinessEndpoint);
|
|
1117
|
+
url.pathname = pathname;
|
|
1118
|
+
return await getJSON(url, {
|
|
1119
|
+
method: "POST",
|
|
1120
|
+
headers: {
|
|
1121
|
+
"Authorization": `Bearer ${token}`,
|
|
1122
|
+
"Content-Type": "application/json"
|
|
1123
|
+
},
|
|
1124
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
async fetch() {
|
|
1128
|
+
const shardGroupKey = sha1Text(JSON.stringify({
|
|
1129
|
+
commitId: this._report.commitId,
|
|
1130
|
+
category: this._report.category,
|
|
1131
|
+
testRunnerName: this._report.testRunner?.name ?? "unknown",
|
|
1132
|
+
testRunnerVersion: this._report.testRunner?.version ?? "unknown"
|
|
1133
|
+
}));
|
|
1134
|
+
const createResponse = await this._api(
|
|
1135
|
+
"/api/testDurations/create",
|
|
1136
|
+
this._options.flakinessAccessToken,
|
|
1137
|
+
{ commitId: this._report.commitId, shardGroupKey }
|
|
1138
|
+
);
|
|
1139
|
+
await this._uploadReport(JSON.stringify(this._report), createResponse.uploadUrl);
|
|
1140
|
+
const submitResponse = await this._api(
|
|
1141
|
+
"/api/testDurations/submit",
|
|
1142
|
+
createResponse.testDurationsToken
|
|
1143
|
+
);
|
|
1144
|
+
return await getJSON(submitResponse.downloadUrl, void 0, DOWNLOAD_BACKOFF);
|
|
1145
|
+
}
|
|
1146
|
+
async _uploadReport(data, uploadUrl) {
|
|
1147
|
+
const compressed = await compressTextAsync(data);
|
|
1148
|
+
const headers = {
|
|
1149
|
+
"Content-Type": "application/json",
|
|
1150
|
+
"Content-Length": Buffer.byteLength(compressed) + "",
|
|
1151
|
+
"Content-Encoding": "br"
|
|
1152
|
+
};
|
|
1153
|
+
await putBuffer(uploadUrl, compressed, headers);
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1084
1157
|
// src/readReport.ts
|
|
1085
1158
|
import fs5 from "fs/promises";
|
|
1086
1159
|
import path from "path";
|
|
@@ -1450,6 +1523,7 @@ export {
|
|
|
1450
1523
|
GithubOIDC,
|
|
1451
1524
|
RAMUtilization,
|
|
1452
1525
|
reportUtils_exports as ReportUtils,
|
|
1526
|
+
fetchTestDurations,
|
|
1453
1527
|
readReport,
|
|
1454
1528
|
showReport,
|
|
1455
1529
|
showReportCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flakiness/sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": "^20.17.0 || >=22.9.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@flakiness/flakiness-report": "^0.
|
|
31
|
+
"@flakiness/flakiness-report": "^0.34.0",
|
|
32
32
|
"@flakiness/playwright": "^1.3.3",
|
|
33
33
|
"@playwright/test": "^1.58.2",
|
|
34
34
|
"@types/debug": "^4.1.12",
|
|
@@ -6,6 +6,9 @@ export type Brand<T, Brand extends string> = T & {
|
|
|
6
6
|
};
|
|
7
7
|
export declare function errorText(error: Error): string | undefined;
|
|
8
8
|
export declare function retryWithBackoff<T>(job: () => Promise<T>, backoff?: number[]): Promise<T>;
|
|
9
|
+
export declare const HTTP_BACKOFF: number[];
|
|
10
|
+
export declare function getJSON<T>(input: RequestInfo | URL, init?: RequestInit, backoff?: number[]): Promise<T>;
|
|
11
|
+
export declare function putBuffer(input: RequestInfo | URL, body: Buffer, headers?: HeadersInit, backoff?: number[]): Promise<void>;
|
|
9
12
|
export declare function shell(command: string, args?: string[], options?: SpawnSyncOptionsWithStringEncoding): string | undefined;
|
|
10
13
|
export declare function sha1Text(data: crypto.BinaryLike): string;
|
|
11
14
|
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;
|
|
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,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,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,22 @@
|
|
|
1
|
+
import { FlakinessReport } from '@flakiness/flakiness-report';
|
|
2
|
+
/**
|
|
3
|
+
* Detects the JavaScript runtime executing the current process.
|
|
4
|
+
*
|
|
5
|
+
* Returns the runtime name (`bun`, `deno`, `node`) and its version, suitable for
|
|
6
|
+
* use as `FlakinessReport.Report.runtime`. Bun and Deno expose Node-compat
|
|
7
|
+
* versions on `process.versions.node`, so this probes their globals first to
|
|
8
|
+
* avoid mis-identifying them as Node.
|
|
9
|
+
*
|
|
10
|
+
* @returns {FlakinessReport.Report['runtime']} The detected runtime, or `undefined` if no
|
|
11
|
+
* supported runtime is recognized (e.g., browser environments).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const report = ReportUtils.normalizeReport({
|
|
16
|
+
* // ...
|
|
17
|
+
* runtime: ReportUtils.detectRuntime(),
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectRuntime(): FlakinessReport.Report['runtime'];
|
|
22
|
+
//# sourceMappingURL=detectRuntime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectRuntime.d.ts","sourceRoot":"","sources":["../../src/detectRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,IAAI,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAgBjE"}
|
|
@@ -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":"
|
|
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"}
|
package/types/src/index.d.ts
CHANGED
|
@@ -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';
|
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,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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { collectSources } from './collectSources.js';
|
|
2
2
|
export { createEnvironment } from './createEnvironment.js';
|
|
3
|
+
export { detectRuntime } from './detectRuntime.js';
|
|
3
4
|
export { normalizeReport } from './normalizeReport.js';
|
|
4
5
|
export { validateReport } from './validateReport.js';
|
|
5
6
|
export { stripAnsi } from './stripAnsi.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|