@flakiness/sdk 0.95.0 → 0.97.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.
@@ -3,247 +3,26 @@ import fs from "fs/promises";
3
3
  import os from "os";
4
4
  import path from "path";
5
5
 
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(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
- 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;
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].message);
23
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
245
24
  else if (e instanceof Error)
246
- console.error(`[flakiness.io err]`, e.message);
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)
package/lib/junit.js CHANGED
@@ -6,17 +6,11 @@ import fs2 from "fs";
6
6
  import path from "path";
7
7
 
8
8
  // src/utils.ts
9
+ import { FlakinessReport } from "@flakiness/report";
9
10
  import crypto from "crypto";
10
11
  import fs from "fs";
11
12
  import http from "http";
12
13
  import https from "https";
13
- import util from "util";
14
- import zlib from "zlib";
15
- var gzipAsync = util.promisify(zlib.gzip);
16
- var gunzipAsync = util.promisify(zlib.gunzip);
17
- var gunzipSync = zlib.gunzipSync;
18
- var brotliCompressAsync = util.promisify(zlib.brotliCompress);
19
- var brotliCompressSync = zlib.brotliCompressSync;
20
14
  function sha1File(filePath) {
21
15
  return new Promise((resolve, reject) => {
22
16
  const hash = crypto.createHash("sha1");
@@ -32,6 +26,10 @@ function sha1File(filePath) {
32
26
  });
33
27
  });
34
28
  }
29
+ var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
30
+ function errorText(error) {
31
+ return FLAKINESS_DBG ? error.stack : error.message;
32
+ }
35
33
  function sha1Buffer(data) {
36
34
  const hash = crypto.createHash("sha1");
37
35
  hash.update(data);
@@ -43,9 +41,9 @@ async function retryWithBackoff(job, backoff = []) {
43
41
  return await job();
44
42
  } catch (e) {
45
43
  if (e instanceof AggregateError)
46
- console.error(`[flakiness.io err]`, e.errors[0].message);
44
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
47
45
  else if (e instanceof Error)
48
- console.error(`[flakiness.io err]`, e.message);
46
+ console.error(`[flakiness.io err]`, errorText(e));
49
47
  else
50
48
  console.error(`[flakiness.io err]`, e);
51
49
  await new Promise((x) => setTimeout(x, timeout));
@@ -63,6 +61,7 @@ var httpUtils;
63
61
  reject = b;
64
62
  });
65
63
  const protocol = url.startsWith("https") ? https : http;
64
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
66
65
  const request = protocol.request(url, { method, headers }, (res) => {
67
66
  const chunks = [];
68
67
  res.on("data", (chunk) => chunks.push(chunk));
@@ -0,0 +1,43 @@
1
+ // src/localGit.ts
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ var execAsync = promisify(exec);
5
+ async function listLocalCommits(gitRoot, head, count) {
6
+ const FIELD_SEPARATOR = "|~|";
7
+ const RECORD_SEPARATOR = "\0";
8
+ const prettyFormat = [
9
+ "%H",
10
+ // %H: Full commit hash
11
+ "%at",
12
+ // %at: Author date as a Unix timestamp (seconds since epoch)
13
+ "%an",
14
+ // %an: Author name
15
+ "%s"
16
+ // %s: Subject (the first line of the commit message)
17
+ ].join(FIELD_SEPARATOR);
18
+ const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
19
+ try {
20
+ const { stdout } = await execAsync(command, { cwd: gitRoot });
21
+ if (!stdout) {
22
+ return [];
23
+ }
24
+ return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
25
+ const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
26
+ return {
27
+ commitId,
28
+ timestamp: parseInt(timestampStr, 10) * 1e3,
29
+ // Convert timestamp from seconds to milliseconds
30
+ author,
31
+ message,
32
+ walkIndex: 0
33
+ };
34
+ });
35
+ } catch (error) {
36
+ console.error(`Failed to list commits for repository at ${gitRoot}:`, error);
37
+ throw error;
38
+ }
39
+ }
40
+ export {
41
+ listLocalCommits
42
+ };
43
+ //# sourceMappingURL=localGit.js.map
@@ -0,0 +1,40 @@
1
+ // src/localReportApi.ts
2
+ import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
3
+ import fs from "fs";
4
+ import { z } from "zod/v4";
5
+ var t = TypedHTTP.Router.create();
6
+ var localReportRouter = {
7
+ ping: t.get({
8
+ handler: async () => {
9
+ return "pong";
10
+ }
11
+ }),
12
+ lastCommits: t.get({
13
+ handler: async ({ ctx }) => {
14
+ return ctx.commits;
15
+ }
16
+ }),
17
+ report: {
18
+ attachment: t.rawMethod("GET", {
19
+ input: z.object({
20
+ attachmentId: z.string().min(1).max(100).transform((id) => id)
21
+ }),
22
+ handler: async ({ ctx, input }) => {
23
+ const idx = ctx.attachmentIdToPath.get(input.attachmentId);
24
+ if (!idx)
25
+ throw TypedHTTP.HttpError.withCode("NOT_FOUND");
26
+ const buffer = await fs.promises.readFile(idx.path);
27
+ return TypedHTTP.ok(buffer, idx.contentType);
28
+ }
29
+ }),
30
+ json: t.get({
31
+ handler: async ({ ctx }) => {
32
+ return ctx.report;
33
+ }
34
+ })
35
+ }
36
+ };
37
+ export {
38
+ localReportRouter
39
+ };
40
+ //# sourceMappingURL=localReportApi.js.map