@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.
@@ -12,19 +12,13 @@ import fs2 from "fs";
12
12
  import path from "path";
13
13
 
14
14
  // src/utils.ts
15
+ import { FlakinessReport } from "@flakiness/report";
15
16
  import assert from "assert";
16
17
  import { spawnSync } from "child_process";
17
18
  import crypto from "crypto";
18
19
  import fs from "fs";
19
20
  import http from "http";
20
21
  import https from "https";
21
- import util from "util";
22
- import zlib from "zlib";
23
- var gzipAsync = util.promisify(zlib.gzip);
24
- var gunzipAsync = util.promisify(zlib.gunzip);
25
- var gunzipSync = zlib.gunzipSync;
26
- var brotliCompressAsync = util.promisify(zlib.brotliCompress);
27
- var brotliCompressSync = zlib.brotliCompressSync;
28
22
  function sha1File(filePath) {
29
23
  return new Promise((resolve, reject) => {
30
24
  const hash = crypto.createHash("sha1");
@@ -40,6 +34,10 @@ function sha1File(filePath) {
40
34
  });
41
35
  });
42
36
  }
37
+ var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
38
+ function errorText(error) {
39
+ return FLAKINESS_DBG ? error.stack : error.message;
40
+ }
43
41
  function sha1Buffer(data) {
44
42
  const hash = crypto.createHash("sha1");
45
43
  hash.update(data);
@@ -51,9 +49,9 @@ async function retryWithBackoff(job, backoff = []) {
51
49
  return await job();
52
50
  } catch (e) {
53
51
  if (e instanceof AggregateError)
54
- console.error(`[flakiness.io err]`, e.errors[0].message);
52
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
55
53
  else if (e instanceof Error)
56
- console.error(`[flakiness.io err]`, e.message);
54
+ console.error(`[flakiness.io err]`, errorText(e));
57
55
  else
58
56
  console.error(`[flakiness.io err]`, e);
59
57
  await new Promise((x) => setTimeout(x, timeout));
@@ -71,6 +69,7 @@ var httpUtils;
71
69
  reject = b;
72
70
  });
73
71
  const protocol = url.startsWith("https") ? https : http;
72
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
74
73
  const request = protocol.request(url, { method, headers }, (res) => {
75
74
  const chunks = [];
76
75
  res.on("data", (chunk) => chunks.push(chunk));
@@ -1,476 +1,26 @@
1
1
  // src/cli/cmd-download.ts
2
- import fs3 from "fs";
3
- import path3 from "path";
4
-
5
- // src/flakinessLink.ts
6
- import fs from "fs/promises";
2
+ import fs from "fs";
7
3
  import path from "path";
8
-
9
- // src/utils.ts
10
- import assert from "assert";
11
- import { spawnSync } from "child_process";
12
- import http from "http";
13
- import https from "https";
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;
22
- async function retryWithBackoff(job, backoff = []) {
23
- for (const timeout of backoff) {
24
- try {
25
- return await job();
26
- } catch (e) {
27
- if (e instanceof AggregateError)
28
- console.error(`[flakiness.io err]`, e.errors[0].message);
29
- else if (e instanceof Error)
30
- console.error(`[flakiness.io err]`, e.message);
31
- else
32
- console.error(`[flakiness.io err]`, e);
33
- await new Promise((x) => setTimeout(x, timeout));
34
- }
35
- }
36
- return await job();
37
- }
38
- var httpUtils;
39
- ((httpUtils2) => {
40
- function createRequest({ url, method = "get", headers = {} }) {
41
- let resolve;
42
- let reject;
43
- const responseDataPromise = new Promise((a, b) => {
44
- resolve = a;
45
- reject = b;
46
- });
47
- const protocol = url.startsWith("https") ? https : http;
48
- const request = protocol.request(url, { method, headers }, (res) => {
49
- const chunks = [];
50
- res.on("data", (chunk) => chunks.push(chunk));
51
- res.on("end", () => {
52
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300)
53
- resolve(Buffer.concat(chunks));
54
- else
55
- reject(new Error(`Request to ${url} failed with ${res.statusCode}`));
56
- });
57
- res.on("error", (error) => reject(error));
58
- });
59
- request.on("error", reject);
60
- return { request, responseDataPromise };
61
- }
62
- httpUtils2.createRequest = createRequest;
63
- async function getBuffer(url, backoff) {
64
- return await retryWithBackoff(async () => {
65
- const { request, responseDataPromise } = createRequest({ url });
66
- request.end();
67
- return await responseDataPromise;
68
- }, backoff);
69
- }
70
- httpUtils2.getBuffer = getBuffer;
71
- async function getText(url, backoff) {
72
- const buffer = await getBuffer(url, backoff);
73
- return buffer.toString("utf-8");
74
- }
75
- httpUtils2.getText = getText;
76
- async function getJSON(url) {
77
- return JSON.parse(await getText(url));
78
- }
79
- httpUtils2.getJSON = getJSON;
80
- async function postText(url, text, backoff) {
81
- const headers = {
82
- "Content-Type": "application/json",
83
- "Content-Length": Buffer.byteLength(text) + ""
84
- };
85
- return await retryWithBackoff(async () => {
86
- const { request, responseDataPromise } = createRequest({ url, headers, method: "post" });
87
- request.write(text);
88
- request.end();
89
- return await responseDataPromise;
90
- }, backoff);
91
- }
92
- httpUtils2.postText = postText;
93
- async function postJSON(url, json, backoff) {
94
- const buffer = await postText(url, JSON.stringify(json), backoff);
95
- return JSON.parse(buffer.toString("utf-8"));
96
- }
97
- httpUtils2.postJSON = postJSON;
98
- })(httpUtils || (httpUtils = {}));
99
- var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
100
- function shell(command, args, options) {
101
- try {
102
- const result = spawnSync(command, args, { encoding: "utf-8", ...options });
103
- if (result.status !== 0) {
104
- console.log(result);
105
- console.log(options);
106
- return void 0;
107
- }
108
- return result.stdout.trim();
109
- } catch (e) {
110
- console.log(e);
111
- return void 0;
112
- }
113
- }
114
- function computeGitRoot(somePathInsideGitRepo) {
115
- const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
116
- cwd: somePathInsideGitRepo,
117
- encoding: "utf-8"
118
- });
119
- assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
120
- return normalizePath(root);
121
- }
122
- var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
123
- var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
124
- function normalizePath(aPath) {
125
- if (IS_WIN32_PATH.test(aPath)) {
126
- aPath = aPath.split(win32Path.sep).join(posixPath.sep);
127
- }
128
- if (IS_ALMOST_POSIX_PATH.test(aPath))
129
- return "/" + aPath[0] + aPath.substring(2);
130
- return aPath;
131
- }
132
-
133
- // src/flakinessLink.ts
134
- var GIT_ROOT = computeGitRoot(process.cwd());
135
- var CONFIG_DIR = path.join(GIT_ROOT, ".flakiness");
136
- var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
137
- var FlakinessLink = class _FlakinessLink {
138
- constructor(_config) {
139
- this._config = _config;
140
- }
141
- static async load() {
142
- const data = await fs.readFile(CONFIG_PATH, "utf-8").catch((e) => void 0);
143
- if (!data)
144
- return void 0;
145
- const json = JSON.parse(data);
146
- return new _FlakinessLink(json);
147
- }
148
- static async remove() {
149
- await fs.unlink(CONFIG_PATH).catch((e) => void 0);
150
- }
151
- path() {
152
- return CONFIG_PATH;
153
- }
154
- projectId() {
155
- return this._config.projectId;
156
- }
157
- async save() {
158
- await fs.mkdir(CONFIG_DIR, { recursive: true });
159
- await fs.writeFile(CONFIG_PATH, JSON.stringify(this._config, null, 2));
160
- }
161
- };
162
-
163
- // src/flakinessSession.ts
164
- import fs2 from "fs/promises";
165
- import os from "os";
166
- import path2 from "path";
167
-
168
- // ../server/lib/common/typedHttp.js
169
- var TypedHTTP;
170
- ((TypedHTTP2) => {
171
- TypedHTTP2.StatusCodes = {
172
- Informational: {
173
- CONTINUE: 100,
174
- SWITCHING_PROTOCOLS: 101,
175
- PROCESSING: 102,
176
- EARLY_HINTS: 103
177
- },
178
- Success: {
179
- OK: 200,
180
- CREATED: 201,
181
- ACCEPTED: 202,
182
- NON_AUTHORITATIVE_INFORMATION: 203,
183
- NO_CONTENT: 204,
184
- RESET_CONTENT: 205,
185
- PARTIAL_CONTENT: 206,
186
- MULTI_STATUS: 207
187
- },
188
- Redirection: {
189
- MULTIPLE_CHOICES: 300,
190
- MOVED_PERMANENTLY: 301,
191
- MOVED_TEMPORARILY: 302,
192
- SEE_OTHER: 303,
193
- NOT_MODIFIED: 304,
194
- USE_PROXY: 305,
195
- TEMPORARY_REDIRECT: 307,
196
- PERMANENT_REDIRECT: 308
197
- },
198
- ClientErrors: {
199
- BAD_REQUEST: 400,
200
- UNAUTHORIZED: 401,
201
- PAYMENT_REQUIRED: 402,
202
- FORBIDDEN: 403,
203
- NOT_FOUND: 404,
204
- METHOD_NOT_ALLOWED: 405,
205
- NOT_ACCEPTABLE: 406,
206
- PROXY_AUTHENTICATION_REQUIRED: 407,
207
- REQUEST_TIMEOUT: 408,
208
- CONFLICT: 409,
209
- GONE: 410,
210
- LENGTH_REQUIRED: 411,
211
- PRECONDITION_FAILED: 412,
212
- REQUEST_TOO_LONG: 413,
213
- REQUEST_URI_TOO_LONG: 414,
214
- UNSUPPORTED_MEDIA_TYPE: 415,
215
- REQUESTED_RANGE_NOT_SATISFIABLE: 416,
216
- EXPECTATION_FAILED: 417,
217
- IM_A_TEAPOT: 418,
218
- INSUFFICIENT_SPACE_ON_RESOURCE: 419,
219
- METHOD_FAILURE: 420,
220
- MISDIRECTED_REQUEST: 421,
221
- UNPROCESSABLE_ENTITY: 422,
222
- LOCKED: 423,
223
- FAILED_DEPENDENCY: 424,
224
- UPGRADE_REQUIRED: 426,
225
- PRECONDITION_REQUIRED: 428,
226
- TOO_MANY_REQUESTS: 429,
227
- REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
228
- UNAVAILABLE_FOR_LEGAL_REASONS: 451
229
- },
230
- ServerErrors: {
231
- INTERNAL_SERVER_ERROR: 500,
232
- NOT_IMPLEMENTED: 501,
233
- BAD_GATEWAY: 502,
234
- SERVICE_UNAVAILABLE: 503,
235
- GATEWAY_TIMEOUT: 504,
236
- HTTP_VERSION_NOT_SUPPORTED: 505,
237
- INSUFFICIENT_STORAGE: 507,
238
- NETWORK_AUTHENTICATION_REQUIRED: 511
239
- }
240
- };
241
- const AllErrorCodes = {
242
- ...TypedHTTP2.StatusCodes.ClientErrors,
243
- ...TypedHTTP2.StatusCodes.ServerErrors
244
- };
245
- class HttpError extends Error {
246
- constructor(status, message) {
247
- super(message);
248
- this.status = status;
249
- }
250
- static withCode(code, message) {
251
- const statusCode = AllErrorCodes[code];
252
- const defaultMessage = code.split("_").map((word) => word.charAt(0) + word.slice(1).toLowerCase()).join(" ");
253
- return new HttpError(statusCode, message ?? defaultMessage);
254
- }
255
- }
256
- TypedHTTP2.HttpError = HttpError;
257
- function isInformationalResponse(response) {
258
- return response.status >= 100 && response.status < 200;
259
- }
260
- TypedHTTP2.isInformationalResponse = isInformationalResponse;
261
- function isSuccessResponse(response) {
262
- return response.status >= 200 && response.status < 300;
263
- }
264
- TypedHTTP2.isSuccessResponse = isSuccessResponse;
265
- function isRedirectResponse(response) {
266
- return response.status >= 300 && response.status < 400;
267
- }
268
- TypedHTTP2.isRedirectResponse = isRedirectResponse;
269
- function isErrorResponse(response) {
270
- return response.status >= 400 && response.status < 600;
271
- }
272
- TypedHTTP2.isErrorResponse = isErrorResponse;
273
- function info(status) {
274
- return { status };
275
- }
276
- TypedHTTP2.info = info;
277
- function ok(data, status) {
278
- return {
279
- status: status ?? TypedHTTP2.StatusCodes.Success.OK,
280
- data
281
- };
282
- }
283
- TypedHTTP2.ok = ok;
284
- function redirect(url, status = 302) {
285
- return { status, url };
286
- }
287
- TypedHTTP2.redirect = redirect;
288
- function error(message, status = TypedHTTP2.StatusCodes.ServerErrors.INTERNAL_SERVER_ERROR) {
289
- return { status, message };
290
- }
291
- TypedHTTP2.error = error;
292
- class Router {
293
- constructor(_resolveContext) {
294
- this._resolveContext = _resolveContext;
295
- }
296
- static create() {
297
- return new Router(async (e) => e.ctx);
298
- }
299
- rawMethod(method, route) {
300
- return {
301
- [method]: {
302
- method,
303
- input: route.input,
304
- etag: route.etag,
305
- resolveContext: this._resolveContext,
306
- handler: route.handler
307
- }
308
- };
309
- }
310
- get(route) {
311
- return this.rawMethod("GET", {
312
- ...route,
313
- handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
314
- });
315
- }
316
- post(route) {
317
- return this.rawMethod("POST", {
318
- ...route,
319
- handler: (...args) => Promise.resolve(route.handler(...args)).then((result) => TypedHTTP2.ok(result))
320
- });
321
- }
322
- use(resolveContext) {
323
- return new Router(async (options) => {
324
- const m = await this._resolveContext(options);
325
- return resolveContext({ ...options, ctx: m });
326
- });
327
- }
328
- }
329
- TypedHTTP2.Router = Router;
330
- function createClient(base, fetchCallback) {
331
- function buildUrl(path4, input, options) {
332
- const method = path4.at(-1);
333
- const url = new URL(path4.slice(0, path4.length - 1).join("/"), base);
334
- const signal = options?.signal;
335
- let body = void 0;
336
- if (method === "GET" && input)
337
- url.searchParams.set("input", JSON.stringify(input));
338
- else if (method !== "GET" && input)
339
- body = JSON.stringify(input);
340
- return {
341
- url,
342
- method,
343
- headers: body ? { "Content-Type": "application/json" } : void 0,
344
- body,
345
- signal
346
- };
347
- }
348
- function createProxy(path4 = []) {
349
- return new Proxy(() => {
350
- }, {
351
- get(target, prop) {
352
- if (typeof prop === "symbol")
353
- return void 0;
354
- if (prop === "prepare")
355
- return (input, options) => buildUrl(path4, input, options);
356
- const newPath = [...path4, prop];
357
- return createProxy(newPath);
358
- },
359
- apply(target, thisArg, args) {
360
- const options = buildUrl(path4, args[0], args[1]);
361
- return fetchCallback(options.url, {
362
- method: options.method,
363
- body: options.body,
364
- headers: options.headers,
365
- signal: options.signal
366
- }).then(async (response) => {
367
- if (response.status >= 200 && response.status < 300) {
368
- if (response.headers.get("content-type")?.includes("application/json")) {
369
- const text = await response.text();
370
- return text.length ? JSON.parse(text) : void 0;
371
- }
372
- return await response.blob();
373
- }
374
- if (response.status >= 400 && response.status < 600) {
375
- const text = await response.text();
376
- if (text)
377
- throw new Error(`HTTP request failed with status ${response.status}: ${text}`);
378
- else
379
- throw new Error(`HTTP request failed with status ${response.status}`);
380
- }
381
- });
382
- }
383
- });
384
- }
385
- return createProxy();
386
- }
387
- TypedHTTP2.createClient = createClient;
388
- })(TypedHTTP || (TypedHTTP = {}));
389
-
390
- // src/serverapi.ts
391
- function createServerAPI(endpoint, options) {
392
- endpoint += "/api/";
393
- const fetcher = options?.auth ? (url, init) => fetch(url, {
394
- ...init,
395
- headers: {
396
- ...init.headers,
397
- "Authorization": `Bearer ${options.auth}`
398
- }
399
- }) : fetch;
400
- if (options?.retries)
401
- return TypedHTTP.createClient(endpoint, (url, init) => retryWithBackoff(() => fetcher(url, init), options.retries));
402
- return TypedHTTP.createClient(endpoint, fetcher);
403
- }
404
-
405
- // src/flakinessSession.ts
406
- var CONFIG_DIR2 = (() => {
407
- const configDir = process.platform === "darwin" ? path2.join(os.homedir(), "Library", "Application Support", "flakiness") : process.platform === "win32" ? path2.join(os.homedir(), "AppData", "Roaming", "flakiness") : path2.join(os.homedir(), ".config", "flakiness");
408
- return configDir;
409
- })();
410
- var CONFIG_PATH2 = path2.join(CONFIG_DIR2, "config.json");
411
- var FlakinessSession = class _FlakinessSession {
412
- constructor(_config) {
413
- this._config = _config;
414
- this.api = createServerAPI(this._config.endpoint, { auth: this._config.token });
415
- }
416
- static async load() {
417
- const data = await fs2.readFile(CONFIG_PATH2, "utf-8").catch((e) => void 0);
418
- if (!data)
419
- return void 0;
420
- const json = JSON.parse(data);
421
- return new _FlakinessSession(json);
422
- }
423
- static async remove() {
424
- await fs2.unlink(CONFIG_PATH2).catch((e) => void 0);
425
- }
426
- api;
427
- endpoint() {
428
- return this._config.endpoint;
429
- }
430
- path() {
431
- return CONFIG_PATH2;
432
- }
433
- sessionToken() {
434
- return this._config.token;
435
- }
436
- async save() {
437
- await fs2.mkdir(CONFIG_DIR2, { recursive: true });
438
- await fs2.writeFile(CONFIG_PATH2, JSON.stringify(this._config, null, 2));
439
- }
440
- };
441
-
442
- // src/cli/cmd-download.ts
443
- async function cmdDownload(runId) {
444
- const session = await FlakinessSession.load();
445
- if (!session) {
446
- console.log(`Please login first`);
447
- process.exit(1);
448
- }
449
- const link = await FlakinessLink.load();
450
- if (!link) {
451
- console.log(`Please run 'npx flakiness link' to link to the project`);
452
- process.exit(1);
453
- }
454
- const project = await session.api.project.getProject.GET({ projectPublicId: link.projectId() });
4
+ async function cmdDownload(session, project, runId) {
455
5
  const urls = await session.api.run.downloadURLs.GET({
456
6
  orgSlug: project.org.orgSlug,
457
7
  projectSlug: project.projectSlug,
458
8
  runId
459
9
  });
460
10
  const rootDir = `fkrun-${runId}`;
461
- if (fs3.existsSync(rootDir)) {
11
+ if (fs.existsSync(rootDir)) {
462
12
  console.log(`Directory ${rootDir} already exists!`);
463
13
  return;
464
14
  }
465
- const attachmentsDir = path3.join(rootDir, "attachments");
466
- await fs3.promises.mkdir(rootDir, { recursive: true });
15
+ const attachmentsDir = path.join(rootDir, "attachments");
16
+ await fs.promises.mkdir(rootDir, { recursive: true });
467
17
  if (urls.attachmentURLs.length)
468
- await fs3.promises.mkdir(attachmentsDir, { recursive: true });
18
+ await fs.promises.mkdir(attachmentsDir, { recursive: true });
469
19
  const response = await fetch(urls.reportURL);
470
20
  if (!response.ok)
471
21
  throw new Error(`HTTP error ${response.status} for report URL: ${urls.reportURL}`);
472
22
  const reportContent = await response.text();
473
- await fs3.promises.writeFile(path3.join(rootDir, "report.json"), reportContent);
23
+ await fs.promises.writeFile(path.join(rootDir, "report.json"), reportContent);
474
24
  const attachmentDownloader = async () => {
475
25
  while (urls.attachmentURLs.length) {
476
26
  const url = urls.attachmentURLs.pop();
@@ -478,8 +28,8 @@ async function cmdDownload(runId) {
478
28
  if (!response2.ok)
479
29
  throw new Error(`HTTP error ${response2.status} for attachment URL: ${url}`);
480
30
  const fileBuffer = Buffer.from(await response2.arrayBuffer());
481
- const filename = path3.basename(new URL(url).pathname);
482
- await fs3.promises.writeFile(path3.join(attachmentsDir, filename), fileBuffer);
31
+ const filename = path.basename(new URL(url).pathname);
32
+ await fs.promises.writeFile(path.join(attachmentsDir, filename), fileBuffer);
483
33
  }
484
34
  };
485
35
  const workerPromises = [];