@flakiness/sdk 0.95.0 → 0.96.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.
@@ -1,9 +1,10 @@
1
1
  // src/playwrightJSONReport.ts
2
- import { FlakinessReport as FK, FlakinessReport } from "@flakiness/report";
2
+ import { FlakinessReport as FK, FlakinessReport as FlakinessReport2 } from "@flakiness/report";
3
3
  import debug from "debug";
4
4
  import { posix as posixPath2 } from "path";
5
5
 
6
6
  // src/utils.ts
7
+ import { FlakinessReport } from "@flakiness/report";
7
8
  import assert from "assert";
8
9
  import { spawnSync } from "child_process";
9
10
  import crypto from "crypto";
@@ -11,14 +12,7 @@ import fs from "fs";
11
12
  import http from "http";
12
13
  import https from "https";
13
14
  import os from "os";
14
- import { posix as posixPath, win32 as win32Path } from "path";
15
- import util from "util";
16
- import zlib from "zlib";
17
- var gzipAsync = util.promisify(zlib.gzip);
18
- var gunzipAsync = util.promisify(zlib.gunzip);
19
- var gunzipSync = zlib.gunzipSync;
20
- var brotliCompressAsync = util.promisify(zlib.brotliCompress);
21
- var brotliCompressSync = zlib.brotliCompressSync;
15
+ import path, { posix as posixPath, win32 as win32Path } from "path";
22
16
  async function existsAsync(aPath) {
23
17
  return fs.promises.stat(aPath).then(() => true).catch((e) => false);
24
18
  }
@@ -43,6 +37,10 @@ function sha1File(filePath) {
43
37
  });
44
38
  });
45
39
  }
40
+ var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
41
+ function errorText(error) {
42
+ return FLAKINESS_DBG ? error.stack : error.message;
43
+ }
46
44
  function sha1Buffer(data) {
47
45
  const hash = crypto.createHash("sha1");
48
46
  hash.update(data);
@@ -54,9 +52,9 @@ async function retryWithBackoff(job, backoff = []) {
54
52
  return await job();
55
53
  } catch (e) {
56
54
  if (e instanceof AggregateError)
57
- console.error(`[flakiness.io err]`, e.errors[0].message);
55
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
58
56
  else if (e instanceof Error)
59
- console.error(`[flakiness.io err]`, e.message);
57
+ console.error(`[flakiness.io err]`, errorText(e));
60
58
  else
61
59
  console.error(`[flakiness.io err]`, e);
62
60
  await new Promise((x) => setTimeout(x, timeout));
@@ -74,6 +72,7 @@ var httpUtils;
74
72
  reject = b;
75
73
  });
76
74
  const protocol = url.startsWith("https") ? https : http;
75
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
77
76
  const request = protocol.request(url, { method, headers }, (res) => {
78
77
  const chunks = [];
79
78
  res.on("data", (chunk) => chunks.push(chunk));
@@ -288,7 +287,7 @@ var PlaywrightJSONReport;
288
287
  };
289
288
  const configPath = jsonReport.config.configFile ? gitFilePath(context.gitRoot, normalizePath(jsonReport.config.configFile)) : void 0;
290
289
  const report = {
291
- category: FlakinessReport.CATEGORY_PLAYWRIGHT,
290
+ category: FlakinessReport2.CATEGORY_PLAYWRIGHT,
292
291
  commitId: metadata.commitId,
293
292
  configPath,
294
293
  url: metadata.runURL,
@@ -1,249 +1,29 @@
1
1
  // src/reportUploader.ts
2
+ import { compressTextAsync, compressTextSync } from "@flakiness/shared/node/compression.js";
3
+ import assert from "assert";
2
4
  import fs from "fs";
3
- import { URL as URL2 } from "url";
4
- import { brotliCompressSync as brotliCompressSync2 } from "zlib";
5
+ import { URL } from "url";
5
6
 
6
- // ../server/lib/common/typedHttp.js
7
- var TypedHTTP;
8
- ((TypedHTTP2) => {
9
- TypedHTTP2.StatusCodes = {
10
- Informational: {
11
- CONTINUE: 100,
12
- SWITCHING_PROTOCOLS: 101,
13
- PROCESSING: 102,
14
- EARLY_HINTS: 103
15
- },
16
- Success: {
17
- OK: 200,
18
- CREATED: 201,
19
- ACCEPTED: 202,
20
- NON_AUTHORITATIVE_INFORMATION: 203,
21
- NO_CONTENT: 204,
22
- RESET_CONTENT: 205,
23
- PARTIAL_CONTENT: 206,
24
- MULTI_STATUS: 207
25
- },
26
- Redirection: {
27
- MULTIPLE_CHOICES: 300,
28
- MOVED_PERMANENTLY: 301,
29
- MOVED_TEMPORARILY: 302,
30
- SEE_OTHER: 303,
31
- NOT_MODIFIED: 304,
32
- USE_PROXY: 305,
33
- TEMPORARY_REDIRECT: 307,
34
- PERMANENT_REDIRECT: 308
35
- },
36
- ClientErrors: {
37
- BAD_REQUEST: 400,
38
- UNAUTHORIZED: 401,
39
- PAYMENT_REQUIRED: 402,
40
- FORBIDDEN: 403,
41
- NOT_FOUND: 404,
42
- METHOD_NOT_ALLOWED: 405,
43
- NOT_ACCEPTABLE: 406,
44
- PROXY_AUTHENTICATION_REQUIRED: 407,
45
- REQUEST_TIMEOUT: 408,
46
- CONFLICT: 409,
47
- GONE: 410,
48
- LENGTH_REQUIRED: 411,
49
- PRECONDITION_FAILED: 412,
50
- REQUEST_TOO_LONG: 413,
51
- REQUEST_URI_TOO_LONG: 414,
52
- UNSUPPORTED_MEDIA_TYPE: 415,
53
- REQUESTED_RANGE_NOT_SATISFIABLE: 416,
54
- EXPECTATION_FAILED: 417,
55
- IM_A_TEAPOT: 418,
56
- INSUFFICIENT_SPACE_ON_RESOURCE: 419,
57
- METHOD_FAILURE: 420,
58
- MISDIRECTED_REQUEST: 421,
59
- UNPROCESSABLE_ENTITY: 422,
60
- LOCKED: 423,
61
- FAILED_DEPENDENCY: 424,
62
- UPGRADE_REQUIRED: 426,
63
- PRECONDITION_REQUIRED: 428,
64
- TOO_MANY_REQUESTS: 429,
65
- REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
66
- UNAVAILABLE_FOR_LEGAL_REASONS: 451
67
- },
68
- ServerErrors: {
69
- INTERNAL_SERVER_ERROR: 500,
70
- NOT_IMPLEMENTED: 501,
71
- BAD_GATEWAY: 502,
72
- SERVICE_UNAVAILABLE: 503,
73
- GATEWAY_TIMEOUT: 504,
74
- HTTP_VERSION_NOT_SUPPORTED: 505,
75
- INSUFFICIENT_STORAGE: 507,
76
- NETWORK_AUTHENTICATION_REQUIRED: 511
77
- }
78
- };
79
- const AllErrorCodes = {
80
- ...TypedHTTP2.StatusCodes.ClientErrors,
81
- ...TypedHTTP2.StatusCodes.ServerErrors
82
- };
83
- class HttpError extends Error {
84
- constructor(status, message) {
85
- super(message);
86
- this.status = status;
87
- }
88
- static withCode(code, message) {
89
- const statusCode = AllErrorCodes[code];
90
- const defaultMessage = code.split("_").map((word) => word.charAt(0) + word.slice(1).toLowerCase()).join(" ");
91
- return new HttpError(statusCode, message ?? defaultMessage);
92
- }
93
- }
94
- TypedHTTP2.HttpError = HttpError;
95
- function isInformationalResponse(response) {
96
- return response.status >= 100 && response.status < 200;
97
- }
98
- TypedHTTP2.isInformationalResponse = isInformationalResponse;
99
- function isSuccessResponse(response) {
100
- return response.status >= 200 && response.status < 300;
101
- }
102
- TypedHTTP2.isSuccessResponse = isSuccessResponse;
103
- function isRedirectResponse(response) {
104
- return response.status >= 300 && response.status < 400;
105
- }
106
- TypedHTTP2.isRedirectResponse = isRedirectResponse;
107
- function isErrorResponse(response) {
108
- return response.status >= 400 && response.status < 600;
109
- }
110
- TypedHTTP2.isErrorResponse = isErrorResponse;
111
- function info(status) {
112
- return { status };
113
- }
114
- TypedHTTP2.info = info;
115
- function ok(data, status) {
116
- return {
117
- status: status ?? TypedHTTP2.StatusCodes.Success.OK,
118
- data
119
- };
120
- }
121
- TypedHTTP2.ok = ok;
122
- function redirect(url, status = 302) {
123
- return { status, url };
124
- }
125
- TypedHTTP2.redirect = redirect;
126
- function error(message, status = TypedHTTP2.StatusCodes.ServerErrors.INTERNAL_SERVER_ERROR) {
127
- return { status, message };
128
- }
129
- TypedHTTP2.error = error;
130
- class Router {
131
- constructor(_resolveContext) {
132
- this._resolveContext = _resolveContext;
133
- }
134
- static create() {
135
- return new Router(async (e) => e.ctx);
136
- }
137
- rawMethod(method, route) {
138
- return {
139
- [method]: {
140
- method,
141
- input: route.input,
142
- etag: route.etag,
143
- resolveContext: this._resolveContext,
144
- handler: route.handler
145
- }
146
- };
147
- }
148
- get(route) {
149
- return this.rawMethod("GET", {
150
- ...route,
151
- handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
152
- });
153
- }
154
- post(route) {
155
- return this.rawMethod("POST", {
156
- ...route,
157
- handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
158
- });
159
- }
160
- use(resolveContext) {
161
- return new Router(async (options) => {
162
- const m = await this._resolveContext(options);
163
- return resolveContext({ ...options, ctx: m });
164
- });
165
- }
166
- }
167
- TypedHTTP2.Router = Router;
168
- function createClient(base, fetchCallback) {
169
- function buildUrl(path, input, options) {
170
- const method = path.at(-1);
171
- const url = new URL(path.slice(0, path.length - 1).join("/"), base);
172
- const signal = options?.signal;
173
- let body = void 0;
174
- if (method === "GET" && input)
175
- url.searchParams.set("input", JSON.stringify(input));
176
- else if (method !== "GET" && input)
177
- body = JSON.stringify(input);
178
- return {
179
- url,
180
- method,
181
- headers: body ? { "Content-Type": "application/json" } : void 0,
182
- body,
183
- signal
184
- };
185
- }
186
- function createProxy(path = []) {
187
- return new Proxy(() => {
188
- }, {
189
- get(target, prop) {
190
- if (typeof prop === "symbol")
191
- return void 0;
192
- if (prop === "prepare")
193
- return (input, options) => buildUrl(path, input, options);
194
- const newPath = [...path, prop];
195
- return createProxy(newPath);
196
- },
197
- apply(target, thisArg, args) {
198
- const options = buildUrl(path, args[0], args[1]);
199
- return fetchCallback(options.url, {
200
- method: options.method,
201
- body: options.body,
202
- headers: options.headers,
203
- signal: options.signal
204
- }).then(async (response) => {
205
- if (response.status >= 200 && response.status < 300) {
206
- if (response.headers.get("content-type")?.includes("application/json")) {
207
- const text = await response.text();
208
- return text.length ? JSON.parse(text) : void 0;
209
- }
210
- return await response.blob();
211
- }
212
- if (response.status >= 400 && response.status < 600) {
213
- const text = await response.text();
214
- if (text)
215
- throw new Error(`HTTP request failed with status ${response.status}: ${text}`);
216
- else
217
- throw new Error(`HTTP request failed with status ${response.status}`);
218
- }
219
- });
220
- }
221
- });
222
- }
223
- return createProxy();
224
- }
225
- TypedHTTP2.createClient = createClient;
226
- })(TypedHTTP || (TypedHTTP = {}));
7
+ // src/serverapi.ts
8
+ import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
227
9
 
228
10
  // src/utils.ts
11
+ import { FlakinessReport } from "@flakiness/report";
229
12
  import http from "http";
230
13
  import https from "https";
231
- import util from "util";
232
- import zlib from "zlib";
233
- var gzipAsync = util.promisify(zlib.gzip);
234
- var gunzipAsync = util.promisify(zlib.gunzip);
235
- var gunzipSync = zlib.gunzipSync;
236
- var brotliCompressAsync = util.promisify(zlib.brotliCompress);
237
- var brotliCompressSync = zlib.brotliCompressSync;
14
+ var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
15
+ function errorText(error) {
16
+ return FLAKINESS_DBG ? error.stack : error.message;
17
+ }
238
18
  async function retryWithBackoff(job, backoff = []) {
239
19
  for (const timeout of backoff) {
240
20
  try {
241
21
  return await job();
242
22
  } catch (e) {
243
23
  if (e instanceof AggregateError)
244
- console.error(`[flakiness.io err]`, e.errors[0].message);
24
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
245
25
  else if (e instanceof Error)
246
- console.error(`[flakiness.io err]`, e.message);
26
+ console.error(`[flakiness.io err]`, errorText(e));
247
27
  else
248
28
  console.error(`[flakiness.io err]`, e);
249
29
  await new Promise((x) => setTimeout(x, timeout));
@@ -261,6 +41,7 @@ var httpUtils;
261
41
  reject = b;
262
42
  });
263
43
  const protocol = url.startsWith("https") ? https : http;
44
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
264
45
  const request = protocol.request(url, { method, headers }, (res) => {
265
46
  const chunks = [];
266
47
  res.on("data", (chunk) => chunks.push(chunk));
@@ -376,11 +157,10 @@ var ReportUpload = class {
376
157
  this._options = options;
377
158
  this._report = report;
378
159
  this._attachments = attachments;
379
- this._api = createServerAPI(this._options.flakinessEndpoint, { retries: HTTP_BACKOFF });
160
+ this._api = createServerAPI(this._options.flakinessEndpoint, { retries: HTTP_BACKOFF, auth: this._options.flakinessAccessToken });
380
161
  }
381
162
  async upload(options) {
382
163
  const response = await this._api.run.startUpload.POST({
383
- flakinessAccessToken: this._options.flakinessAccessToken,
384
164
  attachmentIds: this._attachments.map((attachment) => attachment.id)
385
165
  }).then((result) => ({ result, error: void 0 })).catch((e) => ({ result: void 0, error: e }));
386
166
  if (response?.error || !response.result)
@@ -391,17 +171,17 @@ var ReportUpload = class {
391
171
  const uploadURL = response.result.attachment_upload_urls[attachment.id];
392
172
  if (!uploadURL)
393
173
  throw new Error("Internal error: missing upload URL for attachment!");
394
- return this._uploadAttachment(attachment, uploadURL);
174
+ return this._uploadAttachment(attachment, uploadURL, options?.syncCompression ?? false);
395
175
  })
396
176
  ]);
397
177
  const response2 = await this._api.run.completeUpload.POST({
398
178
  upload_token: response.result.upload_token
399
179
  }).then((result) => ({ result, error: void 0 })).catch((e) => ({ error: e, result: void 0 }));
400
- const url = response2?.result?.report_url ? new URL2(response2?.result.report_url, this._options.flakinessEndpoint).toString() : void 0;
180
+ const url = response2?.result?.report_url ? new URL(response2?.result.report_url, this._options.flakinessEndpoint).toString() : void 0;
401
181
  return { success: true, reportUrl: url };
402
182
  }
403
183
  async _uploadReport(data, uploadUrl, syncCompression) {
404
- const compressed = syncCompression ? brotliCompressSync2(data) : await brotliCompressAsync(data);
184
+ const compressed = syncCompression ? compressTextSync(data) : await compressTextAsync(data);
405
185
  const headers = {
406
186
  "Content-Type": "application/json",
407
187
  "Content-Length": Buffer.byteLength(compressed) + "",
@@ -418,11 +198,34 @@ var ReportUpload = class {
418
198
  await responseDataPromise;
419
199
  }, HTTP_BACKOFF);
420
200
  }
421
- async _uploadAttachment(attachment, uploadUrl) {
422
- const bytesLength = attachment.path ? (await fs.promises.stat(attachment.path)).size : attachment.body ? Buffer.byteLength(attachment.body) : 0;
201
+ async _uploadAttachment(attachment, uploadUrl, syncCompression) {
202
+ const mimeType = attachment.contentType.toLocaleLowerCase().trim();
203
+ const compressable = mimeType.startsWith("text/") || mimeType.endsWith("+json") || mimeType.endsWith("+text") || mimeType.endsWith("+xml");
204
+ if (!compressable && attachment.path) {
205
+ const attachmentPath = attachment.path;
206
+ await retryWithBackoff(async () => {
207
+ const { request, responseDataPromise } = httpUtils.createRequest({
208
+ url: uploadUrl,
209
+ headers: {
210
+ "Content-Type": attachment.contentType,
211
+ "Content-Length": (await fs.promises.stat(attachmentPath)).size + ""
212
+ },
213
+ method: "put"
214
+ });
215
+ fs.createReadStream(attachmentPath).pipe(request);
216
+ await responseDataPromise;
217
+ }, HTTP_BACKOFF);
218
+ return;
219
+ }
220
+ let buffer = attachment.body ? attachment.body : attachment.path ? await fs.promises.readFile(attachment.path) : void 0;
221
+ assert(buffer);
222
+ const encoding = compressable ? "br" : void 0;
223
+ if (compressable)
224
+ buffer = syncCompression ? compressTextSync(buffer) : await compressTextAsync(buffer);
423
225
  const headers = {
424
226
  "Content-Type": attachment.contentType,
425
- "Content-Length": bytesLength + ""
227
+ "Content-Length": Buffer.byteLength(buffer) + "",
228
+ "Content-Encoding": encoding
426
229
  };
427
230
  await retryWithBackoff(async () => {
428
231
  const { request, responseDataPromise } = httpUtils.createRequest({
@@ -430,13 +233,8 @@ var ReportUpload = class {
430
233
  headers,
431
234
  method: "put"
432
235
  });
433
- if (attachment.path) {
434
- fs.createReadStream(attachment.path).pipe(request);
435
- } else {
436
- if (attachment.body)
437
- request.write(attachment.body);
438
- request.end();
439
- }
236
+ request.write(buffer);
237
+ request.end();
440
238
  await responseDataPromise;
441
239
  }, HTTP_BACKOFF);
442
240
  }
package/lib/serverapi.js CHANGED
@@ -1,244 +1,23 @@
1
- // ../server/lib/common/typedHttp.js
2
- var TypedHTTP;
3
- ((TypedHTTP2) => {
4
- TypedHTTP2.StatusCodes = {
5
- Informational: {
6
- CONTINUE: 100,
7
- SWITCHING_PROTOCOLS: 101,
8
- PROCESSING: 102,
9
- EARLY_HINTS: 103
10
- },
11
- Success: {
12
- OK: 200,
13
- CREATED: 201,
14
- ACCEPTED: 202,
15
- NON_AUTHORITATIVE_INFORMATION: 203,
16
- NO_CONTENT: 204,
17
- RESET_CONTENT: 205,
18
- PARTIAL_CONTENT: 206,
19
- MULTI_STATUS: 207
20
- },
21
- Redirection: {
22
- MULTIPLE_CHOICES: 300,
23
- MOVED_PERMANENTLY: 301,
24
- MOVED_TEMPORARILY: 302,
25
- SEE_OTHER: 303,
26
- NOT_MODIFIED: 304,
27
- USE_PROXY: 305,
28
- TEMPORARY_REDIRECT: 307,
29
- PERMANENT_REDIRECT: 308
30
- },
31
- ClientErrors: {
32
- BAD_REQUEST: 400,
33
- UNAUTHORIZED: 401,
34
- PAYMENT_REQUIRED: 402,
35
- FORBIDDEN: 403,
36
- NOT_FOUND: 404,
37
- METHOD_NOT_ALLOWED: 405,
38
- NOT_ACCEPTABLE: 406,
39
- PROXY_AUTHENTICATION_REQUIRED: 407,
40
- REQUEST_TIMEOUT: 408,
41
- CONFLICT: 409,
42
- GONE: 410,
43
- LENGTH_REQUIRED: 411,
44
- PRECONDITION_FAILED: 412,
45
- REQUEST_TOO_LONG: 413,
46
- REQUEST_URI_TOO_LONG: 414,
47
- UNSUPPORTED_MEDIA_TYPE: 415,
48
- REQUESTED_RANGE_NOT_SATISFIABLE: 416,
49
- EXPECTATION_FAILED: 417,
50
- IM_A_TEAPOT: 418,
51
- INSUFFICIENT_SPACE_ON_RESOURCE: 419,
52
- METHOD_FAILURE: 420,
53
- MISDIRECTED_REQUEST: 421,
54
- UNPROCESSABLE_ENTITY: 422,
55
- LOCKED: 423,
56
- FAILED_DEPENDENCY: 424,
57
- UPGRADE_REQUIRED: 426,
58
- PRECONDITION_REQUIRED: 428,
59
- TOO_MANY_REQUESTS: 429,
60
- REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
61
- UNAVAILABLE_FOR_LEGAL_REASONS: 451
62
- },
63
- ServerErrors: {
64
- INTERNAL_SERVER_ERROR: 500,
65
- NOT_IMPLEMENTED: 501,
66
- BAD_GATEWAY: 502,
67
- SERVICE_UNAVAILABLE: 503,
68
- GATEWAY_TIMEOUT: 504,
69
- HTTP_VERSION_NOT_SUPPORTED: 505,
70
- INSUFFICIENT_STORAGE: 507,
71
- NETWORK_AUTHENTICATION_REQUIRED: 511
72
- }
73
- };
74
- const AllErrorCodes = {
75
- ...TypedHTTP2.StatusCodes.ClientErrors,
76
- ...TypedHTTP2.StatusCodes.ServerErrors
77
- };
78
- class HttpError extends Error {
79
- constructor(status, message) {
80
- super(message);
81
- this.status = status;
82
- }
83
- static withCode(code, message) {
84
- const statusCode = AllErrorCodes[code];
85
- const defaultMessage = code.split("_").map((word) => word.charAt(0) + word.slice(1).toLowerCase()).join(" ");
86
- return new HttpError(statusCode, message ?? defaultMessage);
87
- }
88
- }
89
- TypedHTTP2.HttpError = HttpError;
90
- function isInformationalResponse(response) {
91
- return response.status >= 100 && response.status < 200;
92
- }
93
- TypedHTTP2.isInformationalResponse = isInformationalResponse;
94
- function isSuccessResponse(response) {
95
- return response.status >= 200 && response.status < 300;
96
- }
97
- TypedHTTP2.isSuccessResponse = isSuccessResponse;
98
- function isRedirectResponse(response) {
99
- return response.status >= 300 && response.status < 400;
100
- }
101
- TypedHTTP2.isRedirectResponse = isRedirectResponse;
102
- function isErrorResponse(response) {
103
- return response.status >= 400 && response.status < 600;
104
- }
105
- TypedHTTP2.isErrorResponse = isErrorResponse;
106
- function info(status) {
107
- return { status };
108
- }
109
- TypedHTTP2.info = info;
110
- function ok(data, status) {
111
- return {
112
- status: status ?? TypedHTTP2.StatusCodes.Success.OK,
113
- data
114
- };
115
- }
116
- TypedHTTP2.ok = ok;
117
- function redirect(url, status = 302) {
118
- return { status, url };
119
- }
120
- TypedHTTP2.redirect = redirect;
121
- function error(message, status = TypedHTTP2.StatusCodes.ServerErrors.INTERNAL_SERVER_ERROR) {
122
- return { status, message };
123
- }
124
- TypedHTTP2.error = error;
125
- class Router {
126
- constructor(_resolveContext) {
127
- this._resolveContext = _resolveContext;
128
- }
129
- static create() {
130
- return new Router(async (e) => e.ctx);
131
- }
132
- rawMethod(method, route) {
133
- return {
134
- [method]: {
135
- method,
136
- input: route.input,
137
- etag: route.etag,
138
- resolveContext: this._resolveContext,
139
- handler: route.handler
140
- }
141
- };
142
- }
143
- get(route) {
144
- return this.rawMethod("GET", {
145
- ...route,
146
- handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
147
- });
148
- }
149
- post(route) {
150
- return this.rawMethod("POST", {
151
- ...route,
152
- handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
153
- });
154
- }
155
- use(resolveContext) {
156
- return new Router(async (options) => {
157
- const m = await this._resolveContext(options);
158
- return resolveContext({ ...options, ctx: m });
159
- });
160
- }
161
- }
162
- TypedHTTP2.Router = Router;
163
- function createClient(base, fetchCallback) {
164
- function buildUrl(path, input, options) {
165
- const method = path.at(-1);
166
- const url = new URL(path.slice(0, path.length - 1).join("/"), base);
167
- const signal = options?.signal;
168
- let body = void 0;
169
- if (method === "GET" && input)
170
- url.searchParams.set("input", JSON.stringify(input));
171
- else if (method !== "GET" && input)
172
- body = JSON.stringify(input);
173
- return {
174
- url,
175
- method,
176
- headers: body ? { "Content-Type": "application/json" } : void 0,
177
- body,
178
- signal
179
- };
180
- }
181
- function createProxy(path = []) {
182
- return new Proxy(() => {
183
- }, {
184
- get(target, prop) {
185
- if (typeof prop === "symbol")
186
- return void 0;
187
- if (prop === "prepare")
188
- return (input, options) => buildUrl(path, input, options);
189
- const newPath = [...path, prop];
190
- return createProxy(newPath);
191
- },
192
- apply(target, thisArg, args) {
193
- const options = buildUrl(path, args[0], args[1]);
194
- return fetchCallback(options.url, {
195
- method: options.method,
196
- body: options.body,
197
- headers: options.headers,
198
- signal: options.signal
199
- }).then(async (response) => {
200
- if (response.status >= 200 && response.status < 300) {
201
- if (response.headers.get("content-type")?.includes("application/json")) {
202
- const text = await response.text();
203
- return text.length ? JSON.parse(text) : void 0;
204
- }
205
- return await response.blob();
206
- }
207
- if (response.status >= 400 && response.status < 600) {
208
- const text = await response.text();
209
- if (text)
210
- throw new Error(`HTTP request failed with status ${response.status}: ${text}`);
211
- else
212
- throw new Error(`HTTP request failed with status ${response.status}`);
213
- }
214
- });
215
- }
216
- });
217
- }
218
- return createProxy();
219
- }
220
- TypedHTTP2.createClient = createClient;
221
- })(TypedHTTP || (TypedHTTP = {}));
1
+ // src/serverapi.ts
2
+ import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
222
3
 
223
4
  // src/utils.ts
5
+ import { FlakinessReport } from "@flakiness/report";
224
6
  import http from "http";
225
7
  import https from "https";
226
- import util from "util";
227
- import zlib from "zlib";
228
- var gzipAsync = util.promisify(zlib.gzip);
229
- var gunzipAsync = util.promisify(zlib.gunzip);
230
- var gunzipSync = zlib.gunzipSync;
231
- var brotliCompressAsync = util.promisify(zlib.brotliCompress);
232
- var brotliCompressSync = zlib.brotliCompressSync;
8
+ var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
9
+ function errorText(error) {
10
+ return FLAKINESS_DBG ? error.stack : error.message;
11
+ }
233
12
  async function retryWithBackoff(job, backoff = []) {
234
13
  for (const timeout of backoff) {
235
14
  try {
236
15
  return await job();
237
16
  } catch (e) {
238
17
  if (e instanceof AggregateError)
239
- console.error(`[flakiness.io err]`, e.errors[0].message);
18
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
240
19
  else if (e instanceof Error)
241
- console.error(`[flakiness.io err]`, e.message);
20
+ console.error(`[flakiness.io err]`, errorText(e));
242
21
  else
243
22
  console.error(`[flakiness.io err]`, e);
244
23
  await new Promise((x) => setTimeout(x, timeout));
@@ -256,6 +35,7 @@ var httpUtils;
256
35
  reject = b;
257
36
  });
258
37
  const protocol = url.startsWith("https") ? https : http;
38
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
259
39
  const request = protocol.request(url, { method, headers }, (res) => {
260
40
  const chunks = [];
261
41
  res.on("data", (chunk) => chunks.push(chunk));