@flakiness/sdk 0.148.0 → 0.149.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/showReport.js CHANGED
@@ -1,32 +1,97 @@
1
1
  // src/showReport.ts
2
- import { randomUUIDBase62 } from "@flakiness/shared/node/nodeutils.js";
3
2
  import chalk from "chalk";
4
3
  import open from "open";
5
4
 
6
- // src/flakinessProjectConfig.ts
7
- import fs from "fs";
8
- import path from "path";
9
-
10
- // src/git.ts
11
- import assert from "assert";
12
-
13
- // src/pathutils.ts
14
- import { posix as posixPath, win32 as win32Path } from "path";
15
- var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
16
- var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
17
- function normalizePath(aPath) {
18
- if (IS_WIN32_PATH.test(aPath)) {
19
- aPath = aPath.split(win32Path.sep).join(posixPath.sep);
20
- }
21
- if (IS_ALMOST_POSIX_PATH.test(aPath))
22
- return "/" + aPath[0] + aPath.substring(2);
23
- return aPath;
24
- }
25
-
26
- // src/utils.ts
5
+ // src/_internalUtils.ts
27
6
  import { spawnSync } from "child_process";
7
+ import crypto from "crypto";
8
+ import http from "http";
9
+ import https from "https";
10
+ import util from "util";
11
+ import zlib from "zlib";
12
+ var asyncBrotliCompress = util.promisify(zlib.brotliCompress);
28
13
  var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
29
- 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");
14
+ function errorText(error) {
15
+ return FLAKINESS_DBG ? error.stack : error.message;
16
+ }
17
+ async function retryWithBackoff(job, backoff = []) {
18
+ for (const timeout of backoff) {
19
+ try {
20
+ return await job();
21
+ } catch (e) {
22
+ if (e instanceof AggregateError)
23
+ console.error(`[flakiness.io err]`, errorText(e.errors[0]));
24
+ else if (e instanceof Error)
25
+ console.error(`[flakiness.io err]`, errorText(e));
26
+ else
27
+ console.error(`[flakiness.io err]`, e);
28
+ await new Promise((x) => setTimeout(x, timeout));
29
+ }
30
+ }
31
+ return await job();
32
+ }
33
+ var httpUtils;
34
+ ((httpUtils2) => {
35
+ function createRequest({ url, method = "get", headers = {} }) {
36
+ let resolve2;
37
+ let reject;
38
+ const responseDataPromise = new Promise((a, b) => {
39
+ resolve2 = a;
40
+ reject = b;
41
+ });
42
+ const protocol = url.startsWith("https") ? https : http;
43
+ headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
44
+ const request = protocol.request(url, { method, headers }, (res) => {
45
+ const chunks = [];
46
+ res.on("data", (chunk) => chunks.push(chunk));
47
+ res.on("end", () => {
48
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300)
49
+ resolve2(Buffer.concat(chunks));
50
+ else
51
+ reject(new Error(`Request to ${url} failed with ${res.statusCode}`));
52
+ });
53
+ res.on("error", (error) => reject(error));
54
+ });
55
+ request.on("error", reject);
56
+ return { request, responseDataPromise };
57
+ }
58
+ httpUtils2.createRequest = createRequest;
59
+ async function getBuffer(url, backoff) {
60
+ return await retryWithBackoff(async () => {
61
+ const { request, responseDataPromise } = createRequest({ url });
62
+ request.end();
63
+ return await responseDataPromise;
64
+ }, backoff);
65
+ }
66
+ httpUtils2.getBuffer = getBuffer;
67
+ async function getText(url, backoff) {
68
+ const buffer = await getBuffer(url, backoff);
69
+ return buffer.toString("utf-8");
70
+ }
71
+ httpUtils2.getText = getText;
72
+ async function getJSON(url) {
73
+ return JSON.parse(await getText(url));
74
+ }
75
+ httpUtils2.getJSON = getJSON;
76
+ async function postText(url, text, backoff) {
77
+ const headers = {
78
+ "Content-Type": "application/json",
79
+ "Content-Length": Buffer.byteLength(text) + ""
80
+ };
81
+ return await retryWithBackoff(async () => {
82
+ const { request, responseDataPromise } = createRequest({ url, headers, method: "post" });
83
+ request.write(text);
84
+ request.end();
85
+ return await responseDataPromise;
86
+ }, backoff);
87
+ }
88
+ httpUtils2.postText = postText;
89
+ async function postJSON(url, json, backoff) {
90
+ const buffer = await postText(url, JSON.stringify(json), backoff);
91
+ return JSON.parse(buffer.toString("utf-8"));
92
+ }
93
+ httpUtils2.postJSON = postJSON;
94
+ })(httpUtils || (httpUtils = {}));
30
95
  function shell(command, args, options) {
31
96
  try {
32
97
  const result = spawnSync(command, args, { encoding: "utf-8", ...options });
@@ -39,15 +104,227 @@ function shell(command, args, options) {
39
104
  return void 0;
40
105
  }
41
106
  }
107
+ function randomUUIDBase62() {
108
+ const BASE62_CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
109
+ let num = BigInt("0x" + crypto.randomUUID().replace(/-/g, ""));
110
+ if (num === 0n)
111
+ return BASE62_CHARSET[0];
112
+ const chars = [];
113
+ while (num > 0n) {
114
+ const remainder = Number(num % 62n);
115
+ num /= 62n;
116
+ chars.push(BASE62_CHARSET[remainder]);
117
+ }
118
+ return chars.reverse().join("");
119
+ }
42
120
 
43
- // src/git.ts
44
- function computeGitRoot(somePathInsideGitRepo) {
45
- const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
46
- cwd: somePathInsideGitRepo,
47
- encoding: "utf-8"
48
- });
49
- assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
50
- return normalizePath(root);
121
+ // src/flakinessProjectConfig.ts
122
+ import fs from "fs";
123
+ import path from "path";
124
+
125
+ // src/gitWorktree.ts
126
+ import assert from "assert";
127
+ import { exec } from "child_process";
128
+ import debug from "debug";
129
+ import { posix as posixPath, win32 as win32Path } from "path";
130
+ import { promisify } from "util";
131
+ var log = debug("fk:git");
132
+ var execAsync = promisify(exec);
133
+ var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
134
+ var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
135
+ function toPosixAbsolutePath(absolutePath) {
136
+ if (IS_WIN32_PATH.test(absolutePath)) {
137
+ absolutePath = absolutePath.split(win32Path.sep).join(posixPath.sep);
138
+ }
139
+ if (IS_ALMOST_POSIX_PATH.test(absolutePath))
140
+ return "/" + absolutePath[0] + absolutePath.substring(2);
141
+ return absolutePath;
142
+ }
143
+ function toNativeAbsolutePath(posix) {
144
+ if (process.platform !== "win32")
145
+ return posix;
146
+ assert(posix.startsWith("/"), "The path must be absolute");
147
+ const m = posix.match(/^\/([a-zA-Z])(\/.*)?$/);
148
+ assert(m, `Invalid POSIX path: ${posix}`);
149
+ const drive = m[1];
150
+ const rest = (m[2] ?? "").split(posixPath.sep).join(win32Path.sep);
151
+ return drive.toUpperCase() + ":" + rest;
152
+ }
153
+ var GitWorktree = class _GitWorktree {
154
+ constructor(_gitRoot) {
155
+ this._gitRoot = _gitRoot;
156
+ this._posixGitRoot = toPosixAbsolutePath(this._gitRoot);
157
+ }
158
+ /**
159
+ * Creates a GitWorktree instance from any path inside a git repository.
160
+ *
161
+ * @param {string} somePathInsideGitRepo - Any path (file or directory) within a git repository.
162
+ * Can be absolute or relative. The function will locate the git root directory.
163
+ *
164
+ * @returns {GitWorktree} A new GitWorktree instance bound to the discovered git root.
165
+ *
166
+ * @throws {Error} Throws if the path is not inside a git repository or if git commands fail.
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const worktree = GitWorktree.create('./src/my-test.ts');
171
+ * const gitRoot = worktree.rootPath();
172
+ * ```
173
+ */
174
+ static create(somePathInsideGitRepo) {
175
+ const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
176
+ cwd: somePathInsideGitRepo,
177
+ encoding: "utf-8"
178
+ });
179
+ assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
180
+ return new _GitWorktree(root);
181
+ }
182
+ _posixGitRoot;
183
+ /**
184
+ * Returns the native absolute path of the git repository root directory.
185
+ *
186
+ * @returns {string} Native absolute path to the git root. Format matches the current platform
187
+ * (Windows or POSIX).
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const root = worktree.rootPath();
192
+ * // On Windows: 'D:\project'
193
+ * // On Unix: '/project'
194
+ * ```
195
+ */
196
+ rootPath() {
197
+ return this._gitRoot;
198
+ }
199
+ /**
200
+ * Returns the commit ID (SHA-1 hash) of the current HEAD commit.
201
+ *
202
+ * @returns {FlakinessReport.CommitId} Full 40-character commit hash of the HEAD commit.
203
+ *
204
+ * @throws {Error} Throws if git command fails or repository is in an invalid state.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const commitId = worktree.headCommitId();
209
+ * // Returns: 'a1b2c3d4e5f6...' (40-character SHA-1)
210
+ * ```
211
+ */
212
+ headCommitId() {
213
+ const sha = shell(`git`, ["rev-parse", "HEAD"], {
214
+ cwd: this._gitRoot,
215
+ encoding: "utf-8"
216
+ });
217
+ assert(sha, `FAILED: git rev-parse HEAD @ ${this._gitRoot}`);
218
+ return sha.trim();
219
+ }
220
+ /**
221
+ * Converts a native absolute path to a git-relative POSIX path.
222
+ *
223
+ * Takes any absolute path (Windows or POSIX format) and converts it to a POSIX path
224
+ * relative to the git repository root. This is essential for Flakiness reports where
225
+ * all file paths must be git-relative and use POSIX separators.
226
+ *
227
+ * @param {string} absolutePath - Native absolute path to convert. Can be in Windows format
228
+ * (e.g., `D:\project\src\test.ts`) or POSIX format (e.g., `/project/src/test.ts`).
229
+ *
230
+ * @returns {FlakinessReport.GitFilePath} POSIX path relative to git root (e.g., `src/test.ts`).
231
+ * Returns an empty string if the path is the git root itself.
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * const gitPath = worktree.gitPath('/Users/project/src/test.ts');
236
+ * // Returns: 'src/test.ts'
237
+ * ```
238
+ */
239
+ gitPath(absolutePath) {
240
+ return posixPath.relative(this._posixGitRoot, toPosixAbsolutePath(absolutePath));
241
+ }
242
+ /**
243
+ * Converts a git-relative POSIX path to a native absolute path.
244
+ *
245
+ * Takes a POSIX path relative to the git root and converts it to the native absolute path
246
+ * format for the current platform (Windows or POSIX). This is the inverse of `gitPath()`.
247
+ *
248
+ * @param {FlakinessReport.GitFilePath} relativePath - POSIX path relative to git root
249
+ * (e.g., `src/test.ts`).
250
+ *
251
+ * @returns {string} Native absolute path. On Windows, returns Windows format (e.g., `D:\project\src\test.ts`).
252
+ * On POSIX systems, returns POSIX format (e.g., `/project/src/test.ts`).
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * const absolutePath = worktree.absolutePath('src/test.ts');
257
+ * // On Windows: 'D:\project\src\test.ts'
258
+ * // On Unix: '/project/src/test.ts'
259
+ * ```
260
+ */
261
+ absolutePath(relativePath) {
262
+ return toNativeAbsolutePath(posixPath.join(this._posixGitRoot, relativePath));
263
+ }
264
+ /**
265
+ * Lists recent commits from the repository.
266
+ *
267
+ * Retrieves commit information including commit ID, timestamp, author, message, and parent commits.
268
+ * Note: CI environments often have shallow checkouts with limited history, which may affect
269
+ * the number of commits returned.
270
+ *
271
+ * @param {number} count - Maximum number of commits to retrieve, starting from HEAD.
272
+ *
273
+ * @returns {Promise<GitCommit[]>} Promise that resolves to an array of commit objects, ordered
274
+ * from most recent to oldest. Each commit includes:
275
+ * - `commitId` - Full commit hash
276
+ * - `timestamp` - Commit timestamp in milliseconds since Unix epoch
277
+ * - `message` - Commit message (subject line)
278
+ * - `author` - Author name
279
+ * - `parents` - Array of parent commit IDs
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * const commits = await worktree.listCommits(10);
284
+ * console.log(`Latest commit: ${commits[0].message}`);
285
+ * ```
286
+ */
287
+ async listCommits(count) {
288
+ return await listCommits(this._gitRoot, "HEAD", count);
289
+ }
290
+ };
291
+ async function listCommits(gitRoot, head, count) {
292
+ const FIELD_SEPARATOR = "|~|";
293
+ const RECORD_SEPARATOR = "\0";
294
+ const prettyFormat = [
295
+ "%H",
296
+ // Full commit hash
297
+ "%ct",
298
+ // Commit timestamp (Unix seconds)
299
+ "%an",
300
+ // Author name
301
+ "%s",
302
+ // Subject line
303
+ "%P"
304
+ // Parent hashes (space-separated)
305
+ ].join(FIELD_SEPARATOR);
306
+ const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
307
+ try {
308
+ const { stdout } = await execAsync(command, { cwd: gitRoot });
309
+ if (!stdout) {
310
+ return [];
311
+ }
312
+ return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
313
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
314
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
315
+ return {
316
+ commitId,
317
+ timestamp: parseInt(timestampStr, 10) * 1e3,
318
+ author,
319
+ message,
320
+ parents,
321
+ walkIndex: 0
322
+ };
323
+ });
324
+ } catch (error) {
325
+ log(`Failed to list commits for repository at ${gitRoot}:`, error);
326
+ return [];
327
+ }
51
328
  }
52
329
 
53
330
  // src/flakinessProjectConfig.ts
@@ -67,8 +344,8 @@ function computeConfigPath() {
67
344
  return configPath;
68
345
  }
69
346
  try {
70
- const gitRoot = computeGitRoot(process.cwd());
71
- return createConfigPath(gitRoot);
347
+ const worktree = GitWorktree.create(process.cwd());
348
+ return createConfigPath(worktree.rootPath());
72
349
  } catch (e) {
73
350
  return createConfigPath(process.cwd());
74
351
  }
@@ -78,33 +355,110 @@ var FlakinessProjectConfig = class _FlakinessProjectConfig {
78
355
  this._configPath = _configPath;
79
356
  this._config = _config;
80
357
  }
358
+ /**
359
+ * Loads the Flakiness project configuration from disk.
360
+ *
361
+ * Searches for an existing `.flakiness/config.json` file starting from the current working
362
+ * directory and walking up the directory tree. If no config exists, it determines the
363
+ * appropriate location (git root or current directory) for future saves.
364
+ *
365
+ * @returns {Promise<FlakinessProjectConfig>} Promise that resolves to a FlakinessProjectConfig
366
+ * instance. If no config file exists, returns an instance with default/empty values.
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * const config = await FlakinessProjectConfig.load();
371
+ * const projectId = config.projectPublicId();
372
+ * ```
373
+ */
81
374
  static async load() {
82
375
  const configPath = ensureConfigPath();
83
376
  const data = await fs.promises.readFile(configPath, "utf-8").catch((e) => void 0);
84
377
  const json = data ? JSON.parse(data) : {};
85
378
  return new _FlakinessProjectConfig(configPath, json);
86
379
  }
380
+ /**
381
+ * Creates a new empty Flakiness project configuration.
382
+ *
383
+ * Creates a configuration instance with no values set. Use this when you want to build
384
+ * a configuration from scratch. Call `save()` to persist it to disk.
385
+ *
386
+ * @returns {FlakinessProjectConfig} A new empty configuration instance.
387
+ *
388
+ * @example
389
+ * ```typescript
390
+ * const config = FlakinessProjectConfig.createEmpty();
391
+ * config.setProjectPublicId('my-project-id');
392
+ * await config.save();
393
+ * ```
394
+ */
87
395
  static createEmpty() {
88
396
  return new _FlakinessProjectConfig(ensureConfigPath(), {});
89
397
  }
398
+ /**
399
+ * Returns the absolute path to the configuration file.
400
+ *
401
+ * @returns {string} Absolute path to `.flakiness/config.json`.
402
+ */
90
403
  path() {
91
404
  return this._configPath;
92
405
  }
406
+ /**
407
+ * Returns the project's public ID, if configured.
408
+ *
409
+ * The project public ID is used to associate reports with a specific Flakiness.io project.
410
+ *
411
+ * @returns {string | undefined} Project public ID, or `undefined` if not set.
412
+ */
93
413
  projectPublicId() {
94
414
  return this._config.projectPublicId;
95
415
  }
416
+ /**
417
+ * Returns the report viewer URL, either custom or default.
418
+ *
419
+ * @returns {string} Custom report viewer URL if configured, otherwise the default
420
+ * `https://report.flakiness.io`.
421
+ */
96
422
  reportViewerUrl() {
97
423
  return this._config.customReportViewerUrl ?? "https://report.flakiness.io";
98
424
  }
425
+ /**
426
+ * Sets or clears the custom report viewer URL.
427
+ *
428
+ * @param {string | undefined} url - Custom report viewer URL to use, or `undefined` to
429
+ * clear and use the default URL.
430
+ */
99
431
  setCustomReportViewerUrl(url) {
100
432
  if (url)
101
433
  this._config.customReportViewerUrl = url;
102
434
  else
103
435
  delete this._config.customReportViewerUrl;
104
436
  }
437
+ /**
438
+ * Sets the project's public ID.
439
+ *
440
+ * @param {string | undefined} projectId - Project public ID to set, or `undefined` to clear.
441
+ */
105
442
  setProjectPublicId(projectId) {
106
443
  this._config.projectPublicId = projectId;
107
444
  }
445
+ /**
446
+ * Saves the configuration to disk.
447
+ *
448
+ * Writes the current configuration values to `.flakiness/config.json`. Creates the
449
+ * `.flakiness` directory if it doesn't exist.
450
+ *
451
+ * @returns {Promise<void>} Promise that resolves when the file has been written.
452
+ *
453
+ * @throws {Error} Throws if unable to create directories or write the file.
454
+ *
455
+ * @example
456
+ * ```typescript
457
+ * const config = await FlakinessProjectConfig.load();
458
+ * config.setProjectPublicId('my-project');
459
+ * await config.save();
460
+ * ```
461
+ */
108
462
  async save() {
109
463
  await fs.promises.mkdir(path.dirname(this._configPath), { recursive: true });
110
464
  await fs.promises.writeFile(this._configPath, JSON.stringify(this._config, null, 2));
@@ -112,11 +466,11 @@ var FlakinessProjectConfig = class _FlakinessProjectConfig {
112
466
  };
113
467
 
114
468
  // src/staticServer.ts
115
- import debug from "debug";
469
+ import debug2 from "debug";
116
470
  import * as fs2 from "fs";
117
- import * as http from "http";
471
+ import * as http2 from "http";
118
472
  import * as path2 from "path";
119
- var log = debug("fk:static_server");
473
+ var log2 = debug2("fk:static_server");
120
474
  var StaticServer = class {
121
475
  _server;
122
476
  _absoluteFolderPath;
@@ -138,7 +492,7 @@ var StaticServer = class {
138
492
  this._pathPrefix = "/" + pathPrefix.replace(/^\//, "").replace(/\/$/, "");
139
493
  this._absoluteFolderPath = path2.resolve(folderPath);
140
494
  this._cors = cors;
141
- this._server = http.createServer((req, res) => this._handleRequest(req, res));
495
+ this._server = http2.createServer((req, res) => this._handleRequest(req, res));
142
496
  }
143
497
  port() {
144
498
  const address = this._server.address();
@@ -167,7 +521,7 @@ var StaticServer = class {
167
521
  this._server.once("error", errListener);
168
522
  this._server.listen(port, host);
169
523
  await result;
170
- log('Serving "%s" on "%s"', this._absoluteFolderPath, this.address());
524
+ log2('Serving "%s" on "%s"', this._absoluteFolderPath, this.address());
171
525
  }
172
526
  async start(port, host = "127.0.0.1") {
173
527
  if (port === 0) {
@@ -180,12 +534,12 @@ var StaticServer = class {
180
534
  return this.address();
181
535
  if (err.code !== "EADDRINUSE")
182
536
  throw err;
183
- log("Port %d is busy (EADDRINUSE). Trying next port...", port);
537
+ log2("Port %d is busy (EADDRINUSE). Trying next port...", port);
184
538
  port = port + 1;
185
539
  if (port > 65535)
186
540
  port = 4e3;
187
541
  }
188
- log("All sequential ports busy. Falling back to random port.");
542
+ log2("All sequential ports busy. Falling back to random port.");
189
543
  await this._startServer(0, host);
190
544
  return this.address();
191
545
  }
@@ -193,10 +547,10 @@ var StaticServer = class {
193
547
  return new Promise((resolve2, reject) => {
194
548
  this._server.close((err) => {
195
549
  if (err) {
196
- log("Error stopping server: %o", err);
550
+ log2("Error stopping server: %o", err);
197
551
  reject(err);
198
552
  } else {
199
- log("Server stopped.");
553
+ log2("Server stopped.");
200
554
  resolve2();
201
555
  }
202
556
  });
@@ -205,7 +559,7 @@ var StaticServer = class {
205
559
  _errorResponse(req, res, code, text) {
206
560
  res.writeHead(code, { "Content-Type": "text/plain" });
207
561
  res.end(text);
208
- log(`[${code}] ${req.method} ${req.url}`);
562
+ log2(`[${code}] ${req.method} ${req.url}`);
209
563
  }
210
564
  _handleRequest(req, res) {
211
565
  const { url, method } = req;
@@ -223,9 +577,9 @@ var StaticServer = class {
223
577
  this._errorResponse(req, res, 405, "Method Not Allowed");
224
578
  return;
225
579
  }
226
- req.on("aborted", () => log(`ABORTED ${req.method} ${req.url}`));
580
+ req.on("aborted", () => log2(`ABORTED ${req.method} ${req.url}`));
227
581
  res.on("close", () => {
228
- if (!res.headersSent) log(`CLOSED BEFORE SEND ${req.method} ${req.url}`);
582
+ if (!res.headersSent) log2(`CLOSED BEFORE SEND ${req.method} ${req.url}`);
229
583
  });
230
584
  if (!url || !url.startsWith(this._pathPrefix)) {
231
585
  this._errorResponse(req, res, 404, "Not Found");
@@ -246,11 +600,11 @@ var StaticServer = class {
246
600
  const ext = path2.extname(filePath).toLowerCase();
247
601
  const contentType = this._mimeTypes[ext] || "application/octet-stream";
248
602
  res.writeHead(200, { "Content-Type": contentType });
249
- log(`[200] ${req.method} ${req.url} -> ${filePath}`);
603
+ log2(`[200] ${req.method} ${req.url} -> ${filePath}`);
250
604
  const readStream = fs2.createReadStream(filePath);
251
605
  readStream.pipe(res);
252
606
  readStream.on("error", (err2) => {
253
- log("Stream error: %o", err2);
607
+ log2("Stream error: %o", err2);
254
608
  res.end();
255
609
  });
256
610
  });
@@ -0,0 +1,9 @@
1
+ // src/stripAnsi.ts
2
+ 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");
3
+ function stripAnsi(str) {
4
+ return str.replace(ansiRegex, "");
5
+ }
6
+ export {
7
+ stripAnsi
8
+ };
9
+ //# sourceMappingURL=stripAnsi.js.map
@@ -44,9 +44,24 @@ function toFKUtilization(sample, previous) {
44
44
  };
45
45
  }
46
46
  var SystemUtilizationSampler = class {
47
+ /**
48
+ * The accumulated system utilization data.
49
+ *
50
+ * This object is populated as samples are collected and can be directly included in
51
+ * Flakiness reports. It contains:
52
+ * - `samples` - Array of utilization samples with CPU/memory percentages and durations
53
+ * - `startTimestamp` - Timestamp when sampling began
54
+ * - `totalMemoryBytes` - Total system memory in bytes
55
+ */
47
56
  result;
48
57
  _lastSample = getSystemUtilization();
49
58
  _timer;
59
+ /**
60
+ * Creates a new SystemUtilizationSampler and starts sampling immediately.
61
+ *
62
+ * The first sample is collected after 50ms, and subsequent samples are collected
63
+ * every 1000ms. Call `dispose()` to stop sampling and clean up resources.
64
+ */
50
65
  constructor() {
51
66
  this.result = {
52
67
  samples: [],
@@ -61,6 +76,12 @@ var SystemUtilizationSampler = class {
61
76
  this._lastSample = sample;
62
77
  this._timer = setTimeout(this._addSample.bind(this), 1e3);
63
78
  }
79
+ /**
80
+ * Stops sampling and cleans up resources.
81
+ *
82
+ * Call this method when you're done collecting utilization data to stop the sampling
83
+ * timer and prevent memory leaks. The `result` object remains accessible after disposal.
84
+ */
64
85
  dispose() {
65
86
  clearTimeout(this._timer);
66
87
  }