@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.
- package/lib/cli/cli.js +1199 -380
- package/lib/cli/cmd-convert.js +8 -9
- package/lib/cli/cmd-download.js +9 -459
- package/lib/cli/cmd-link.js +83 -268
- package/lib/cli/cmd-login.js +18 -230
- package/lib/cli/cmd-logout.js +22 -230
- package/lib/cli/cmd-serve.js +479 -1
- package/lib/cli/cmd-status.js +83 -268
- package/lib/cli/cmd-unlink.js +67 -32
- package/lib/cli/cmd-upload-playwright-json.js +49 -253
- package/lib/cli/cmd-upload.js +90 -287
- package/lib/cli/cmd-whoami.js +16 -230
- package/lib/{flakinessLink.js → flakinessConfig.js} +64 -33
- package/lib/flakinessSession.js +16 -230
- package/lib/junit.js +8 -9
- package/lib/localGit.js +43 -0
- package/lib/localReportApi.js +40 -0
- package/lib/localReportServer.js +300 -0
- package/lib/playwright-test.js +470 -309
- package/lib/playwrightJSONReport.js +11 -12
- package/lib/reportUploader.js +45 -247
- package/lib/serverapi.js +10 -230
- package/lib/utils.js +45 -15
- package/package.json +15 -5
- package/types/tsconfig.tsbuildinfo +1 -1
package/lib/cli/cmd-whoami.js
CHANGED
|
@@ -5,247 +5,26 @@ import fs from "fs/promises";
|
|
|
5
5
|
import os from "os";
|
|
6
6
|
import path from "path";
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
((TypedHTTP2) => {
|
|
11
|
-
TypedHTTP2.StatusCodes = {
|
|
12
|
-
Informational: {
|
|
13
|
-
CONTINUE: 100,
|
|
14
|
-
SWITCHING_PROTOCOLS: 101,
|
|
15
|
-
PROCESSING: 102,
|
|
16
|
-
EARLY_HINTS: 103
|
|
17
|
-
},
|
|
18
|
-
Success: {
|
|
19
|
-
OK: 200,
|
|
20
|
-
CREATED: 201,
|
|
21
|
-
ACCEPTED: 202,
|
|
22
|
-
NON_AUTHORITATIVE_INFORMATION: 203,
|
|
23
|
-
NO_CONTENT: 204,
|
|
24
|
-
RESET_CONTENT: 205,
|
|
25
|
-
PARTIAL_CONTENT: 206,
|
|
26
|
-
MULTI_STATUS: 207
|
|
27
|
-
},
|
|
28
|
-
Redirection: {
|
|
29
|
-
MULTIPLE_CHOICES: 300,
|
|
30
|
-
MOVED_PERMANENTLY: 301,
|
|
31
|
-
MOVED_TEMPORARILY: 302,
|
|
32
|
-
SEE_OTHER: 303,
|
|
33
|
-
NOT_MODIFIED: 304,
|
|
34
|
-
USE_PROXY: 305,
|
|
35
|
-
TEMPORARY_REDIRECT: 307,
|
|
36
|
-
PERMANENT_REDIRECT: 308
|
|
37
|
-
},
|
|
38
|
-
ClientErrors: {
|
|
39
|
-
BAD_REQUEST: 400,
|
|
40
|
-
UNAUTHORIZED: 401,
|
|
41
|
-
PAYMENT_REQUIRED: 402,
|
|
42
|
-
FORBIDDEN: 403,
|
|
43
|
-
NOT_FOUND: 404,
|
|
44
|
-
METHOD_NOT_ALLOWED: 405,
|
|
45
|
-
NOT_ACCEPTABLE: 406,
|
|
46
|
-
PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
47
|
-
REQUEST_TIMEOUT: 408,
|
|
48
|
-
CONFLICT: 409,
|
|
49
|
-
GONE: 410,
|
|
50
|
-
LENGTH_REQUIRED: 411,
|
|
51
|
-
PRECONDITION_FAILED: 412,
|
|
52
|
-
REQUEST_TOO_LONG: 413,
|
|
53
|
-
REQUEST_URI_TOO_LONG: 414,
|
|
54
|
-
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
55
|
-
REQUESTED_RANGE_NOT_SATISFIABLE: 416,
|
|
56
|
-
EXPECTATION_FAILED: 417,
|
|
57
|
-
IM_A_TEAPOT: 418,
|
|
58
|
-
INSUFFICIENT_SPACE_ON_RESOURCE: 419,
|
|
59
|
-
METHOD_FAILURE: 420,
|
|
60
|
-
MISDIRECTED_REQUEST: 421,
|
|
61
|
-
UNPROCESSABLE_ENTITY: 422,
|
|
62
|
-
LOCKED: 423,
|
|
63
|
-
FAILED_DEPENDENCY: 424,
|
|
64
|
-
UPGRADE_REQUIRED: 426,
|
|
65
|
-
PRECONDITION_REQUIRED: 428,
|
|
66
|
-
TOO_MANY_REQUESTS: 429,
|
|
67
|
-
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
|
68
|
-
UNAVAILABLE_FOR_LEGAL_REASONS: 451
|
|
69
|
-
},
|
|
70
|
-
ServerErrors: {
|
|
71
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
72
|
-
NOT_IMPLEMENTED: 501,
|
|
73
|
-
BAD_GATEWAY: 502,
|
|
74
|
-
SERVICE_UNAVAILABLE: 503,
|
|
75
|
-
GATEWAY_TIMEOUT: 504,
|
|
76
|
-
HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
77
|
-
INSUFFICIENT_STORAGE: 507,
|
|
78
|
-
NETWORK_AUTHENTICATION_REQUIRED: 511
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
const AllErrorCodes = {
|
|
82
|
-
...TypedHTTP2.StatusCodes.ClientErrors,
|
|
83
|
-
...TypedHTTP2.StatusCodes.ServerErrors
|
|
84
|
-
};
|
|
85
|
-
class HttpError extends Error {
|
|
86
|
-
constructor(status, message) {
|
|
87
|
-
super(message);
|
|
88
|
-
this.status = status;
|
|
89
|
-
}
|
|
90
|
-
static withCode(code, message) {
|
|
91
|
-
const statusCode = AllErrorCodes[code];
|
|
92
|
-
const defaultMessage = code.split("_").map((word) => word.charAt(0) + word.slice(1).toLowerCase()).join(" ");
|
|
93
|
-
return new HttpError(statusCode, message ?? defaultMessage);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
TypedHTTP2.HttpError = HttpError;
|
|
97
|
-
function isInformationalResponse(response) {
|
|
98
|
-
return response.status >= 100 && response.status < 200;
|
|
99
|
-
}
|
|
100
|
-
TypedHTTP2.isInformationalResponse = isInformationalResponse;
|
|
101
|
-
function isSuccessResponse(response) {
|
|
102
|
-
return response.status >= 200 && response.status < 300;
|
|
103
|
-
}
|
|
104
|
-
TypedHTTP2.isSuccessResponse = isSuccessResponse;
|
|
105
|
-
function isRedirectResponse(response) {
|
|
106
|
-
return response.status >= 300 && response.status < 400;
|
|
107
|
-
}
|
|
108
|
-
TypedHTTP2.isRedirectResponse = isRedirectResponse;
|
|
109
|
-
function isErrorResponse(response) {
|
|
110
|
-
return response.status >= 400 && response.status < 600;
|
|
111
|
-
}
|
|
112
|
-
TypedHTTP2.isErrorResponse = isErrorResponse;
|
|
113
|
-
function info(status) {
|
|
114
|
-
return { status };
|
|
115
|
-
}
|
|
116
|
-
TypedHTTP2.info = info;
|
|
117
|
-
function ok(data, status) {
|
|
118
|
-
return {
|
|
119
|
-
status: status ?? TypedHTTP2.StatusCodes.Success.OK,
|
|
120
|
-
data
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
TypedHTTP2.ok = ok;
|
|
124
|
-
function redirect(url, status = 302) {
|
|
125
|
-
return { status, url };
|
|
126
|
-
}
|
|
127
|
-
TypedHTTP2.redirect = redirect;
|
|
128
|
-
function error(message, status = TypedHTTP2.StatusCodes.ServerErrors.INTERNAL_SERVER_ERROR) {
|
|
129
|
-
return { status, message };
|
|
130
|
-
}
|
|
131
|
-
TypedHTTP2.error = error;
|
|
132
|
-
class Router {
|
|
133
|
-
constructor(_resolveContext) {
|
|
134
|
-
this._resolveContext = _resolveContext;
|
|
135
|
-
}
|
|
136
|
-
static create() {
|
|
137
|
-
return new Router(async (e) => e.ctx);
|
|
138
|
-
}
|
|
139
|
-
rawMethod(method, route) {
|
|
140
|
-
return {
|
|
141
|
-
[method]: {
|
|
142
|
-
method,
|
|
143
|
-
input: route.input,
|
|
144
|
-
etag: route.etag,
|
|
145
|
-
resolveContext: this._resolveContext,
|
|
146
|
-
handler: route.handler
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
get(route) {
|
|
151
|
-
return this.rawMethod("GET", {
|
|
152
|
-
...route,
|
|
153
|
-
handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
post(route) {
|
|
157
|
-
return this.rawMethod("POST", {
|
|
158
|
-
...route,
|
|
159
|
-
handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
use(resolveContext) {
|
|
163
|
-
return new Router(async (options) => {
|
|
164
|
-
const m = await this._resolveContext(options);
|
|
165
|
-
return resolveContext({ ...options, ctx: m });
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
TypedHTTP2.Router = Router;
|
|
170
|
-
function createClient(base, fetchCallback) {
|
|
171
|
-
function buildUrl(path2, input, options) {
|
|
172
|
-
const method = path2.at(-1);
|
|
173
|
-
const url = new URL(path2.slice(0, path2.length - 1).join("/"), base);
|
|
174
|
-
const signal = options?.signal;
|
|
175
|
-
let body = void 0;
|
|
176
|
-
if (method === "GET" && input)
|
|
177
|
-
url.searchParams.set("input", JSON.stringify(input));
|
|
178
|
-
else if (method !== "GET" && input)
|
|
179
|
-
body = JSON.stringify(input);
|
|
180
|
-
return {
|
|
181
|
-
url,
|
|
182
|
-
method,
|
|
183
|
-
headers: body ? { "Content-Type": "application/json" } : void 0,
|
|
184
|
-
body,
|
|
185
|
-
signal
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function createProxy(path2 = []) {
|
|
189
|
-
return new Proxy(() => {
|
|
190
|
-
}, {
|
|
191
|
-
get(target, prop) {
|
|
192
|
-
if (typeof prop === "symbol")
|
|
193
|
-
return void 0;
|
|
194
|
-
if (prop === "prepare")
|
|
195
|
-
return (input, options) => buildUrl(path2, input, options);
|
|
196
|
-
const newPath = [...path2, prop];
|
|
197
|
-
return createProxy(newPath);
|
|
198
|
-
},
|
|
199
|
-
apply(target, thisArg, args) {
|
|
200
|
-
const options = buildUrl(path2, args[0], args[1]);
|
|
201
|
-
return fetchCallback(options.url, {
|
|
202
|
-
method: options.method,
|
|
203
|
-
body: options.body,
|
|
204
|
-
headers: options.headers,
|
|
205
|
-
signal: options.signal
|
|
206
|
-
}).then(async (response) => {
|
|
207
|
-
if (response.status >= 200 && response.status < 300) {
|
|
208
|
-
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
209
|
-
const text = await response.text();
|
|
210
|
-
return text.length ? JSON.parse(text) : void 0;
|
|
211
|
-
}
|
|
212
|
-
return await response.blob();
|
|
213
|
-
}
|
|
214
|
-
if (response.status >= 400 && response.status < 600) {
|
|
215
|
-
const text = await response.text();
|
|
216
|
-
if (text)
|
|
217
|
-
throw new Error(`HTTP request failed with status ${response.status}: ${text}`);
|
|
218
|
-
else
|
|
219
|
-
throw new Error(`HTTP request failed with status ${response.status}`);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
return createProxy();
|
|
226
|
-
}
|
|
227
|
-
TypedHTTP2.createClient = createClient;
|
|
228
|
-
})(TypedHTTP || (TypedHTTP = {}));
|
|
8
|
+
// src/serverapi.ts
|
|
9
|
+
import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
|
|
229
10
|
|
|
230
11
|
// src/utils.ts
|
|
12
|
+
import { FlakinessReport } from "@flakiness/report";
|
|
231
13
|
import http from "http";
|
|
232
14
|
import https from "https";
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
var gunzipSync = zlib.gunzipSync;
|
|
238
|
-
var brotliCompressAsync = util.promisify(zlib.brotliCompress);
|
|
239
|
-
var brotliCompressSync = zlib.brotliCompressSync;
|
|
15
|
+
var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
|
|
16
|
+
function errorText(error) {
|
|
17
|
+
return FLAKINESS_DBG ? error.stack : error.message;
|
|
18
|
+
}
|
|
240
19
|
async function retryWithBackoff(job, backoff = []) {
|
|
241
20
|
for (const timeout of backoff) {
|
|
242
21
|
try {
|
|
243
22
|
return await job();
|
|
244
23
|
} catch (e) {
|
|
245
24
|
if (e instanceof AggregateError)
|
|
246
|
-
console.error(`[flakiness.io err]`, e.errors[0]
|
|
25
|
+
console.error(`[flakiness.io err]`, errorText(e.errors[0]));
|
|
247
26
|
else if (e instanceof Error)
|
|
248
|
-
console.error(`[flakiness.io err]`, e
|
|
27
|
+
console.error(`[flakiness.io err]`, errorText(e));
|
|
249
28
|
else
|
|
250
29
|
console.error(`[flakiness.io err]`, e);
|
|
251
30
|
await new Promise((x) => setTimeout(x, timeout));
|
|
@@ -263,6 +42,7 @@ var httpUtils;
|
|
|
263
42
|
reject = b;
|
|
264
43
|
});
|
|
265
44
|
const protocol = url.startsWith("https") ? https : http;
|
|
45
|
+
headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
|
|
266
46
|
const request = protocol.request(url, { method, headers }, (res) => {
|
|
267
47
|
const chunks = [];
|
|
268
48
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -344,6 +124,12 @@ var FlakinessSession = class _FlakinessSession {
|
|
|
344
124
|
this._config = _config;
|
|
345
125
|
this.api = createServerAPI(this._config.endpoint, { auth: this._config.token });
|
|
346
126
|
}
|
|
127
|
+
static async loadOrDie() {
|
|
128
|
+
const session = await _FlakinessSession.load();
|
|
129
|
+
if (!session)
|
|
130
|
+
throw new Error(`Please login first with 'npx flakiness login'`);
|
|
131
|
+
return session;
|
|
132
|
+
}
|
|
347
133
|
static async load() {
|
|
348
134
|
const data = await fs.readFile(CONFIG_PATH, "utf-8").catch((e) => void 0);
|
|
349
135
|
if (!data)
|
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import fs from "fs
|
|
3
|
-
import
|
|
1
|
+
// src/flakinessConfig.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path2 from "path";
|
|
4
4
|
|
|
5
5
|
// src/utils.ts
|
|
6
|
+
import { FlakinessReport } from "@flakiness/report";
|
|
6
7
|
import assert from "assert";
|
|
7
8
|
import { spawnSync } from "child_process";
|
|
8
9
|
import http from "http";
|
|
9
10
|
import https from "https";
|
|
10
|
-
import { posix as posixPath, win32 as win32Path } from "path";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var gunzipSync = zlib.gunzipSync;
|
|
16
|
-
var brotliCompressAsync = util.promisify(zlib.brotliCompress);
|
|
17
|
-
var brotliCompressSync = zlib.brotliCompressSync;
|
|
11
|
+
import path, { posix as posixPath, win32 as win32Path } from "path";
|
|
12
|
+
var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
|
|
13
|
+
function errorText(error) {
|
|
14
|
+
return FLAKINESS_DBG ? error.stack : error.message;
|
|
15
|
+
}
|
|
18
16
|
async function retryWithBackoff(job, backoff = []) {
|
|
19
17
|
for (const timeout of backoff) {
|
|
20
18
|
try {
|
|
21
19
|
return await job();
|
|
22
20
|
} catch (e) {
|
|
23
21
|
if (e instanceof AggregateError)
|
|
24
|
-
console.error(`[flakiness.io err]`, e.errors[0]
|
|
22
|
+
console.error(`[flakiness.io err]`, errorText(e.errors[0]));
|
|
25
23
|
else if (e instanceof Error)
|
|
26
|
-
console.error(`[flakiness.io err]`, e
|
|
24
|
+
console.error(`[flakiness.io err]`, errorText(e));
|
|
27
25
|
else
|
|
28
26
|
console.error(`[flakiness.io err]`, e);
|
|
29
27
|
await new Promise((x) => setTimeout(x, timeout));
|
|
@@ -41,6 +39,7 @@ var httpUtils;
|
|
|
41
39
|
reject = b;
|
|
42
40
|
});
|
|
43
41
|
const protocol = url.startsWith("https") ? https : http;
|
|
42
|
+
headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
|
|
44
43
|
const request = protocol.request(url, { method, headers }, (res) => {
|
|
45
44
|
const chunks = [];
|
|
46
45
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -126,36 +125,68 @@ function normalizePath(aPath) {
|
|
|
126
125
|
return aPath;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
// src/
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
var
|
|
134
|
-
|
|
128
|
+
// src/flakinessConfig.ts
|
|
129
|
+
function createConfigPath(dir) {
|
|
130
|
+
return path2.join(dir, ".flakiness", "config.json");
|
|
131
|
+
}
|
|
132
|
+
var gConfigPath;
|
|
133
|
+
function ensureConfigPath() {
|
|
134
|
+
if (!gConfigPath)
|
|
135
|
+
gConfigPath = computeConfigPath();
|
|
136
|
+
return gConfigPath;
|
|
137
|
+
}
|
|
138
|
+
function computeConfigPath() {
|
|
139
|
+
for (let p = process.cwd(); p !== path2.resolve(p, ".."); p = path2.resolve(p, "..")) {
|
|
140
|
+
const configPath = createConfigPath(p);
|
|
141
|
+
if (fs.existsSync(configPath))
|
|
142
|
+
return configPath;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const gitRoot = computeGitRoot(process.cwd());
|
|
146
|
+
return createConfigPath(gitRoot);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return createConfigPath(process.cwd());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
var FlakinessConfig = class _FlakinessConfig {
|
|
152
|
+
constructor(_configPath, _config) {
|
|
153
|
+
this._configPath = _configPath;
|
|
135
154
|
this._config = _config;
|
|
136
155
|
}
|
|
137
156
|
static async load() {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return new _FlakinessLink(json);
|
|
157
|
+
const configPath = ensureConfigPath();
|
|
158
|
+
const data = await fs.promises.readFile(configPath, "utf-8").catch((e) => void 0);
|
|
159
|
+
const json = data ? JSON.parse(data) : {};
|
|
160
|
+
return new _FlakinessConfig(configPath, json);
|
|
143
161
|
}
|
|
144
|
-
static async
|
|
145
|
-
await
|
|
162
|
+
static async projectOrDie(session) {
|
|
163
|
+
const config = await _FlakinessConfig.load();
|
|
164
|
+
const projectPublicId = config.projectPublicId();
|
|
165
|
+
if (!projectPublicId)
|
|
166
|
+
throw new Error(`Please link to flakiness project with 'npx flakiness link'`);
|
|
167
|
+
const project = await session.api.project.getProject.GET({ projectPublicId }).catch((e) => void 0);
|
|
168
|
+
if (!project)
|
|
169
|
+
throw new Error(`Failed to fetch linked project; please re-link with 'npx flakiness link'`);
|
|
170
|
+
return project;
|
|
171
|
+
}
|
|
172
|
+
static createEmpty() {
|
|
173
|
+
return new _FlakinessConfig(ensureConfigPath(), {});
|
|
146
174
|
}
|
|
147
175
|
path() {
|
|
148
|
-
return
|
|
176
|
+
return this._configPath;
|
|
177
|
+
}
|
|
178
|
+
projectPublicId() {
|
|
179
|
+
return this._config.projectPublicId;
|
|
149
180
|
}
|
|
150
|
-
projectId
|
|
151
|
-
|
|
181
|
+
setProjectPublicId(projectId) {
|
|
182
|
+
this._config.projectPublicId = projectId;
|
|
152
183
|
}
|
|
153
184
|
async save() {
|
|
154
|
-
await fs.mkdir(
|
|
155
|
-
await fs.writeFile(
|
|
185
|
+
await fs.promises.mkdir(path2.dirname(this._configPath), { recursive: true });
|
|
186
|
+
await fs.promises.writeFile(this._configPath, JSON.stringify(this._config, null, 2));
|
|
156
187
|
}
|
|
157
188
|
};
|
|
158
189
|
export {
|
|
159
|
-
|
|
190
|
+
FlakinessConfig
|
|
160
191
|
};
|
|
161
|
-
//# sourceMappingURL=
|
|
192
|
+
//# sourceMappingURL=flakinessConfig.js.map
|
package/lib/flakinessSession.js
CHANGED
|
@@ -3,247 +3,26 @@ import fs from "fs/promises";
|
|
|
3
3
|
import os from "os";
|
|
4
4
|
import path from "path";
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
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(path2, input, options) {
|
|
170
|
-
const method = path2.at(-1);
|
|
171
|
-
const url = new URL(path2.slice(0, path2.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(path2 = []) {
|
|
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(path2, input, options);
|
|
194
|
-
const newPath = [...path2, prop];
|
|
195
|
-
return createProxy(newPath);
|
|
196
|
-
},
|
|
197
|
-
apply(target, thisArg, args) {
|
|
198
|
-
const options = buildUrl(path2, 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 = {}));
|
|
6
|
+
// src/serverapi.ts
|
|
7
|
+
import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
|
|
227
8
|
|
|
228
9
|
// src/utils.ts
|
|
10
|
+
import { FlakinessReport } from "@flakiness/report";
|
|
229
11
|
import http from "http";
|
|
230
12
|
import https from "https";
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
var gunzipSync = zlib.gunzipSync;
|
|
236
|
-
var brotliCompressAsync = util.promisify(zlib.brotliCompress);
|
|
237
|
-
var brotliCompressSync = zlib.brotliCompressSync;
|
|
13
|
+
var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
|
|
14
|
+
function errorText(error) {
|
|
15
|
+
return FLAKINESS_DBG ? error.stack : error.message;
|
|
16
|
+
}
|
|
238
17
|
async function retryWithBackoff(job, backoff = []) {
|
|
239
18
|
for (const timeout of backoff) {
|
|
240
19
|
try {
|
|
241
20
|
return await job();
|
|
242
21
|
} catch (e) {
|
|
243
22
|
if (e instanceof AggregateError)
|
|
244
|
-
console.error(`[flakiness.io err]`, e.errors[0]
|
|
23
|
+
console.error(`[flakiness.io err]`, errorText(e.errors[0]));
|
|
245
24
|
else if (e instanceof Error)
|
|
246
|
-
console.error(`[flakiness.io err]`, e
|
|
25
|
+
console.error(`[flakiness.io err]`, errorText(e));
|
|
247
26
|
else
|
|
248
27
|
console.error(`[flakiness.io err]`, e);
|
|
249
28
|
await new Promise((x) => setTimeout(x, timeout));
|
|
@@ -261,6 +40,7 @@ var httpUtils;
|
|
|
261
40
|
reject = b;
|
|
262
41
|
});
|
|
263
42
|
const protocol = url.startsWith("https") ? https : http;
|
|
43
|
+
headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
|
|
264
44
|
const request = protocol.request(url, { method, headers }, (res) => {
|
|
265
45
|
const chunks = [];
|
|
266
46
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -342,6 +122,12 @@ var FlakinessSession = class _FlakinessSession {
|
|
|
342
122
|
this._config = _config;
|
|
343
123
|
this.api = createServerAPI(this._config.endpoint, { auth: this._config.token });
|
|
344
124
|
}
|
|
125
|
+
static async loadOrDie() {
|
|
126
|
+
const session = await _FlakinessSession.load();
|
|
127
|
+
if (!session)
|
|
128
|
+
throw new Error(`Please login first with 'npx flakiness login'`);
|
|
129
|
+
return session;
|
|
130
|
+
}
|
|
345
131
|
static async load() {
|
|
346
132
|
const data = await fs.readFile(CONFIG_PATH, "utf-8").catch((e) => void 0);
|
|
347
133
|
if (!data)
|